From 38b6c4f5eb49d404ff3f2248b017cea415c49618 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Fri, 15 May 2020 15:55:27 -0400 Subject: [PATCH 01/16] Make lttng_dynamic_buffer_append_buffer const-correct MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérémie Galarneau Change-Id: I6d42d2d9f8beca15b026fc43ee57270173480c2d --- src/common/dynamic-buffer.c | 2 +- src/common/dynamic-buffer.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/dynamic-buffer.c b/src/common/dynamic-buffer.c index 1ae2fa2c5..7005ef501 100644 --- a/src/common/dynamic-buffer.c +++ b/src/common/dynamic-buffer.c @@ -68,7 +68,7 @@ end: LTTNG_HIDDEN int lttng_dynamic_buffer_append_buffer(struct lttng_dynamic_buffer *dst_buffer, - struct lttng_dynamic_buffer *src_buffer) + const struct lttng_dynamic_buffer *src_buffer) { int ret; diff --git a/src/common/dynamic-buffer.h b/src/common/dynamic-buffer.h index 1f27f7403..1352a67c1 100644 --- a/src/common/dynamic-buffer.h +++ b/src/common/dynamic-buffer.h @@ -46,7 +46,7 @@ int lttng_dynamic_buffer_append(struct lttng_dynamic_buffer *buffer, */ LTTNG_HIDDEN int lttng_dynamic_buffer_append_buffer(struct lttng_dynamic_buffer *dst_buffer, - struct lttng_dynamic_buffer *src_buffer); + const struct lttng_dynamic_buffer *src_buffer); /* * Set the buffer's size to new_size. The capacity of the buffer will -- 2.34.1 From fbd55aaefb30b4cda8d25a02975559724d387e55 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Fri, 15 May 2020 16:04:11 -0400 Subject: [PATCH 02/16] Add lttng_dynamic_buffer_append_view util MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Add lttng_dynamic_buffer_append_view() which appends the contents of a buffer view to a dynamic buffer. Signed-off-by: Jérémie Galarneau Change-Id: I4082ba2c848b79aa2116847987067453638de441 --- src/common/dynamic-buffer.c | 18 ++++++++++++++++++ src/common/dynamic-buffer.h | 11 +++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/common/dynamic-buffer.c b/src/common/dynamic-buffer.c index 7005ef501..b6544e62b 100644 --- a/src/common/dynamic-buffer.c +++ b/src/common/dynamic-buffer.c @@ -6,6 +6,7 @@ */ #include +#include #include #include @@ -83,6 +84,23 @@ end: return ret; } +LTTNG_HIDDEN +int lttng_dynamic_buffer_append_view(struct lttng_dynamic_buffer *buffer, + const struct lttng_buffer_view *src) +{ + int ret; + + if (!buffer || !src) { + ret = -1; + goto end; + } + + ret = lttng_dynamic_buffer_append(buffer, src->data, + src->size); +end: + return ret; +} + LTTNG_HIDDEN int lttng_dynamic_buffer_set_size(struct lttng_dynamic_buffer *buffer, size_t new_size) diff --git a/src/common/dynamic-buffer.h b/src/common/dynamic-buffer.h index 1352a67c1..3435c846c 100644 --- a/src/common/dynamic-buffer.h +++ b/src/common/dynamic-buffer.h @@ -12,6 +12,8 @@ #include #include +struct lttng_buffer_view; + struct lttng_dynamic_buffer { char *data; /* size is the buffer's currently used capacity. */ @@ -48,6 +50,15 @@ LTTNG_HIDDEN int lttng_dynamic_buffer_append_buffer(struct lttng_dynamic_buffer *dst_buffer, const struct lttng_dynamic_buffer *src_buffer); +/* + * Performs the same action as lttng_dynamic_buffer_append(), but using a + * buffer view as the source buffer. The source buffer's size is used in lieu + * of "len". + */ +LTTNG_HIDDEN +int lttng_dynamic_buffer_append_view(struct lttng_dynamic_buffer *buffer, + const struct lttng_buffer_view *view); + /* * Set the buffer's size to new_size. The capacity of the buffer will * be expanded (if necessary) to accommodates new_size. Areas acquired by -- 2.34.1 From dec2c8e12255d3533664cba9042f0d051c4207ac Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Thu, 14 May 2020 16:08:56 -0400 Subject: [PATCH 03/16] Fix: lttng: Destroying session message repeated during destruction MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Observed Issue ============== The `Destroying session X` is repeated indifinitely whenever the data pending phase lasts more than one iteration. ``` $ lttng destroy Destroying session eloi_turcotte.Destroying session eloi_turcotte.Destroying session eloi_turcotte.Destroying session eloi_turcotte.D ``` Cause ===== Missing check that the message has been printed. Solution ======== Use the same check as is done later for lttng_destruction_handle_wait_for_completion(). Known drawbacks =============== None. Signed-off-by: Jérémie Galarneau Change-Id: I6cd29d917925644a4994c515b4177bbd05ffa98e --- src/bin/lttng/commands/destroy.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bin/lttng/commands/destroy.c b/src/bin/lttng/commands/destroy.c index eed1fe5c5..2e473f519 100644 --- a/src/bin/lttng/commands/destroy.c +++ b/src/bin/lttng/commands/destroy.c @@ -87,10 +87,13 @@ static int destroy_session(struct lttng_session *session) * availability. */ if (ret) { - _MSG("Destroying session %s", session->name); - newline_needed = true; - printed_destroy_msg = true; - fflush(stdout); + if (!printed_destroy_msg) { + _MSG("Destroying session %s", + session->name); + newline_needed = true; + printed_destroy_msg = true; + fflush(stdout); + } usleep(DEFAULT_DATA_AVAILABILITY_WAIT_TIME_US); _MSG("."); -- 2.34.1 From 3e778ab02ef13b55c0be8f752e126eee55e0956b Mon Sep 17 00:00:00 2001 From: Jonathan Rajotte Date: Wed, 20 May 2020 20:53:45 -0400 Subject: [PATCH 04/16] Fix: common: fs_handle_seek returns negative value on success MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Observed issue ============== Babeltrace 1/2 fails to fetch data from a live session. Error: PLUGIN/SRC.CTF.LTTNG-LIVE/VIEWER lttng_live_get_stream_bytes@viewer-connection.c:1593 [lttng-live] Received get_data_packet response: error PLUGIN/CTF/MSG-ITER request_medium_bytes@msg-iter.c:546 [lttng-live] User function failed: status=ERROR PLUGIN/CTF/MSG-ITER ctf_msg_iter_get_next_message@msg-iter.c:2881 [lttng-live] Cannot handle state: msg-it-addr=0x562d87521a40, state=DSCOPE_TRACE_PACKET_HEADER_BEGIN PLUGIN/SRC.CTF.LTTNG-LIVE lttng_live_iterator_next_handle_one_active_data_stream@lttng-live.c:821 [lttng-live] CTF message iterator failed to get next message: msg-iter=0x562d87521a40, msg-iter-status=ERROR PLUGIN/SRC.CTF.LTTNG-LIVE lttng_live_msg_iter_next@lttng-live.c:1499 [lttng-live] Error preparing the next batch of messages: live-iter-status=LTTNG_LIVE_ITERATOR_STATUS_ERROR LIB/MSG-ITER bt_message_iterator_next@iterator.c:865 Component input port message iterator's "next" method failed: iter-addr=0x562d8751ab70, iter-upstream-comp-name="lttng-live", iter-upstream-comp-log-level=WARNING, iter-upstream-comp-class-type=SOURCE, iter-upstream-comp-class-name="lttng-live", iter-upstream-comp-class-partial-descr="Connect to an LTTng relay daemon", iter-upstream-port-type=OUTPUT, iter-upstream-port-name="out", status=ERROR PLUGIN/FLT.UTILS.MUXER muxer_upstream_msg_iter_next@muxer.c:446 [muxer] Upstream iterator's next method returned an error: status=ERROR PLUGIN/FLT.UTILS.MUXER validate_muxer_upstream_msg_iters@muxer.c:989 [muxer] Cannot validate muxer's upstream message iterator wrapper: muxer-msg-iter-addr=0x562d87515280, muxer-upstream-msg-iter-wrap-addr=0x562d8751ca90 PLUGIN/FLT.UTILS.MUXER muxer_msg_iter_next@muxer.c:1417 [muxer] Cannot get next message: comp-addr=0x562d8751a260, muxer-comp-addr=0x562d8751a2e0, muxer-msg-iter-addr=0x562d87515280, msg-iter-addr=0x562d8751aa90, status=ERROR LIB/MSG-ITER bt_message_iterator_next@iterator.c:865 Component input port message iterator's "next" method failed: iter-addr=0x562d8751aa90, iter-upstream-comp-name="muxer", iter-upstream-comp-log-level=WARNING, iter-upstream-comp-class-type=FILTER, iter-upstream-comp-class-name="muxer", iter-upstream-comp-class-partial-descr="Sort messages from multiple inpu", iter-upstream-port-type=OUTPUT, iter-upstream-port-name="out", status=ERROR LIB/GRAPH consume_graph_sink@graph.c:462 Component's "consume" method failed: status=ERROR, comp-addr=0x562d8751a3d0, comp-name="pretty", comp-log-level=WARNING, comp-class-type=SINK, comp-class-name="pretty", comp-class-partial-descr="Pretty-print messages (`text` fo", comp-class-is-frozen=0, comp-class-so-handle-addr=0x562d8751a110, comp-class-so-handle-path="/usr/local/lib/babeltrace2/plugins/babeltrace-plugin-text.so", comp-input-port-count=1, comp-output-port-count=0 CLI cmd_run@babeltrace2.c:2529 Graph failed to complete successfully PLUGIN/SRC.CTF.LTTNG-LIVE/VIEWER lttng_live_session_detach@viewer-connection.c:1211 [lttng-live] Unknown detach return code 0 The relevant relayd log: DEBUG2 - Relay get data packet (in viewer_get_packet() at live.c:1770) PERROR - Failed to seek file system handle of viewer stream 4 to offset 2244861952: Success (in viewer_get_packet() at live.c:1810) DEBUG1 - Sent 262156 bytes for stream 4 (in viewer_get_packet() at live.c:1852) Cause ===== The fs_handle_seek function calls lseek on a stream file of ~2.5GB. The return value of the lseek call is downcasted from off_t (signed 64 bit on my system) to int. The resulting value is negative and force an error at the call sites. Solution ======== Use off_t as the return type. Note that current call sites of fs_handle_seek already expect an off_t return value. ag fs_handle_seek: src/bin/lttng-relayd/stream.c 249: lseek_ret = fs_handle_seek(previous_stream_file, previous_stream_copy_origin, SEEK_SET); src/bin/lttng-relayd/live.c 1804: lseek_ret = fs_handle_seek(vstream->stream_file.handle, src/bin/lttng-relayd/viewer-stream.c 176: lseek_ret = fs_handle_seek( ag lseek_ret: src/bin/lttng-relayd/stream.c 193: off_t lseek_ret, previous_stream_copy_origin; src/bin/lttng-relayd/live.c 1760: off_t lseek_ret; src/bin/lttng-relayd/viewer-stream.c 174: off_t lseek_ret; Known drawbacks ========= This limitation existed before this patch. On 32bit system without _FILE_OFFSET_BITS=64 defined at compile time (-D_FILE_OFFSET_BITS=64) the lseek operation could return EOVERFLOW for stream file bigger then 2,147,483,647 bytes. Anybody working on a 32bit system should be aware of the limitation of working in 32bit. Signed-off-by: Jonathan Rajotte Signed-off-by: Jérémie Galarneau Change-Id: Ib6c7bb3c9c402bdfbe9b447b1f8f298de6058caa --- src/common/fs-handle.c | 6 +++--- src/common/fs-handle.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/fs-handle.c b/src/common/fs-handle.c index ff081235c..46d9d7de5 100644 --- a/src/common/fs-handle.c +++ b/src/common/fs-handle.c @@ -70,7 +70,7 @@ end: LTTNG_HIDDEN int fs_handle_truncate(struct fs_handle *handle, off_t offset) { - int ret; + int ret; const int fd = fs_handle_get_fd(handle); if (fd < 0) { @@ -85,9 +85,9 @@ end: } LTTNG_HIDDEN -int fs_handle_seek(struct fs_handle *handle, off_t offset, int whence) +off_t fs_handle_seek(struct fs_handle *handle, off_t offset, int whence) { - int ret; + off_t ret; const int fd = fs_handle_get_fd(handle); if (fd < 0) { diff --git a/src/common/fs-handle.h b/src/common/fs-handle.h index e36f7a4de..1627f7cfe 100644 --- a/src/common/fs-handle.h +++ b/src/common/fs-handle.h @@ -69,6 +69,6 @@ LTTNG_HIDDEN int fs_handle_truncate(struct fs_handle *handle, off_t offset); LTTNG_HIDDEN -int fs_handle_seek(struct fs_handle *handle, off_t offset, int whence); +off_t fs_handle_seek(struct fs_handle *handle, off_t offset, int whence); #endif /* FS_HANDLE_H */ -- 2.34.1 From 41dc93d9df7c6bba1e1fb765d726700c9eafb144 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Tue, 28 Apr 2020 21:40:12 -0400 Subject: [PATCH 05/16] kerner-ctl: add RING_RING_BUFFER_GET_NEXT_SUBBUF_METADATA_CHECK MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Add a wrapper for RING_RING_BUFFER_GET_NEXT_SUBBUF_METADATA_CHECK which gets the next metadata subbuffer and returns a boolean flag indicating whether the metadata is guaranteed to be in a consistent state at the end of this sub-buffer (can be parsed). Signed-off-by: Jérémie Galarneau Change-Id: I13fbdfe51c3c4ef04581409e0fbc9837ed6d555d --- src/common/kernel-ctl/kernel-ctl.c | 7 +++++++ src/common/kernel-ctl/kernel-ctl.h | 3 +++ src/common/kernel-ctl/kernel-ioctl.h | 5 +++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/common/kernel-ctl/kernel-ctl.c b/src/common/kernel-ctl/kernel-ctl.c index caae421fc..10e281d69 100644 --- a/src/common/kernel-ctl/kernel-ctl.c +++ b/src/common/kernel-ctl/kernel-ctl.c @@ -510,6 +510,13 @@ int kernctl_buffer_clear(int fd) return LTTNG_IOCTL_CHECK(fd, RING_BUFFER_CLEAR); } +int kernctl_get_next_subbuf_metadata_check(int fd, bool *consistent) +{ + return LTTNG_IOCTL_NO_CHECK(fd, + RING_RING_BUFFER_GET_NEXT_SUBBUF_METADATA_CHECK, + consistent); +} + /* returns the version of the metadata. */ int kernctl_get_metadata_version(int fd, uint64_t *version) { diff --git a/src/common/kernel-ctl/kernel-ctl.h b/src/common/kernel-ctl/kernel-ctl.h index 60fcabd6e..49925ea6c 100644 --- a/src/common/kernel-ctl/kernel-ctl.h +++ b/src/common/kernel-ctl/kernel-ctl.h @@ -9,6 +9,8 @@ #ifndef _LTTNG_KERNEL_CTL_H #define _LTTNG_KERNEL_CTL_H +#include + #include #include #include @@ -94,6 +96,7 @@ int kernctl_buffer_flush_empty(int fd); int kernctl_buffer_clear(int fd); int kernctl_get_metadata_version(int fd, uint64_t *version); int kernctl_metadata_cache_dump(int fd); +int kernctl_get_next_subbuf_metadata_check(int fd, bool *consistent); /* index */ int kernctl_get_timestamp_begin(int fd, uint64_t *timestamp_begin); diff --git a/src/common/kernel-ctl/kernel-ioctl.h b/src/common/kernel-ctl/kernel-ioctl.h index 59d71191f..ed6555e10 100644 --- a/src/common/kernel-ctl/kernel-ioctl.h +++ b/src/common/kernel-ctl/kernel-ioctl.h @@ -53,9 +53,10 @@ * Reset the position of what has been consumed from the metadata cache to 0 * so it can be read again. */ -#define RING_BUFFER_METADATA_CACHE_DUMP _IO(0xF6, 0x10) +#define RING_BUFFER_METADATA_CACHE_DUMP _IO(0xF6, 0x10) /* Clear ring buffer content */ -#define RING_BUFFER_CLEAR _IO(0xF6, 0x11) +#define RING_BUFFER_CLEAR _IO(0xF6, 0x11) +#define RING_RING_BUFFER_GET_NEXT_SUBBUF_METADATA_CHECK _IOR(0xF6, 0x12, uint32_t) /* returns the timestamp begin of the current sub-buffer */ #define LTTNG_RING_BUFFER_GET_TIMESTAMP_BEGIN _IOR(0xF6, 0x20, uint64_t) -- 2.34.1 From fad4b619a1f7491acaf9b8fd2396c115b5bef56f Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Wed, 29 Apr 2020 00:03:43 -0400 Subject: [PATCH 06/16] consumerd: refactor: combine duplicated check_*_functions MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The check_ust_stream and check_kernel_stream functions are identical except for the call to the domain-specific call to consumer_flush_*_index. A "flush_index" callback is passed to check_stream in order to share the rest of that code. Signed-off-by: Jérémie Galarneau Change-Id: Iafdb64192322c0106a555b67f54290dadc4f0579 --- src/common/consumer/consumer-timer.c | 93 +++++----------------------- 1 file changed, 16 insertions(+), 77 deletions(-) diff --git a/src/common/consumer/consumer-timer.c b/src/common/consumer/consumer-timer.c index 003e5adef..c190d3b62 100644 --- a/src/common/consumer/consumer-timer.c +++ b/src/common/consumer/consumer-timer.c @@ -26,6 +26,7 @@ typedef int (*get_consumed_cb)(struct lttng_consumer_stream *stream, unsigned long *consumed); typedef int (*get_produced_cb)(struct lttng_consumer_stream *stream, unsigned long *produced); +typedef int (*flush_index_cb)(struct lttng_consumer_stream *stream); static struct timer_signal_data timer_signal = { .tid = 0, @@ -175,7 +176,8 @@ end: return ret; } -static int check_kernel_stream(struct lttng_consumer_stream *stream) +static int check_stream(struct lttng_consumer_stream *stream, + flush_index_cb flush_index) { int ret; @@ -214,7 +216,7 @@ static int check_kernel_stream(struct lttng_consumer_stream *stream) } break; } - ret = consumer_flush_kernel_index(stream); + ret = flush_index(stream); pthread_mutex_unlock(&stream->lock); end: return ret; @@ -259,53 +261,6 @@ end: return ret; } -static int check_ust_stream(struct lttng_consumer_stream *stream) -{ - int ret; - - assert(stream); - assert(stream->ustream); - /* - * While holding the stream mutex, try to take a snapshot, if it - * succeeds, it means that data is ready to be sent, just let the data - * thread handle that. Otherwise, if the snapshot returns EAGAIN, it - * means that there is no data to read after the flush, so we can - * safely send the empty index. - * - * Doing a trylock and checking if waiting on metadata if - * trylock fails. Bail out of the stream is indeed waiting for - * metadata to be pushed. Busy wait on trylock otherwise. - */ - for (;;) { - ret = pthread_mutex_trylock(&stream->lock); - switch (ret) { - case 0: - break; /* We have the lock. */ - case EBUSY: - pthread_mutex_lock(&stream->metadata_timer_lock); - if (stream->waiting_on_metadata) { - ret = 0; - stream->missed_metadata_flush = true; - pthread_mutex_unlock(&stream->metadata_timer_lock); - goto end; /* Bail out. */ - } - pthread_mutex_unlock(&stream->metadata_timer_lock); - /* Try again. */ - caa_cpu_relax(); - continue; - default: - ERR("Unexpected pthread_mutex_trylock error %d", ret); - ret = -1; - goto end; - } - break; - } - ret = consumer_flush_ust_index(stream); - pthread_mutex_unlock(&stream->lock); -end: - return ret; -} - /* * Execute action on a live timer */ @@ -315,8 +270,12 @@ static void live_timer(struct lttng_consumer_local_data *ctx, int ret; struct lttng_consumer_channel *channel; struct lttng_consumer_stream *stream; - struct lttng_ht *ht; struct lttng_ht_iter iter; + const struct lttng_ht *ht = consumer_data.stream_per_chan_id_ht; + const flush_index_cb flush_index = + ctx->type == LTTNG_CONSUMER_KERNEL ? + consumer_flush_kernel_index : + consumer_flush_ust_index; channel = si->si_value.sival_ptr; assert(channel); @@ -324,38 +283,18 @@ static void live_timer(struct lttng_consumer_local_data *ctx, if (channel->switch_timer_error) { goto error; } - ht = consumer_data.stream_per_chan_id_ht; DBG("Live timer for channel %" PRIu64, channel->key); rcu_read_lock(); - switch (ctx->type) { - case LTTNG_CONSUMER32_UST: - case LTTNG_CONSUMER64_UST: - cds_lfht_for_each_entry_duplicate(ht->ht, - ht->hash_fct(&channel->key, lttng_ht_seed), - ht->match_fct, &channel->key, &iter.iter, - stream, node_channel_id.node) { - ret = check_ust_stream(stream); - if (ret < 0) { - goto error_unlock; - } - } - break; - case LTTNG_CONSUMER_KERNEL: - cds_lfht_for_each_entry_duplicate(ht->ht, - ht->hash_fct(&channel->key, lttng_ht_seed), - ht->match_fct, &channel->key, &iter.iter, - stream, node_channel_id.node) { - ret = check_kernel_stream(stream); - if (ret < 0) { - goto error_unlock; - } + cds_lfht_for_each_entry_duplicate(ht->ht, + ht->hash_fct(&channel->key, lttng_ht_seed), + ht->match_fct, &channel->key, &iter.iter, + stream, node_channel_id.node) { + ret = check_stream(stream, flush_index); + if (ret < 0) { + goto error_unlock; } - break; - case LTTNG_CONSUMER_UNKNOWN: - assert(0); - break; } error_unlock: -- 2.34.1 From 128708c34ee7d054755d110df12940155c2dd781 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 4 May 2020 18:21:48 -0400 Subject: [PATCH 07/16] consumerd: move address computation from on_read_subbuffer_mmap MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The computation of the subbuffer's address is moved outside of lttng_consumer_on_read_subbuffer_mmap to make it usable with a regular buffer. This facilitates an upcoming change. Moreover this has the benefit of isolating domain-specific logic from this function which is supposed to be domain-agnostic. Signed-off-by: Jérémie Galarneau Change-Id: I16f8ccaa73804f98fa03e69136548e6d6b7782e5 --- src/common/consumer/consumer.c | 38 +--------- src/common/consumer/consumer.h | 4 +- src/common/kernel-consumer/kernel-consumer.c | 44 ++++++++++- src/common/ust-consumer/ust-consumer.c | 80 +++++++++++++------- src/common/ust-consumer/ust-consumer.h | 3 - 5 files changed, 99 insertions(+), 70 deletions(-) diff --git a/src/common/consumer/consumer.c b/src/common/consumer/consumer.c index c043353b9..64057c785 100644 --- a/src/common/consumer/consumer.c +++ b/src/common/consumer/consumer.c @@ -1670,12 +1670,12 @@ end: */ ssize_t lttng_consumer_on_read_subbuffer_mmap( struct lttng_consumer_local_data *ctx, - struct lttng_consumer_stream *stream, unsigned long len, + struct lttng_consumer_stream *stream, + const char *buffer, + unsigned long len, unsigned long padding, struct ctf_packet_index *index) { - unsigned long mmap_offset; - void *mmap_base; ssize_t ret = 0; off_t orig_offset = stream->out_fd_offset; /* Default is on the disk */ @@ -1697,36 +1697,6 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( } } - /* get the offset inside the fd to mmap */ - switch (consumer_data.type) { - case LTTNG_CONSUMER_KERNEL: - mmap_base = stream->mmap_base; - ret = kernctl_get_mmap_read_offset(stream->wait_fd, &mmap_offset); - if (ret < 0) { - PERROR("tracer ctl get_mmap_read_offset"); - goto end; - } - break; - case LTTNG_CONSUMER32_UST: - case LTTNG_CONSUMER64_UST: - mmap_base = lttng_ustctl_get_mmap_base(stream); - if (!mmap_base) { - ERR("read mmap get mmap base for stream %s", stream->name); - ret = -EPERM; - goto end; - } - ret = lttng_ustctl_get_mmap_read_offset(stream, &mmap_offset); - if (ret != 0) { - PERROR("tracer ctl get_mmap_read_offset"); - ret = -EINVAL; - goto end; - } - break; - default: - ERR("Unknown consumer_data type"); - assert(0); - } - /* Handle stream on the relayd if the output is on the network */ if (relayd) { unsigned long netlen = len; @@ -1803,7 +1773,7 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( * This call guarantee that len or less is returned. It's impossible to * receive a ret value that is bigger than len. */ - ret = lttng_write(outfd, mmap_base + mmap_offset, len); + ret = lttng_write(outfd, buffer, len); DBG("Consumer mmap write() ret %zd (len %lu)", ret, len); if (ret < 0 || ((size_t) ret != len)) { /* diff --git a/src/common/consumer/consumer.h b/src/common/consumer/consumer.h index 23d4faf02..3ff9dbf99 100644 --- a/src/common/consumer/consumer.h +++ b/src/common/consumer/consumer.h @@ -791,7 +791,9 @@ struct lttng_consumer_local_data *lttng_consumer_create( void lttng_consumer_destroy(struct lttng_consumer_local_data *ctx); ssize_t lttng_consumer_on_read_subbuffer_mmap( struct lttng_consumer_local_data *ctx, - struct lttng_consumer_stream *stream, unsigned long len, + struct lttng_consumer_stream *stream, + const char *buffer, + unsigned long len, unsigned long padding, struct ctf_packet_index *index); ssize_t lttng_consumer_on_read_subbuffer_splice( diff --git a/src/common/kernel-consumer/kernel-consumer.c b/src/common/kernel-consumer/kernel-consumer.c index 5075e0592..275c963df 100644 --- a/src/common/kernel-consumer/kernel-consumer.c +++ b/src/common/kernel-consumer/kernel-consumer.c @@ -7,6 +7,7 @@ * */ +#include #define _LGPL_SOURCE #include #include @@ -113,6 +114,25 @@ int lttng_kconsumer_get_consumed_snapshot(struct lttng_consumer_stream *stream, return ret; } +static +int get_current_subbuf_addr(struct lttng_consumer_stream *stream, + const char **addr) +{ + int ret; + unsigned long mmap_offset; + const char *mmap_base = stream->mmap_base; + + ret = kernctl_get_mmap_read_offset(stream->wait_fd, &mmap_offset); + if (ret < 0) { + PERROR("Failed to get mmap read offset"); + goto error; + } + + *addr = mmap_base + mmap_offset; +error: + return ret; +} + /* * Take a snapshot of all the stream of a channel * RCU read-side lock must be held across this function to ensure existence of @@ -228,6 +248,7 @@ static int lttng_kconsumer_snapshot_channel( while ((long) (consumed_pos - produced_pos) < 0) { ssize_t read_len; unsigned long len, padded_len; + const char *subbuf_addr; health_code_update(); @@ -257,7 +278,13 @@ static int lttng_kconsumer_snapshot_channel( goto error_put_subbuf; } - read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, stream, len, + ret = get_current_subbuf_addr(stream, &subbuf_addr); + if (ret) { + goto error_put_subbuf; + } + + read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, + stream, subbuf_addr, len, padded_len - len, NULL); /* * We write the padded len in local tracefiles but the data len @@ -1691,6 +1718,9 @@ ssize_t lttng_kconsumer_read_subbuffer(struct lttng_consumer_stream *stream, } break; case CONSUMER_CHANNEL_MMAP: + { + const char *subbuf_addr; + /* Get subbuffer size without padding */ err = kernctl_get_subbuf_size(infd, &subbuf_size); if (err != 0) { @@ -1710,13 +1740,20 @@ ssize_t lttng_kconsumer_read_subbuffer(struct lttng_consumer_stream *stream, goto error; } + ret = get_current_subbuf_addr(stream, &subbuf_addr); + if (ret) { + goto error_put_subbuf; + } + /* Make sure the tracer is not gone mad on us! */ assert(len >= subbuf_size); padding = len - subbuf_size; /* write the subbuffer to the tracefile */ - ret = lttng_consumer_on_read_subbuffer_mmap(ctx, stream, subbuf_size, + ret = lttng_consumer_on_read_subbuffer_mmap(ctx, stream, + subbuf_addr, + subbuf_size, padding, &index); /* * The mmap operation should write subbuf_size amount of data when @@ -1736,11 +1773,12 @@ ssize_t lttng_kconsumer_read_subbuffer(struct lttng_consumer_stream *stream, write_index = 0; } break; + } default: ERR("Unknown output method"); ret = -EPERM; } - +error_put_subbuf: err = kernctl_put_next_subbuf(infd); if (err != 0) { if (err == -EFAULT) { diff --git a/src/common/ust-consumer/ust-consumer.c b/src/common/ust-consumer/ust-consumer.c index 77a9670e7..9055d0cf8 100644 --- a/src/common/ust-consumer/ust-consumer.c +++ b/src/common/ust-consumer/ust-consumer.c @@ -7,6 +7,7 @@ * */ +#include #define _LGPL_SOURCE #include #include @@ -1078,6 +1079,35 @@ error: return ret; } +static +int get_current_subbuf_addr(struct lttng_consumer_stream *stream, + const char **addr) +{ + int ret; + unsigned long mmap_offset; + const char *mmap_base; + + mmap_base = ustctl_get_mmap_base(stream->ustream); + if (!mmap_base) { + ERR("Failed to get mmap base for stream `%s`", + stream->name); + ret = -EPERM; + goto error; + } + + ret = ustctl_get_mmap_read_offset(stream->ustream, &mmap_offset); + if (ret != 0) { + ERR("Failed to get mmap offset for stream `%s`", stream->name); + ret = -EINVAL; + goto error; + } + + *addr = mmap_base + mmap_offset; +error: + return ret; + +} + /* * Take a snapshot of all the stream of a channel. * RCU read-side lock and the channel lock must be held by the caller. @@ -1180,6 +1210,7 @@ static int snapshot_channel(struct lttng_consumer_channel *channel, while ((long) (consumed_pos - produced_pos) < 0) { ssize_t read_len; unsigned long len, padded_len; + const char *subbuf_addr; health_code_update(); @@ -1209,7 +1240,13 @@ static int snapshot_channel(struct lttng_consumer_channel *channel, goto error_put_subbuf; } - read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, stream, len, + ret = get_current_subbuf_addr(stream, &subbuf_addr); + if (ret) { + goto error_put_subbuf; + } + + read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, + stream, subbuf_addr, len, padded_len - len, NULL); if (use_relayd) { if (read_len != len) { @@ -2205,31 +2242,6 @@ end: return ret; } -/* - * Wrapper over the mmap() read offset from ust-ctl library. Since this can be - * compiled out, we isolate it in this library. - */ -int lttng_ustctl_get_mmap_read_offset(struct lttng_consumer_stream *stream, - unsigned long *off) -{ - assert(stream); - assert(stream->ustream); - - return ustctl_get_mmap_read_offset(stream->ustream, off); -} - -/* - * Wrapper over the mmap() read offset from ust-ctl library. Since this can be - * compiled out, we isolate it in this library. - */ -void *lttng_ustctl_get_mmap_base(struct lttng_consumer_stream *stream) -{ - assert(stream); - assert(stream->ustream); - - return ustctl_get_mmap_base(stream->ustream); -} - void lttng_ustctl_flush_buffer(struct lttng_consumer_stream *stream, int producer_active) { @@ -2795,6 +2807,7 @@ int lttng_ustconsumer_read_subbuffer(struct lttng_consumer_stream *stream, long ret = 0; struct ustctl_consumer_stream *ustream; struct ctf_packet_index index; + const char *subbuf_addr; assert(stream); assert(stream->ustream); @@ -2904,11 +2917,19 @@ retry: padding = len - subbuf_size; + ret = get_current_subbuf_addr(stream, &subbuf_addr); + if (ret) { + write_index = 0; + goto error_put_subbuf; + } + /* write the subbuffer to the tracefile */ - ret = lttng_consumer_on_read_subbuffer_mmap(ctx, stream, subbuf_size, padding, &index); + ret = lttng_consumer_on_read_subbuffer_mmap( + ctx, stream, subbuf_addr, subbuf_size, padding, &index); /* - * The mmap operation should write subbuf_size amount of data when network - * streaming or the full padding (len) size when we are _not_ streaming. + * The mmap operation should write subbuf_size amount of data when + * network streaming or the full padding (len) size when we are _not_ + * streaming. */ if ((ret != subbuf_size && stream->net_seq_idx != (uint64_t) -1ULL) || (ret != len && stream->net_seq_idx == (uint64_t) -1ULL)) { @@ -2925,6 +2946,7 @@ retry: ret, len, subbuf_size); write_index = 0; } +error_put_subbuf: err = ustctl_put_next_subbuf(ustream); assert(err == 0); diff --git a/src/common/ust-consumer/ust-consumer.h b/src/common/ust-consumer/ust-consumer.h index 406846896..b0a1df7f3 100644 --- a/src/common/ust-consumer/ust-consumer.h +++ b/src/common/ust-consumer/ust-consumer.h @@ -40,9 +40,6 @@ int lttng_ustconsumer_on_recv_stream(struct lttng_consumer_stream *stream); void lttng_ustconsumer_on_stream_hangup(struct lttng_consumer_stream *stream); -int lttng_ustctl_get_mmap_read_offset(struct lttng_consumer_stream *stream, - unsigned long *off); -void *lttng_ustctl_get_mmap_base(struct lttng_consumer_stream *stream); void lttng_ustctl_flush_buffer(struct lttng_consumer_stream *stream, int producer_active); int lttng_ustconsumer_get_stream_id(struct lttng_consumer_stream *stream, -- 2.34.1 From fd424d99bfddf3581bc55fdf14799a50661beddd Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 4 May 2020 19:04:02 -0400 Subject: [PATCH 08/16] consumerd: cleanup: use buffer view interface for mmap read subbuf MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Replace explicit pointer + size parameters by an lttng_buffer_view in lttng_consumer_on_read_subbuffer_mmap(). Signed-off-by: Jérémie Galarneau Change-Id: I76f35b3e295c596cdf4bbb8a6d01168a850a721a --- src/common/consumer/consumer.c | 32 +++++++++++--------- src/common/consumer/consumer.h | 4 +-- src/common/kernel-consumer/kernel-consumer.c | 22 ++++++++------ src/common/ust-consumer/ust-consumer.c | 12 ++++++-- 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/common/consumer/consumer.c b/src/common/consumer/consumer.c index 64057c785..d70a5d1d4 100644 --- a/src/common/consumer/consumer.c +++ b/src/common/consumer/consumer.c @@ -1671,8 +1671,7 @@ end: ssize_t lttng_consumer_on_read_subbuffer_mmap( struct lttng_consumer_local_data *ctx, struct lttng_consumer_stream *stream, - const char *buffer, - unsigned long len, + const struct lttng_buffer_view *buffer, unsigned long padding, struct ctf_packet_index *index) { @@ -1682,6 +1681,8 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( int outfd = stream->out_fd; struct consumer_relayd_sock_pair *relayd = NULL; unsigned int relayd_hang_up = 0; + const size_t subbuf_content_size = buffer->size - padding; + size_t write_len; /* RCU lock for the relayd pointer */ rcu_read_lock(); @@ -1699,7 +1700,7 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( /* Handle stream on the relayd if the output is on the network */ if (relayd) { - unsigned long netlen = len; + unsigned long netlen = subbuf_content_size; /* * Lock the control socket for the complete duration of the function @@ -1737,10 +1738,10 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( goto write_error; } } - } else { - /* No streaming, we have to set the len with the full padding */ - len += padding; + write_len = subbuf_content_size; + } else { + /* No streaming; we have to write the full padding. */ if (stream->metadata_flag && stream->reset_metadata_flag) { ret = utils_truncate_stream_file(stream->out_fd, 0); if (ret < 0) { @@ -1754,7 +1755,7 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( * Check if we need to change the tracefile before writing the packet. */ if (stream->chan->tracefile_size > 0 && - (stream->tracefile_size_current + len) > + (stream->tracefile_size_current + buffer->size) > stream->chan->tracefile_size) { ret = consumer_stream_rotate_output_files(stream); if (ret) { @@ -1763,19 +1764,21 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( outfd = stream->out_fd; orig_offset = 0; } - stream->tracefile_size_current += len; + stream->tracefile_size_current += buffer->size; if (index) { index->offset = htobe64(stream->out_fd_offset); } + + write_len = buffer->size; } /* * This call guarantee that len or less is returned. It's impossible to * receive a ret value that is bigger than len. */ - ret = lttng_write(outfd, buffer, len); - DBG("Consumer mmap write() ret %zd (len %lu)", ret, len); - if (ret < 0 || ((size_t) ret != len)) { + ret = lttng_write(outfd, buffer->data, write_len); + DBG("Consumer mmap write() ret %zd (len %lu)", ret, write_len); + if (ret < 0 || ((size_t) ret != write_len)) { /* * Report error to caller if nothing was written else at least send the * amount written. @@ -1796,7 +1799,8 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( DBG("Consumer mmap write detected relayd hang up"); } else { /* Unhandled error, print it and stop function right now. */ - PERROR("Error in write mmap (ret %zd != len %lu)", ret, len); + PERROR("Error in write mmap (ret %zd != write_len %zu)", ret, + write_len); } goto write_error; } @@ -1805,9 +1809,9 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( /* This call is useless on a socket so better save a syscall. */ if (!relayd) { /* This won't block, but will start writeout asynchronously */ - lttng_sync_file_range(outfd, stream->out_fd_offset, len, + lttng_sync_file_range(outfd, stream->out_fd_offset, write_len, SYNC_FILE_RANGE_WRITE); - stream->out_fd_offset += len; + stream->out_fd_offset += write_len; lttng_consumer_sync_trace_file(stream, orig_offset); } diff --git a/src/common/consumer/consumer.h b/src/common/consumer/consumer.h index 3ff9dbf99..b7593d001 100644 --- a/src/common/consumer/consumer.h +++ b/src/common/consumer/consumer.h @@ -26,6 +26,7 @@ #include #include #include +#include /* Commands for consumer */ enum lttng_consumer_command { @@ -792,8 +793,7 @@ void lttng_consumer_destroy(struct lttng_consumer_local_data *ctx); ssize_t lttng_consumer_on_read_subbuffer_mmap( struct lttng_consumer_local_data *ctx, struct lttng_consumer_stream *stream, - const char *buffer, - unsigned long len, + const struct lttng_buffer_view *buffer, unsigned long padding, struct ctf_packet_index *index); ssize_t lttng_consumer_on_read_subbuffer_splice( diff --git a/src/common/kernel-consumer/kernel-consumer.c b/src/common/kernel-consumer/kernel-consumer.c index 275c963df..54314ec75 100644 --- a/src/common/kernel-consumer/kernel-consumer.c +++ b/src/common/kernel-consumer/kernel-consumer.c @@ -7,6 +7,7 @@ * */ +#include "common/buffer-view.h" #include #define _LGPL_SOURCE #include @@ -249,9 +250,9 @@ static int lttng_kconsumer_snapshot_channel( ssize_t read_len; unsigned long len, padded_len; const char *subbuf_addr; + struct lttng_buffer_view subbuf_view; health_code_update(); - DBG("Kernel consumer taking snapshot at pos %lu", consumed_pos); ret = kernctl_get_subbuf(stream->wait_fd, &consumed_pos); @@ -283,8 +284,10 @@ static int lttng_kconsumer_snapshot_channel( goto error_put_subbuf; } + subbuf_view = lttng_buffer_view_init( + subbuf_addr, 0, padded_len); read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, - stream, subbuf_addr, len, + stream, &subbuf_view, padded_len - len, NULL); /* * We write the padded len in local tracefiles but the data len @@ -1720,6 +1723,7 @@ ssize_t lttng_kconsumer_read_subbuffer(struct lttng_consumer_stream *stream, case CONSUMER_CHANNEL_MMAP: { const char *subbuf_addr; + struct lttng_buffer_view subbuf_view; /* Get subbuffer size without padding */ err = kernctl_get_subbuf_size(infd, &subbuf_size); @@ -1750,15 +1754,15 @@ ssize_t lttng_kconsumer_read_subbuffer(struct lttng_consumer_stream *stream, padding = len - subbuf_size; + subbuf_view = lttng_buffer_view_init(subbuf_addr, 0, len); + /* write the subbuffer to the tracefile */ - ret = lttng_consumer_on_read_subbuffer_mmap(ctx, stream, - subbuf_addr, - subbuf_size, - padding, &index); + ret = lttng_consumer_on_read_subbuffer_mmap( + ctx, stream, &subbuf_view, padding, &index); /* - * The mmap operation should write subbuf_size amount of data when - * network streaming or the full padding (len) size when we are _not_ - * streaming. + * The mmap operation should write subbuf_size amount of data + * when network streaming or the full padding (len) size when we + * are _not_ streaming. */ if ((ret != subbuf_size && stream->net_seq_idx != (uint64_t) -1ULL) || (ret != len && stream->net_seq_idx == (uint64_t) -1ULL)) { diff --git a/src/common/ust-consumer/ust-consumer.c b/src/common/ust-consumer/ust-consumer.c index 9055d0cf8..a6d8463a8 100644 --- a/src/common/ust-consumer/ust-consumer.c +++ b/src/common/ust-consumer/ust-consumer.c @@ -1211,6 +1211,7 @@ static int snapshot_channel(struct lttng_consumer_channel *channel, ssize_t read_len; unsigned long len, padded_len; const char *subbuf_addr; + struct lttng_buffer_view subbuf_view; health_code_update(); @@ -1245,9 +1246,11 @@ static int snapshot_channel(struct lttng_consumer_channel *channel, goto error_put_subbuf; } + subbuf_view = lttng_buffer_view_init( + subbuf_addr, 0, padded_len); read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, - stream, subbuf_addr, len, - padded_len - len, NULL); + stream, &subbuf_view, padded_len - len, + NULL); if (use_relayd) { if (read_len != len) { ret = -EPERM; @@ -2808,6 +2811,7 @@ int lttng_ustconsumer_read_subbuffer(struct lttng_consumer_stream *stream, struct ustctl_consumer_stream *ustream; struct ctf_packet_index index; const char *subbuf_addr; + struct lttng_buffer_view subbuf_view; assert(stream); assert(stream->ustream); @@ -2923,9 +2927,11 @@ retry: goto error_put_subbuf; } + subbuf_view = lttng_buffer_view_init(subbuf_addr, 0, len); + /* write the subbuffer to the tracefile */ ret = lttng_consumer_on_read_subbuffer_mmap( - ctx, stream, subbuf_addr, subbuf_size, padding, &index); + ctx, stream, &subbuf_view, padding, &index); /* * The mmap operation should write subbuf_size amount of data when * network streaming or the full padding (len) size when we are _not_ -- 2.34.1 From 49f45573c5077c2e1766819bf13518ac560a3ee0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Tue, 5 May 2020 13:13:03 -0400 Subject: [PATCH 09/16] consumerd: pass channel instance to stream creation function MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Both callsites of consumer_allocate_stream() set the stream's "chan" pointer after the creation. Pass the channel directly to the stream creation function so it can initialize the stream according to the channel's settings. Signed-off-by: Jérémie Galarneau Change-Id: Icea7088e7695e310585bf398e14e6443d67a30bb --- src/common/consumer/consumer.c | 5 ++++- src/common/consumer/consumer.h | 4 +++- src/common/kernel-consumer/kernel-consumer.c | 5 +++-- src/common/ust-consumer/ust-consumer.c | 5 +++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/common/consumer/consumer.c b/src/common/consumer/consumer.c index d70a5d1d4..4101925c6 100644 --- a/src/common/consumer/consumer.c +++ b/src/common/consumer/consumer.c @@ -568,7 +568,9 @@ void consumer_stream_update_channel_attributes( channel->tracefile_size; } -struct lttng_consumer_stream *consumer_allocate_stream(uint64_t channel_key, +struct lttng_consumer_stream *consumer_allocate_stream( + struct lttng_consumer_channel *channel, + uint64_t channel_key, uint64_t stream_key, const char *channel_name, uint64_t relayd_id, @@ -596,6 +598,7 @@ struct lttng_consumer_stream *consumer_allocate_stream(uint64_t channel_key, } rcu_read_lock(); + stream->chan = channel; stream->key = stream_key; stream->trace_chunk = trace_chunk; stream->out_fd = -1; diff --git a/src/common/consumer/consumer.h b/src/common/consumer/consumer.h index b7593d001..077e959d7 100644 --- a/src/common/consumer/consumer.h +++ b/src/common/consumer/consumer.h @@ -740,7 +740,9 @@ void consumer_stream_update_channel_attributes( struct lttng_consumer_stream *stream, struct lttng_consumer_channel *channel); -struct lttng_consumer_stream *consumer_allocate_stream(uint64_t channel_key, +struct lttng_consumer_stream *consumer_allocate_stream( + struct lttng_consumer_channel *channel, + uint64_t channel_key, uint64_t stream_key, const char *channel_name, uint64_t relayd_id, diff --git a/src/common/kernel-consumer/kernel-consumer.c b/src/common/kernel-consumer/kernel-consumer.c index 54314ec75..5aec4ecf8 100644 --- a/src/common/kernel-consumer/kernel-consumer.c +++ b/src/common/kernel-consumer/kernel-consumer.c @@ -654,7 +654,9 @@ int lttng_kconsumer_recv_cmd(struct lttng_consumer_local_data *ctx, health_code_update(); pthread_mutex_lock(&channel->lock); - new_stream = consumer_allocate_stream(channel->key, + new_stream = consumer_allocate_stream( + channel, + channel->key, fd, channel->name, channel->relayd_id, @@ -676,7 +678,6 @@ int lttng_kconsumer_recv_cmd(struct lttng_consumer_local_data *ctx, goto error_add_stream_nosignal; } - new_stream->chan = channel; new_stream->wait_fd = fd; ret = kernctl_get_max_subbuf_size(new_stream->wait_fd, &new_stream->max_sb_size); diff --git a/src/common/ust-consumer/ust-consumer.c b/src/common/ust-consumer/ust-consumer.c index a6d8463a8..b46a50502 100644 --- a/src/common/ust-consumer/ust-consumer.c +++ b/src/common/ust-consumer/ust-consumer.c @@ -147,7 +147,9 @@ static struct lttng_consumer_stream *allocate_stream(int cpu, int key, assert(channel); assert(ctx); - stream = consumer_allocate_stream(channel->key, + stream = consumer_allocate_stream( + channel, + channel->key, key, channel->name, channel->relayd_id, @@ -176,7 +178,6 @@ static struct lttng_consumer_stream *allocate_stream(int cpu, int key, } consumer_stream_update_channel_attributes(stream, channel); - stream->chan = channel; error: if (_alloc_ret) { -- 2.34.1 From a2814ea7573bf5edd5323d6f89c48ff14105db69 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Tue, 5 May 2020 15:48:05 -0400 Subject: [PATCH 10/16] consumerd: tag metadata channel as being part of a live session MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit metadata channels that are part of a live session must be handled differently than when they are part of non-live sessions since complete "metadata units" must be accumulated before they are forwarded to a relay daemon. This allows a follow-up fix to use this information since the live_timer_interval of a metadata channel is always 0. Signed-off-by: Jérémie Galarneau Change-Id: I53db4bc717b149ed20e0309531db6f0241e873e1 --- src/bin/lttng-sessiond/client.c | 1 + src/bin/lttng-sessiond/consumer.c | 4 +++ src/bin/lttng-sessiond/consumer.h | 2 ++ src/bin/lttng-sessiond/kernel-consumer.c | 19 ++++++-------- src/bin/lttng-sessiond/trace-kernel.h | 1 + src/bin/lttng-sessiond/ust-consumer.c | 1 + src/common/consumer/consumer.c | 2 ++ src/common/consumer/consumer.h | 3 +++ src/common/kernel-consumer/kernel-consumer.c | 1 + src/common/sessiond-comm/sessiond-comm.h | 3 +++ src/common/ust-consumer/ust-consumer.c | 26 +++----------------- 11 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/bin/lttng-sessiond/client.c b/src/bin/lttng-sessiond/client.c index a36b81c18..9f9afa25a 100644 --- a/src/bin/lttng-sessiond/client.c +++ b/src/bin/lttng-sessiond/client.c @@ -495,6 +495,7 @@ static int create_kernel_session(struct ltt_session *session) session->kernel_session->gid = session->gid; session->kernel_session->output_traces = session->output_traces; session->kernel_session->snapshot_mode = session->snapshot_mode; + session->kernel_session->is_live_session = session->live_timer != 0; return LTTNG_OK; diff --git a/src/bin/lttng-sessiond/consumer.c b/src/bin/lttng-sessiond/consumer.c index dc945020b..d282f59c9 100644 --- a/src/bin/lttng-sessiond/consumer.c +++ b/src/bin/lttng-sessiond/consumer.c @@ -913,6 +913,7 @@ void consumer_init_ask_channel_comm_msg(struct lttcomm_consumer_msg *msg, unsigned int switch_timer_interval, unsigned int read_timer_interval, unsigned int live_timer_interval, + bool is_in_live_session, unsigned int monitor_timer_interval, int output, int type, @@ -959,6 +960,7 @@ void consumer_init_ask_channel_comm_msg(struct lttcomm_consumer_msg *msg, msg->u.ask_channel.switch_timer_interval = switch_timer_interval; msg->u.ask_channel.read_timer_interval = read_timer_interval; msg->u.ask_channel.live_timer_interval = live_timer_interval; + msg->u.ask_channel.is_live = is_in_live_session; msg->u.ask_channel.monitor_timer_interval = monitor_timer_interval; msg->u.ask_channel.output = output; msg->u.ask_channel.type = type; @@ -1014,6 +1016,7 @@ void consumer_init_add_channel_comm_msg(struct lttcomm_consumer_msg *msg, uint64_t tracefile_count, unsigned int monitor, unsigned int live_timer_interval, + bool is_in_live_session, unsigned int monitor_timer_interval, struct lttng_trace_chunk *trace_chunk) { @@ -1043,6 +1046,7 @@ void consumer_init_add_channel_comm_msg(struct lttcomm_consumer_msg *msg, msg->u.channel.tracefile_count = tracefile_count; msg->u.channel.monitor = monitor; msg->u.channel.live_timer_interval = live_timer_interval; + msg->u.channel.is_live = is_in_live_session; msg->u.channel.monitor_timer_interval = monitor_timer_interval; strncpy(msg->u.channel.pathname, pathname, diff --git a/src/bin/lttng-sessiond/consumer.h b/src/bin/lttng-sessiond/consumer.h index 926697e1e..54c962a86 100644 --- a/src/bin/lttng-sessiond/consumer.h +++ b/src/bin/lttng-sessiond/consumer.h @@ -233,6 +233,7 @@ void consumer_init_ask_channel_comm_msg(struct lttcomm_consumer_msg *msg, unsigned int switch_timer_interval, unsigned int read_timer_interval, unsigned int live_timer_interval, + bool is_in_live_session, unsigned int monitor_timer_interval, int output, int type, @@ -275,6 +276,7 @@ void consumer_init_add_channel_comm_msg(struct lttcomm_consumer_msg *msg, uint64_t tracefile_count, unsigned int monitor, unsigned int live_timer_interval, + bool is_in_live_session, unsigned int monitor_timer_interval, struct lttng_trace_chunk *trace_chunk); int consumer_is_data_pending(uint64_t session_id, diff --git a/src/bin/lttng-sessiond/kernel-consumer.c b/src/bin/lttng-sessiond/kernel-consumer.c index 0fff2c1a1..9622d3058 100644 --- a/src/bin/lttng-sessiond/kernel-consumer.c +++ b/src/bin/lttng-sessiond/kernel-consumer.c @@ -156,6 +156,7 @@ int kernel_consumer_add_channel(struct consumer_socket *sock, channel->channel->attr.tracefile_count, monitor, channel->channel->attr.live_timer_interval, + ksession->is_live_session, channel_attr_extended->monitor_timer_interval, ksession->current_trace_chunk); @@ -221,19 +222,13 @@ int kernel_consumer_add_metadata(struct consumer_socket *sock, consumer = ksession->consumer; /* Prep channel message structure */ - consumer_init_add_channel_comm_msg(&lkm, - ksession->metadata->key, - ksession->id, - "", - ksession->uid, - ksession->gid, - consumer->net_seq_index, - DEFAULT_METADATA_NAME, - 1, + consumer_init_add_channel_comm_msg(&lkm, ksession->metadata->key, + ksession->id, "", ksession->uid, ksession->gid, + consumer->net_seq_index, DEFAULT_METADATA_NAME, 1, DEFAULT_KERNEL_CHANNEL_OUTPUT, - CONSUMER_CHANNEL_TYPE_METADATA, - 0, 0, - monitor, 0, 0, ksession->current_trace_chunk); + CONSUMER_CHANNEL_TYPE_METADATA, 0, 0, monitor, 0, + ksession->is_live_session, 0, + ksession->current_trace_chunk); health_code_update(); diff --git a/src/bin/lttng-sessiond/trace-kernel.h b/src/bin/lttng-sessiond/trace-kernel.h index 61dd8eae9..dd6f21edf 100644 --- a/src/bin/lttng-sessiond/trace-kernel.h +++ b/src/bin/lttng-sessiond/trace-kernel.h @@ -111,6 +111,7 @@ struct ltt_kernel_session { unsigned int output_traces; unsigned int snapshot_mode; unsigned int has_non_default_channel; + bool is_live_session; /* Current trace chunk of the ltt_session. */ struct lttng_trace_chunk *current_trace_chunk; /* Tracker lists */ diff --git a/src/bin/lttng-sessiond/ust-consumer.c b/src/bin/lttng-sessiond/ust-consumer.c index e5ba996cc..25dc7ed1c 100644 --- a/src/bin/lttng-sessiond/ust-consumer.c +++ b/src/bin/lttng-sessiond/ust-consumer.c @@ -136,6 +136,7 @@ static int ask_channel_creation(struct ust_app_session *ua_sess, ua_chan->attr.switch_timer_interval, ua_chan->attr.read_timer_interval, ua_sess->live_timer_interval, + ua_sess->live_timer_interval != 0, ua_chan->monitor_timer_interval, output, (int) ua_chan->attr.type, diff --git a/src/common/consumer/consumer.c b/src/common/consumer/consumer.c index 4101925c6..e2e7438f6 100644 --- a/src/common/consumer/consumer.c +++ b/src/common/consumer/consumer.c @@ -1066,6 +1066,7 @@ struct lttng_consumer_channel *consumer_allocate_channel(uint64_t key, uint64_t session_id_per_pid, unsigned int monitor, unsigned int live_timer_interval, + bool is_in_live_session, const char *root_shm_path, const char *shm_path) { @@ -1097,6 +1098,7 @@ struct lttng_consumer_channel *consumer_allocate_channel(uint64_t key, channel->tracefile_count = tracefile_count; channel->monitor = monitor; channel->live_timer_interval = live_timer_interval; + channel->is_live = is_in_live_session; pthread_mutex_init(&channel->lock, NULL); pthread_mutex_init(&channel->timer_lock, NULL); diff --git a/src/common/consumer/consumer.h b/src/common/consumer/consumer.h index 077e959d7..000040982 100644 --- a/src/common/consumer/consumer.h +++ b/src/common/consumer/consumer.h @@ -183,6 +183,8 @@ struct lttng_consumer_channel { int live_timer_enabled; timer_t live_timer; int live_timer_error; + /* Channel is part of a live session ? */ + bool is_live; /* For channel monitoring timer. */ int monitor_timer_enabled; @@ -764,6 +766,7 @@ struct lttng_consumer_channel *consumer_allocate_channel(uint64_t key, uint64_t session_id_per_pid, unsigned int monitor, unsigned int live_timer_interval, + bool is_in_live_session, const char *root_shm_path, const char *shm_path); void consumer_del_stream(struct lttng_consumer_stream *stream, diff --git a/src/common/kernel-consumer/kernel-consumer.c b/src/common/kernel-consumer/kernel-consumer.c index 5aec4ecf8..7032a7f7f 100644 --- a/src/common/kernel-consumer/kernel-consumer.c +++ b/src/common/kernel-consumer/kernel-consumer.c @@ -513,6 +513,7 @@ int lttng_kconsumer_recv_cmd(struct lttng_consumer_local_data *ctx, msg.u.channel.tracefile_count, 0, msg.u.channel.monitor, msg.u.channel.live_timer_interval, + msg.u.channel.is_live, NULL, NULL); if (new_channel == NULL) { lttng_consumer_send_error(ctx, LTTCOMM_CONSUMERD_OUTFD_ERROR); diff --git a/src/common/sessiond-comm/sessiond-comm.h b/src/common/sessiond-comm/sessiond-comm.h index fd1ceae11..0b4c05fb7 100644 --- a/src/common/sessiond-comm/sessiond-comm.h +++ b/src/common/sessiond-comm/sessiond-comm.h @@ -534,6 +534,8 @@ struct lttcomm_consumer_msg { uint32_t monitor; /* timer to check the streams usage in live mode (usec). */ unsigned int live_timer_interval; + /* is part of a live session */ + uint8_t is_live; /* timer to sample a channel's positions (usec). */ unsigned int monitor_timer_interval; } LTTNG_PACKED channel; /* Only used by Kernel. */ @@ -567,6 +569,7 @@ struct lttcomm_consumer_msg { uint32_t switch_timer_interval; /* usec */ uint32_t read_timer_interval; /* usec */ unsigned int live_timer_interval; /* usec */ + uint8_t is_live; /* is part of a live session */ uint32_t monitor_timer_interval; /* usec */ int32_t output; /* splice, mmap */ int32_t type; /* metadata or per_cpu */ diff --git a/src/common/ust-consumer/ust-consumer.c b/src/common/ust-consumer/ust-consumer.c index b46a50502..e5143cd4a 100644 --- a/src/common/ust-consumer/ust-consumer.c +++ b/src/common/ust-consumer/ust-consumer.c @@ -111,26 +111,6 @@ error: return ret; } -/* - * Allocate and return a consumer channel object. - */ -static struct lttng_consumer_channel *allocate_channel(uint64_t session_id, - const uint64_t *chunk_id, const char *pathname, const char *name, - uint64_t relayd_id, uint64_t key, enum lttng_event_output output, - uint64_t tracefile_size, uint64_t tracefile_count, - uint64_t session_id_per_pid, unsigned int monitor, - unsigned int live_timer_interval, - const char *root_shm_path, const char *shm_path) -{ - assert(pathname); - assert(name); - - return consumer_allocate_channel(key, session_id, chunk_id, pathname, - name, relayd_id, output, tracefile_size, - tracefile_count, session_id_per_pid, monitor, - live_timer_interval, root_shm_path, shm_path); -} - /* * Allocate and return a consumer stream object. If _alloc_ret is not NULL, the * error value if applicable is set in it else it is kept untouched. @@ -1480,19 +1460,21 @@ int lttng_ustconsumer_recv_cmd(struct lttng_consumer_local_data *ctx, }; /* Create a plain object and reserve a channel key. */ - channel = allocate_channel(msg.u.ask_channel.session_id, + channel = consumer_allocate_channel( + msg.u.ask_channel.key, + msg.u.ask_channel.session_id, msg.u.ask_channel.chunk_id.is_set ? &chunk_id : NULL, msg.u.ask_channel.pathname, msg.u.ask_channel.name, msg.u.ask_channel.relayd_id, - msg.u.ask_channel.key, (enum lttng_event_output) msg.u.ask_channel.output, msg.u.ask_channel.tracefile_size, msg.u.ask_channel.tracefile_count, msg.u.ask_channel.session_id_per_pid, msg.u.ask_channel.monitor, msg.u.ask_channel.live_timer_interval, + msg.u.ask_channel.is_live, msg.u.ask_channel.root_shm_path, msg.u.ask_channel.shm_path); if (!channel) { -- 2.34.1 From d42266a417afa6e8ca6024590b8282002aebca07 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Tue, 5 May 2020 18:54:32 -0400 Subject: [PATCH 11/16] sessiond: enforce mmap output type for kernel metadata channel MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit A follow-up fix causes the consumer daemon to accumulate metadata packets into a complete "unit" that can be parsed before sending it to the relay daemon. The consumer daemon will also need to extract the contents of the metadata cache when computing a rotation position (follow-up fix too). Hence, it is not possible to rely on the splice back-end as the consumer daemon may need to accumulate more content than can be backed by the ring buffer's underlying pages. In both cases, the splice output mode could still be used when combined with a memfd, but I see no tangible benefit. Moreover, it would require a 3.17 kernel. Curiously the kernel metadata channel configuration appears to be hard-coded twice; once in the ltt_kernel_session's ltt_kernel_metadata, and once again in kernel_consumer_add_metadata(). kernel_consumer_add_metadata is modified to use the kernel session's metadata configuration. Signed-off-by: Jérémie Galarneau Change-Id: Ia4cad82f595d3eee50d081851c234d4c2ef7ee5f --- src/bin/lttng-sessiond/kernel-consumer.c | 23 ++++++++++++---- src/bin/lttng-sessiond/kernel.c | 1 + src/bin/lttng-sessiond/trace-kernel.c | 35 +++++++++++++++++++++--- src/common/defaults.h | 7 +++-- tests/unit/test_kernel_data.c | 8 +++--- 5 files changed, 57 insertions(+), 17 deletions(-) diff --git a/src/bin/lttng-sessiond/kernel-consumer.c b/src/bin/lttng-sessiond/kernel-consumer.c index 9622d3058..6244963cc 100644 --- a/src/bin/lttng-sessiond/kernel-consumer.c +++ b/src/bin/lttng-sessiond/kernel-consumer.c @@ -222,12 +222,23 @@ int kernel_consumer_add_metadata(struct consumer_socket *sock, consumer = ksession->consumer; /* Prep channel message structure */ - consumer_init_add_channel_comm_msg(&lkm, ksession->metadata->key, - ksession->id, "", ksession->uid, ksession->gid, - consumer->net_seq_index, DEFAULT_METADATA_NAME, 1, - DEFAULT_KERNEL_CHANNEL_OUTPUT, - CONSUMER_CHANNEL_TYPE_METADATA, 0, 0, monitor, 0, - ksession->is_live_session, 0, + consumer_init_add_channel_comm_msg(&lkm, + ksession->metadata->key, + ksession->id, + "", + ksession->uid, + ksession->gid, + consumer->net_seq_index, + ksession->metadata->conf->name, + 1, + ksession->metadata->conf->attr.output, + CONSUMER_CHANNEL_TYPE_METADATA, + ksession->metadata->conf->attr.tracefile_size, + ksession->metadata->conf->attr.tracefile_count, + monitor, + ksession->metadata->conf->attr.live_timer_interval, + ksession->is_live_session, + 0, ksession->current_trace_chunk); health_code_update(); diff --git a/src/bin/lttng-sessiond/kernel.c b/src/bin/lttng-sessiond/kernel.c index 53efad5e7..a117575b7 100644 --- a/src/bin/lttng-sessiond/kernel.c +++ b/src/bin/lttng-sessiond/kernel.c @@ -8,6 +8,7 @@ #include "bin/lttng-sessiond/tracker.h" #include "common/tracker.h" #include "common/utils.h" +#include "lttng/event.h" #include "lttng/lttng-error.h" #include "lttng/tracker.h" #define _LGPL_SOURCE diff --git a/src/bin/lttng-sessiond/trace-kernel.c b/src/bin/lttng-sessiond/trace-kernel.c index 723ffddd3..044fc98d2 100644 --- a/src/bin/lttng-sessiond/trace-kernel.c +++ b/src/bin/lttng-sessiond/trace-kernel.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "consumer.h" #include "trace-kernel.h" @@ -485,6 +486,7 @@ error: */ struct ltt_kernel_metadata *trace_kernel_create_metadata(void) { + int ret; struct ltt_kernel_metadata *lkm; struct lttng_channel *chan; @@ -495,13 +497,38 @@ struct ltt_kernel_metadata *trace_kernel_create_metadata(void) goto error; } + ret = lttng_strncpy( + chan->name, DEFAULT_METADATA_NAME, sizeof(chan->name)); + if (ret) { + ERR("Failed to initialize metadata channel name to `%s`", + DEFAULT_METADATA_NAME); + goto error; + } + /* Set default attributes */ - chan->attr.overwrite = DEFAULT_CHANNEL_OVERWRITE; + chan->attr.overwrite = DEFAULT_METADATA_OVERWRITE; chan->attr.subbuf_size = default_get_metadata_subbuf_size(); chan->attr.num_subbuf = DEFAULT_METADATA_SUBBUF_NUM; - chan->attr.switch_timer_interval = DEFAULT_KERNEL_CHANNEL_SWITCH_TIMER; - chan->attr.read_timer_interval = DEFAULT_KERNEL_CHANNEL_READ_TIMER; - chan->attr.output = DEFAULT_KERNEL_CHANNEL_OUTPUT; + chan->attr.switch_timer_interval = DEFAULT_METADATA_SWITCH_TIMER; + chan->attr.read_timer_interval = DEFAULT_METADATA_READ_TIMER;; + + + /* + * The metadata channel of kernel sessions must use the "mmap" + * back-end since the consumer daemon accumulates complete + * metadata units before sending them to the relay daemon in + * live mode. The consumer daemon also needs to extract the contents + * of the metadata cache when computing a rotation position. + * + * In both cases, it is not possible to rely on the splice + * back-end as the consumer daemon may need to accumulate more + * content than can be backed by the ring buffer's underlying + * pages. + */ + chan->attr.output = LTTNG_EVENT_MMAP; + chan->attr.tracefile_size = 0; + chan->attr.tracefile_count = 0; + chan->attr.live_timer_interval = 0; /* Init metadata */ lkm->fd = -1; diff --git a/src/common/defaults.h b/src/common/defaults.h index baf3d5fcc..1e4b7fac7 100644 --- a/src/common/defaults.h +++ b/src/common/defaults.h @@ -210,9 +210,10 @@ #define DEFAULT_METADATA_SUBBUF_SIZE CONFIG_DEFAULT_METADATA_SUBBUF_SIZE #define DEFAULT_METADATA_SUBBUF_NUM CONFIG_DEFAULT_METADATA_SUBBUF_NUM #define DEFAULT_METADATA_CACHE_SIZE CONFIG_DEFAULT_METADATA_CACHE_SIZE -#define DEFAULT_METADATA_SWITCH_TIMER CONFIG_DEFAULT_METADATA_SWITCH_TIMER -#define DEFAULT_METADATA_READ_TIMER CONFIG_DEFAULT_METADATA_READ_TIMER -#define DEFAULT_METADATA_OUTPUT _DEFAULT_CHANNEL_OUTPUT +#define DEFAULT_METADATA_SWITCH_TIMER 0 +#define DEFAULT_METADATA_READ_TIMER 0 +#define DEFAULT_METADATA_OVERWRITE 0 +#define DEFAULT_METADATA_OUTPUT LTTNG_EVENT_MMAP /* Kernel has different defaults */ diff --git a/tests/unit/test_kernel_data.c b/tests/unit/test_kernel_data.c index 322c2eb21..444ae86ae 100644 --- a/tests/unit/test_kernel_data.c +++ b/tests/unit/test_kernel_data.c @@ -81,17 +81,17 @@ static void test_create_kernel_metadata(void) ok(kern->metadata->fd == -1 && kern->metadata->conf != NULL && kern->metadata->conf->attr.overwrite - == DEFAULT_CHANNEL_OVERWRITE && + == DEFAULT_METADATA_OVERWRITE && kern->metadata->conf->attr.subbuf_size == default_get_metadata_subbuf_size() && kern->metadata->conf->attr.num_subbuf == DEFAULT_METADATA_SUBBUF_NUM && kern->metadata->conf->attr.switch_timer_interval - == DEFAULT_KERNEL_CHANNEL_SWITCH_TIMER && + == DEFAULT_METADATA_SWITCH_TIMER && kern->metadata->conf->attr.read_timer_interval - == DEFAULT_KERNEL_CHANNEL_READ_TIMER && + == DEFAULT_METADATA_READ_TIMER && kern->metadata->conf->attr.output - == DEFAULT_KERNEL_CHANNEL_OUTPUT, + == LTTNG_EVENT_MMAP, "Validate kernel session metadata"); trace_kernel_destroy_metadata(kern->metadata); -- 2.34.1 From 23d565989350c270c68e9a6c8edfbe2dd6a6895d Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Fri, 8 May 2020 16:00:11 -0400 Subject: [PATCH 12/16] consumerd: move rotation logic to domain-agnostic read path MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The "rotation ready" logic is duplicated in both user space and kernel specializations of the read subbuffer functions. It is moved to the domain-agnostic caller where it is needed only once. This makes it easier to implement a follow-up fix and reduces code duplication. Signed-off-by: Jérémie Galarneau Change-Id: Iae952a2cd52fa458cec956ae219492557e4adf79 --- src/common/consumer/consumer.c | 41 +++++++++++++++++++- src/common/kernel-consumer/kernel-consumer.c | 40 +++---------------- src/common/ust-consumer/ust-consumer.c | 37 ++---------------- 3 files changed, 49 insertions(+), 69 deletions(-) diff --git a/src/common/consumer/consumer.c b/src/common/consumer/consumer.c index e2e7438f6..f13e90a68 100644 --- a/src/common/consumer/consumer.c +++ b/src/common/consumer/consumer.c @@ -3413,6 +3413,7 @@ ssize_t lttng_consumer_read_subbuffer(struct lttng_consumer_stream *stream, struct lttng_consumer_local_data *ctx) { ssize_t ret; + int rotation_ret; pthread_mutex_lock(&stream->chan->lock); pthread_mutex_lock(&stream->lock); @@ -3420,6 +3421,19 @@ ssize_t lttng_consumer_read_subbuffer(struct lttng_consumer_stream *stream, pthread_mutex_lock(&stream->metadata_rdv_lock); } + /* + * If the stream was flagged to be ready for rotation before we extract + * the next packet, rotate it now. + */ + if (stream->rotate_ready) { + DBG("Rotate stream before consuming data"); + ret = lttng_consumer_rotate_stream(ctx, stream); + if (ret < 0) { + ERR("Stream rotation error before consuming data"); + goto end; + } + } + switch (consumer_data.type) { case LTTNG_CONSUMER_KERNEL: ret = lttng_kconsumer_read_subbuffer(stream, ctx); @@ -3435,13 +3449,38 @@ ssize_t lttng_consumer_read_subbuffer(struct lttng_consumer_stream *stream, break; } + if (ret < 0) { + goto end; + } + + /* + * After extracting the packet, we check if the stream is now ready to + * be rotated and perform the action immediately. + * + * Don't overwrite `ret` as callers expect the number of bytes + * consumed to be returned on success. + */ + rotation_ret = lttng_consumer_stream_is_rotate_ready(stream); + if (rotation_ret == 1) { + rotation_ret = lttng_consumer_rotate_stream(ctx, stream); + if (rotation_ret < 0) { + ret = rotation_ret; + ERR("Stream rotation error after consuming data"); + goto end; + } + } else if (rotation_ret < 0) { + ret = rotation_ret; + ERR("Failed to check if stream was ready to rotate after consuming data"); + goto end; + } + +end: if (stream->metadata_flag) { pthread_cond_broadcast(&stream->metadata_rdv); pthread_mutex_unlock(&stream->metadata_rdv_lock); } pthread_mutex_unlock(&stream->lock); pthread_mutex_unlock(&stream->chan->lock); - return ret; } diff --git a/src/common/kernel-consumer/kernel-consumer.c b/src/common/kernel-consumer/kernel-consumer.c index 7032a7f7f..4a2e4e07d 100644 --- a/src/common/kernel-consumer/kernel-consumer.c +++ b/src/common/kernel-consumer/kernel-consumer.c @@ -1590,26 +1590,14 @@ ssize_t lttng_kconsumer_read_subbuffer(struct lttng_consumer_stream *stream, struct lttng_consumer_local_data *ctx) { unsigned long len, subbuf_size, padding; - int err, write_index = 1, rotation_ret; + int err, write_index = 1; ssize_t ret = 0; int infd = stream->wait_fd; struct ctf_packet_index index = {}; + bool in_error_state = false; DBG("In read_subbuffer (infd : %d)", infd); - /* - * If the stream was flagged to be ready for rotation before we extract the - * next packet, rotate it now. - */ - if (stream->rotate_ready) { - DBG("Rotate stream before extracting data"); - rotation_ret = lttng_consumer_rotate_stream(ctx, stream); - if (rotation_ret < 0) { - ERR("Stream rotation error"); - ret = -1; - goto error; - } - } /* Get the next subbuffer */ err = kernctl_get_next_subbuf(infd); @@ -1795,11 +1783,13 @@ error_put_subbuf: } ret = err; goto error; + } else if (in_error_state) { + goto error; } /* Write index if needed. */ if (!write_index) { - goto rotate; + goto end; } if (stream->chan->live_timer_interval && !stream->metadata_flag) { @@ -1832,25 +1822,7 @@ error_put_subbuf: goto error; } -rotate: - /* - * After extracting the packet, we check if the stream is now ready to be - * rotated and perform the action immediately. - */ - rotation_ret = lttng_consumer_stream_is_rotate_ready(stream); - if (rotation_ret == 1) { - rotation_ret = lttng_consumer_rotate_stream(ctx, stream); - if (rotation_ret < 0) { - ERR("Stream rotation error"); - ret = -1; - goto error; - } - } else if (rotation_ret < 0) { - ERR("Checking if stream is ready to rotate"); - ret = -1; - goto error; - } - +end: error: return ret; } diff --git a/src/common/ust-consumer/ust-consumer.c b/src/common/ust-consumer/ust-consumer.c index e5143cd4a..2f09eac80 100644 --- a/src/common/ust-consumer/ust-consumer.c +++ b/src/common/ust-consumer/ust-consumer.c @@ -2789,7 +2789,7 @@ int lttng_ustconsumer_read_subbuffer(struct lttng_consumer_stream *stream, struct lttng_consumer_local_data *ctx) { unsigned long len, subbuf_size, padding; - int err, write_index = 1, rotation_ret; + int err, write_index = 1; long ret = 0; struct ustctl_consumer_stream *ustream; struct ctf_packet_index index; @@ -2827,20 +2827,6 @@ int lttng_ustconsumer_read_subbuffer(struct lttng_consumer_stream *stream, } } - /* - * If the stream was flagged to be ready for rotation before we extract the - * next packet, rotate it now. - */ - if (stream->rotate_ready) { - DBG("Rotate stream before extracting data"); - rotation_ret = lttng_consumer_rotate_stream(ctx, stream); - if (rotation_ret < 0) { - ERR("Stream rotation error"); - ret = -1; - goto error; - } - } - retry: /* Get the next subbuffer */ err = ustctl_get_next_subbuf(ustream); @@ -2952,7 +2938,7 @@ error_put_subbuf: /* Write index if needed. */ if (!write_index) { - goto rotate; + goto end; } if (stream->chan->live_timer_interval && !stream->metadata_flag) { @@ -2987,24 +2973,7 @@ error_put_subbuf: goto error; } -rotate: - /* - * After extracting the packet, we check if the stream is now ready to be - * rotated and perform the action immediately. - */ - rotation_ret = lttng_consumer_stream_is_rotate_ready(stream); - if (rotation_ret == 1) { - rotation_ret = lttng_consumer_rotate_stream(ctx, stream); - if (rotation_ret < 0) { - ERR("Stream rotation error"); - ret = -1; - goto error; - } - } else if (rotation_ret < 0) { - ERR("Checking if stream is ready to rotate"); - ret = -1; - goto error; - } +end: error: return ret; } -- 2.34.1 From 6f9449c22eef59294cf1e1dc3610a5cbf14baec0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Sun, 10 May 2020 18:00:26 -0400 Subject: [PATCH 13/16] consumerd: refactor: split read_subbuf into sub-operations MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The read_subbuf code paths intertwine domain-specific operations and metadata/data-specific logic which makes modifications error prone and introduces a fair amount of code duplication. lttng_consumer_read_subbuffer is effectively turned into a template method invoking overridable callbacks making most of the consumption logic domain and data/metadata agnostic. The goal is not to extensively clean-up that code path. A follow-up fix introduces metadata buffering logic which would not reasonably fit in the current scheme. This clean-up makes it easier to safely introduce those changes. No changes in behaviour are intended by this change. Signed-off-by: Jérémie Galarneau Change-Id: I9366f2e2a38018ca9b617b93ad9259340180c55d --- src/common/consumer/consumer-stream.c | 664 ++++++++++++++----- src/common/consumer/consumer-stream.h | 18 + src/common/consumer/consumer.c | 207 +++--- src/common/consumer/consumer.h | 171 ++++- src/common/kernel-consumer/kernel-consumer.c | 565 +++++----------- src/common/kernel-consumer/kernel-consumer.h | 2 - src/common/ust-consumer/ust-consumer.c | 528 ++++++--------- 7 files changed, 1149 insertions(+), 1006 deletions(-) diff --git a/src/common/consumer/consumer-stream.c b/src/common/consumer/consumer-stream.c index 8318d79d9..5dc380e5e 100644 --- a/src/common/consumer/consumer-stream.c +++ b/src/common/consumer/consumer-stream.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "consumer-stream.h" @@ -36,6 +38,509 @@ static void free_stream_rcu(struct rcu_head *head) free(stream); } +static void consumer_stream_data_lock_all(struct lttng_consumer_stream *stream) +{ + pthread_mutex_lock(&stream->chan->lock); + pthread_mutex_lock(&stream->lock); +} + +static void consumer_stream_data_unlock_all(struct lttng_consumer_stream *stream) +{ + pthread_mutex_unlock(&stream->lock); + pthread_mutex_unlock(&stream->chan->lock); +} + +static void consumer_stream_metadata_lock_all(struct lttng_consumer_stream *stream) +{ + consumer_stream_data_lock_all(stream); + pthread_mutex_lock(&stream->metadata_rdv_lock); +} + +static void consumer_stream_metadata_unlock_all(struct lttng_consumer_stream *stream) +{ + pthread_mutex_unlock(&stream->metadata_rdv_lock); + consumer_stream_data_unlock_all(stream); +} + +/* Only used for data streams. */ +static int consumer_stream_update_stats(struct lttng_consumer_stream *stream, + const struct stream_subbuffer *subbuf) +{ + int ret = 0; + uint64_t sequence_number; + const uint64_t discarded_events = + LTTNG_OPTIONAL_GET(subbuf->info.data.sequence_number); + + if (!subbuf->info.data.sequence_number.is_set) { + /* Command not supported by the tracer. */ + sequence_number = -1ULL; + stream->sequence_number_unavailable = true; + } else { + sequence_number = subbuf->info.data.sequence_number.value; + } + + /* + * Start the sequence when we extract the first packet in case we don't + * start at 0 (for example if a consumer is not connected to the + * session immediately after the beginning). + */ + if (stream->last_sequence_number == -1ULL) { + stream->last_sequence_number = sequence_number; + } else if (sequence_number > stream->last_sequence_number) { + stream->chan->lost_packets += sequence_number - + stream->last_sequence_number - 1; + } else { + /* seq <= last_sequence_number */ + ERR("Sequence number inconsistent : prev = %" PRIu64 + ", current = %" PRIu64, + stream->last_sequence_number, sequence_number); + ret = -1; + goto end; + } + stream->last_sequence_number = sequence_number; + + if (discarded_events < stream->last_discarded_events) { + /* + * Overflow has occurred. We assume only one wrap-around + * has occurred. + */ + stream->chan->discarded_events += + (1ULL << (CAA_BITS_PER_LONG - 1)) - + stream->last_discarded_events + + discarded_events; + } else { + stream->chan->discarded_events += discarded_events - + stream->last_discarded_events; + } + stream->last_discarded_events = discarded_events; + ret = 0; + +end: + return ret; +} + +static +void ctf_packet_index_populate(struct ctf_packet_index *index, + off_t offset, const struct stream_subbuffer *subbuffer) +{ + *index = (typeof(*index)){ + .offset = htobe64(offset), + .packet_size = htobe64(subbuffer->info.data.packet_size), + .content_size = htobe64(subbuffer->info.data.content_size), + .timestamp_begin = htobe64( + subbuffer->info.data.timestamp_begin), + .timestamp_end = htobe64( + subbuffer->info.data.timestamp_end), + .events_discarded = htobe64( + subbuffer->info.data.events_discarded), + .stream_id = htobe64(subbuffer->info.data.stream_id), + .stream_instance_id = htobe64( + subbuffer->info.data.stream_instance_id.is_set ? + subbuffer->info.data.stream_instance_id.value : -1ULL), + .packet_seq_num = htobe64( + subbuffer->info.data.sequence_number.is_set ? + subbuffer->info.data.sequence_number.value : -1ULL), + }; +} + +static ssize_t consumer_stream_consume_mmap( + struct lttng_consumer_local_data *ctx, + struct lttng_consumer_stream *stream, + const struct stream_subbuffer *subbuffer) +{ + const unsigned long padding_size = + subbuffer->info.data.padded_subbuf_size - + subbuffer->info.data.subbuf_size; + + return lttng_consumer_on_read_subbuffer_mmap( + ctx, stream, &subbuffer->buffer.buffer, padding_size); +} + +static ssize_t consumer_stream_consume_splice( + struct lttng_consumer_local_data *ctx, + struct lttng_consumer_stream *stream, + const struct stream_subbuffer *subbuffer) +{ + return lttng_consumer_on_read_subbuffer_splice(ctx, stream, + subbuffer->info.data.padded_subbuf_size, 0); +} + +static int consumer_stream_send_index( + struct lttng_consumer_stream *stream, + const struct stream_subbuffer *subbuffer, + struct lttng_consumer_local_data *ctx) +{ + off_t packet_offset = 0; + struct ctf_packet_index index = {}; + + /* + * This is called after consuming the sub-buffer; substract the + * effect this sub-buffer from the offset. + */ + if (stream->net_seq_idx == (uint64_t) -1ULL) { + packet_offset = stream->out_fd_offset - + subbuffer->info.data.padded_subbuf_size; + } + + ctf_packet_index_populate(&index, packet_offset, subbuffer); + return consumer_stream_write_index(stream, &index); +} + +/* + * Actually do the metadata sync using the given metadata stream. + * + * Return 0 on success else a negative value. ENODATA can be returned also + * indicating that there is no metadata available for that stream. + */ +static int do_sync_metadata(struct lttng_consumer_stream *metadata, + struct lttng_consumer_local_data *ctx) +{ + int ret; + + assert(metadata); + assert(metadata->metadata_flag); + assert(ctx); + + /* + * In UST, since we have to write the metadata from the cache packet + * by packet, we might need to start this procedure multiple times + * until all the metadata from the cache has been extracted. + */ + do { + /* + * Steps : + * - Lock the metadata stream + * - Check if metadata stream node was deleted before locking. + * - if yes, release and return success + * - Check if new metadata is ready (flush + snapshot pos) + * - If nothing : release and return. + * - Lock the metadata_rdv_lock + * - Unlock the metadata stream + * - cond_wait on metadata_rdv to wait the wakeup from the + * metadata thread + * - Unlock the metadata_rdv_lock + */ + pthread_mutex_lock(&metadata->lock); + + /* + * There is a possibility that we were able to acquire a reference on the + * stream from the RCU hash table but between then and now, the node might + * have been deleted just before the lock is acquired. Thus, after locking, + * we make sure the metadata node has not been deleted which means that the + * buffers are closed. + * + * In that case, there is no need to sync the metadata hence returning a + * success return code. + */ + ret = cds_lfht_is_node_deleted(&metadata->node.node); + if (ret) { + ret = 0; + goto end_unlock_mutex; + } + + switch (ctx->type) { + case LTTNG_CONSUMER_KERNEL: + /* + * Empty the metadata cache and flush the current stream. + */ + ret = lttng_kconsumer_sync_metadata(metadata); + break; + case LTTNG_CONSUMER32_UST: + case LTTNG_CONSUMER64_UST: + /* + * Ask the sessiond if we have new metadata waiting and update the + * consumer metadata cache. + */ + ret = lttng_ustconsumer_sync_metadata(ctx, metadata); + break; + default: + assert(0); + ret = -1; + break; + } + /* + * Error or no new metadata, we exit here. + */ + if (ret <= 0 || ret == ENODATA) { + goto end_unlock_mutex; + } + + /* + * At this point, new metadata have been flushed, so we wait on the + * rendez-vous point for the metadata thread to wake us up when it + * finishes consuming the metadata and continue execution. + */ + + pthread_mutex_lock(&metadata->metadata_rdv_lock); + + /* + * Release metadata stream lock so the metadata thread can process it. + */ + pthread_mutex_unlock(&metadata->lock); + + /* + * Wait on the rendez-vous point. Once woken up, it means the metadata was + * consumed and thus synchronization is achieved. + */ + pthread_cond_wait(&metadata->metadata_rdv, &metadata->metadata_rdv_lock); + pthread_mutex_unlock(&metadata->metadata_rdv_lock); + } while (ret == EAGAIN); + + /* Success */ + return 0; + +end_unlock_mutex: + pthread_mutex_unlock(&metadata->lock); + return ret; +} + +/* + * Synchronize the metadata using a given session ID. A successful acquisition + * of a metadata stream will trigger a request to the session daemon and a + * snapshot so the metadata thread can consume it. + * + * This function call is a rendez-vous point between the metadata thread and + * the data thread. + * + * Return 0 on success or else a negative value. + */ +int consumer_stream_sync_metadata(struct lttng_consumer_local_data *ctx, + uint64_t session_id) +{ + int ret; + struct lttng_consumer_stream *stream = NULL; + struct lttng_ht_iter iter; + struct lttng_ht *ht; + + assert(ctx); + + /* Ease our life a bit. */ + ht = consumer_data.stream_list_ht; + + rcu_read_lock(); + + /* Search the metadata associated with the session id of the given stream. */ + + cds_lfht_for_each_entry_duplicate(ht->ht, + ht->hash_fct(&session_id, lttng_ht_seed), ht->match_fct, + &session_id, &iter.iter, stream, node_session_id.node) { + if (!stream->metadata_flag) { + continue; + } + + ret = do_sync_metadata(stream, ctx); + if (ret < 0) { + goto end; + } + } + + /* + * Force return code to 0 (success) since ret might be ENODATA for instance + * which is not an error but rather that we should come back. + */ + ret = 0; + +end: + rcu_read_unlock(); + return ret; +} + +static int consumer_stream_sync_metadata_index( + struct lttng_consumer_stream *stream, + const struct stream_subbuffer *subbuffer, + struct lttng_consumer_local_data *ctx) +{ + int ret; + + /* Block until all the metadata is sent. */ + pthread_mutex_lock(&stream->metadata_timer_lock); + assert(!stream->missed_metadata_flush); + stream->waiting_on_metadata = true; + pthread_mutex_unlock(&stream->metadata_timer_lock); + + ret = consumer_stream_sync_metadata(ctx, stream->session_id); + + pthread_mutex_lock(&stream->metadata_timer_lock); + stream->waiting_on_metadata = false; + if (stream->missed_metadata_flush) { + stream->missed_metadata_flush = false; + pthread_mutex_unlock(&stream->metadata_timer_lock); + (void) stream->read_subbuffer_ops.send_live_beacon(stream); + } else { + pthread_mutex_unlock(&stream->metadata_timer_lock); + } + if (ret < 0) { + goto end; + } + + ret = consumer_stream_send_index(stream, subbuffer, ctx); +end: + return ret; +} + +/* + * Check if the local version of the metadata stream matches with the version + * of the metadata stream in the kernel. If it was updated, set the reset flag + * on the stream. + */ +static +int metadata_stream_check_version(struct lttng_consumer_stream *stream, + const struct stream_subbuffer *subbuffer) +{ + if (stream->metadata_version == subbuffer->info.metadata.version) { + goto end; + } + + DBG("New metadata version detected"); + stream->metadata_version = subbuffer->info.metadata.version; + stream->reset_metadata_flag = 1; + + if (stream->read_subbuffer_ops.reset_metadata) { + stream->read_subbuffer_ops.reset_metadata(stream); + } + +end: + return 0; +} + +struct lttng_consumer_stream *consumer_stream_create( + struct lttng_consumer_channel *channel, + uint64_t channel_key, + uint64_t stream_key, + const char *channel_name, + uint64_t relayd_id, + uint64_t session_id, + struct lttng_trace_chunk *trace_chunk, + int cpu, + int *alloc_ret, + enum consumer_channel_type type, + unsigned int monitor) +{ + int ret; + struct lttng_consumer_stream *stream; + + stream = zmalloc(sizeof(*stream)); + if (stream == NULL) { + PERROR("malloc struct lttng_consumer_stream"); + ret = -ENOMEM; + goto end; + } + + if (trace_chunk && !lttng_trace_chunk_get(trace_chunk)) { + ERR("Failed to acquire trace chunk reference during the creation of a stream"); + ret = -1; + goto error; + } + + rcu_read_lock(); + stream->chan = channel; + stream->key = stream_key; + stream->trace_chunk = trace_chunk; + stream->out_fd = -1; + stream->out_fd_offset = 0; + stream->output_written = 0; + stream->net_seq_idx = relayd_id; + stream->session_id = session_id; + stream->monitor = monitor; + stream->endpoint_status = CONSUMER_ENDPOINT_ACTIVE; + stream->index_file = NULL; + stream->last_sequence_number = -1ULL; + stream->rotate_position = -1ULL; + pthread_mutex_init(&stream->lock, NULL); + pthread_mutex_init(&stream->metadata_timer_lock, NULL); + + /* If channel is the metadata, flag this stream as metadata. */ + if (type == CONSUMER_CHANNEL_TYPE_METADATA) { + stream->metadata_flag = 1; + /* Metadata is flat out. */ + strncpy(stream->name, DEFAULT_METADATA_NAME, sizeof(stream->name)); + /* Live rendez-vous point. */ + pthread_cond_init(&stream->metadata_rdv, NULL); + pthread_mutex_init(&stream->metadata_rdv_lock, NULL); + } else { + /* Format stream name to _ */ + ret = snprintf(stream->name, sizeof(stream->name), "%s_%d", + channel_name, cpu); + if (ret < 0) { + PERROR("snprintf stream name"); + goto error; + } + } + + switch (channel->output) { + case CONSUMER_CHANNEL_SPLICE: + stream->output = LTTNG_EVENT_SPLICE; + ret = utils_create_pipe(stream->splice_pipe); + if (ret < 0) { + goto error; + } + break; + case CONSUMER_CHANNEL_MMAP: + stream->output = LTTNG_EVENT_MMAP; + break; + default: + abort(); + } + + /* Key is always the wait_fd for streams. */ + lttng_ht_node_init_u64(&stream->node, stream->key); + + /* Init node per channel id key */ + lttng_ht_node_init_u64(&stream->node_channel_id, channel_key); + + /* Init session id node with the stream session id */ + lttng_ht_node_init_u64(&stream->node_session_id, stream->session_id); + + DBG3("Allocated stream %s (key %" PRIu64 ", chan_key %" PRIu64 + " relayd_id %" PRIu64 ", session_id %" PRIu64, + stream->name, stream->key, channel_key, + stream->net_seq_idx, stream->session_id); + + rcu_read_unlock(); + + if (type == CONSUMER_CHANNEL_TYPE_METADATA) { + stream->read_subbuffer_ops.lock = + consumer_stream_metadata_lock_all; + stream->read_subbuffer_ops.unlock = + consumer_stream_metadata_unlock_all; + stream->read_subbuffer_ops.pre_consume_subbuffer = + metadata_stream_check_version; + } else { + stream->read_subbuffer_ops.lock = consumer_stream_data_lock_all; + stream->read_subbuffer_ops.unlock = + consumer_stream_data_unlock_all; + stream->read_subbuffer_ops.pre_consume_subbuffer = + consumer_stream_update_stats; + if (channel->is_live) { + stream->read_subbuffer_ops.post_consume = + consumer_stream_sync_metadata_index; + } else { + stream->read_subbuffer_ops.post_consume = + consumer_stream_send_index; + } + } + + if (channel->output == CONSUMER_CHANNEL_MMAP) { + stream->read_subbuffer_ops.consume_subbuffer = + consumer_stream_consume_mmap; + } else { + stream->read_subbuffer_ops.consume_subbuffer = + consumer_stream_consume_splice; + } + + return stream; + +error: + rcu_read_unlock(); + lttng_trace_chunk_put(stream->trace_chunk); + free(stream); +end: + if (alloc_ret) { + *alloc_ret = ret; + } + return NULL; +} + /* * Close stream on the relayd side. This call can destroy a relayd if the * conditions are met. @@ -393,165 +898,6 @@ error: return ret; } -/* - * Actually do the metadata sync using the given metadata stream. - * - * Return 0 on success else a negative value. ENODATA can be returned also - * indicating that there is no metadata available for that stream. - */ -static int do_sync_metadata(struct lttng_consumer_stream *metadata, - struct lttng_consumer_local_data *ctx) -{ - int ret; - - assert(metadata); - assert(metadata->metadata_flag); - assert(ctx); - - /* - * In UST, since we have to write the metadata from the cache packet - * by packet, we might need to start this procedure multiple times - * until all the metadata from the cache has been extracted. - */ - do { - /* - * Steps : - * - Lock the metadata stream - * - Check if metadata stream node was deleted before locking. - * - if yes, release and return success - * - Check if new metadata is ready (flush + snapshot pos) - * - If nothing : release and return. - * - Lock the metadata_rdv_lock - * - Unlock the metadata stream - * - cond_wait on metadata_rdv to wait the wakeup from the - * metadata thread - * - Unlock the metadata_rdv_lock - */ - pthread_mutex_lock(&metadata->lock); - - /* - * There is a possibility that we were able to acquire a reference on the - * stream from the RCU hash table but between then and now, the node might - * have been deleted just before the lock is acquired. Thus, after locking, - * we make sure the metadata node has not been deleted which means that the - * buffers are closed. - * - * In that case, there is no need to sync the metadata hence returning a - * success return code. - */ - ret = cds_lfht_is_node_deleted(&metadata->node.node); - if (ret) { - ret = 0; - goto end_unlock_mutex; - } - - switch (ctx->type) { - case LTTNG_CONSUMER_KERNEL: - /* - * Empty the metadata cache and flush the current stream. - */ - ret = lttng_kconsumer_sync_metadata(metadata); - break; - case LTTNG_CONSUMER32_UST: - case LTTNG_CONSUMER64_UST: - /* - * Ask the sessiond if we have new metadata waiting and update the - * consumer metadata cache. - */ - ret = lttng_ustconsumer_sync_metadata(ctx, metadata); - break; - default: - assert(0); - ret = -1; - break; - } - /* - * Error or no new metadata, we exit here. - */ - if (ret <= 0 || ret == ENODATA) { - goto end_unlock_mutex; - } - - /* - * At this point, new metadata have been flushed, so we wait on the - * rendez-vous point for the metadata thread to wake us up when it - * finishes consuming the metadata and continue execution. - */ - - pthread_mutex_lock(&metadata->metadata_rdv_lock); - - /* - * Release metadata stream lock so the metadata thread can process it. - */ - pthread_mutex_unlock(&metadata->lock); - - /* - * Wait on the rendez-vous point. Once woken up, it means the metadata was - * consumed and thus synchronization is achieved. - */ - pthread_cond_wait(&metadata->metadata_rdv, &metadata->metadata_rdv_lock); - pthread_mutex_unlock(&metadata->metadata_rdv_lock); - } while (ret == EAGAIN); - - /* Success */ - return 0; - -end_unlock_mutex: - pthread_mutex_unlock(&metadata->lock); - return ret; -} - -/* - * Synchronize the metadata using a given session ID. A successful acquisition - * of a metadata stream will trigger a request to the session daemon and a - * snapshot so the metadata thread can consume it. - * - * This function call is a rendez-vous point between the metadata thread and - * the data thread. - * - * Return 0 on success or else a negative value. - */ -int consumer_stream_sync_metadata(struct lttng_consumer_local_data *ctx, - uint64_t session_id) -{ - int ret; - struct lttng_consumer_stream *stream = NULL; - struct lttng_ht_iter iter; - struct lttng_ht *ht; - - assert(ctx); - - /* Ease our life a bit. */ - ht = consumer_data.stream_list_ht; - - rcu_read_lock(); - - /* Search the metadata associated with the session id of the given stream. */ - - cds_lfht_for_each_entry_duplicate(ht->ht, - ht->hash_fct(&session_id, lttng_ht_seed), ht->match_fct, - &session_id, &iter.iter, stream, node_session_id.node) { - if (!stream->metadata_flag) { - continue; - } - - ret = do_sync_metadata(stream, ctx); - if (ret < 0) { - goto end; - } - } - - /* - * Force return code to 0 (success) since ret might be ENODATA for instance - * which is not an error but rather that we should come back. - */ - ret = 0; - -end: - rcu_read_unlock(); - return ret; -} - int consumer_stream_create_output_files(struct lttng_consumer_stream *stream, bool create_index) { diff --git a/src/common/consumer/consumer-stream.h b/src/common/consumer/consumer-stream.h index 0e1827c4a..eb00dac78 100644 --- a/src/common/consumer/consumer-stream.h +++ b/src/common/consumer/consumer-stream.h @@ -10,6 +10,24 @@ #include "consumer.h" +/* + * Create a consumer stream. + * + * The channel lock MUST be acquired. + */ +struct lttng_consumer_stream *consumer_stream_create( + struct lttng_consumer_channel *channel, + uint64_t channel_key, + uint64_t stream_key, + const char *channel_name, + uint64_t relayd_id, + uint64_t session_id, + struct lttng_trace_chunk *trace_chunk, + int cpu, + int *alloc_ret, + enum consumer_channel_type type, + unsigned int monitor); + /* * Close stream's file descriptors and, if needed, close stream also on the * relayd side. diff --git a/src/common/consumer/consumer.c b/src/common/consumer/consumer.c index f13e90a68..5c211339d 100644 --- a/src/common/consumer/consumer.c +++ b/src/common/consumer/consumer.c @@ -7,6 +7,7 @@ * */ +#include "common/index/ctf-index.h" #define _LGPL_SOURCE #include #include @@ -568,98 +569,6 @@ void consumer_stream_update_channel_attributes( channel->tracefile_size; } -struct lttng_consumer_stream *consumer_allocate_stream( - struct lttng_consumer_channel *channel, - uint64_t channel_key, - uint64_t stream_key, - const char *channel_name, - uint64_t relayd_id, - uint64_t session_id, - struct lttng_trace_chunk *trace_chunk, - int cpu, - int *alloc_ret, - enum consumer_channel_type type, - unsigned int monitor) -{ - int ret; - struct lttng_consumer_stream *stream; - - stream = zmalloc(sizeof(*stream)); - if (stream == NULL) { - PERROR("malloc struct lttng_consumer_stream"); - ret = -ENOMEM; - goto end; - } - - if (trace_chunk && !lttng_trace_chunk_get(trace_chunk)) { - ERR("Failed to acquire trace chunk reference during the creation of a stream"); - ret = -1; - goto error; - } - - rcu_read_lock(); - stream->chan = channel; - stream->key = stream_key; - stream->trace_chunk = trace_chunk; - stream->out_fd = -1; - stream->out_fd_offset = 0; - stream->output_written = 0; - stream->net_seq_idx = relayd_id; - stream->session_id = session_id; - stream->monitor = monitor; - stream->endpoint_status = CONSUMER_ENDPOINT_ACTIVE; - stream->index_file = NULL; - stream->last_sequence_number = -1ULL; - stream->rotate_position = -1ULL; - pthread_mutex_init(&stream->lock, NULL); - pthread_mutex_init(&stream->metadata_timer_lock, NULL); - - /* If channel is the metadata, flag this stream as metadata. */ - if (type == CONSUMER_CHANNEL_TYPE_METADATA) { - stream->metadata_flag = 1; - /* Metadata is flat out. */ - strncpy(stream->name, DEFAULT_METADATA_NAME, sizeof(stream->name)); - /* Live rendez-vous point. */ - pthread_cond_init(&stream->metadata_rdv, NULL); - pthread_mutex_init(&stream->metadata_rdv_lock, NULL); - } else { - /* Format stream name to _ */ - ret = snprintf(stream->name, sizeof(stream->name), "%s_%d", - channel_name, cpu); - if (ret < 0) { - PERROR("snprintf stream name"); - goto error; - } - } - - /* Key is always the wait_fd for streams. */ - lttng_ht_node_init_u64(&stream->node, stream->key); - - /* Init node per channel id key */ - lttng_ht_node_init_u64(&stream->node_channel_id, channel_key); - - /* Init session id node with the stream session id */ - lttng_ht_node_init_u64(&stream->node_session_id, stream->session_id); - - DBG3("Allocated stream %s (key %" PRIu64 ", chan_key %" PRIu64 - " relayd_id %" PRIu64 ", session_id %" PRIu64, - stream->name, stream->key, channel_key, - stream->net_seq_idx, stream->session_id); - - rcu_read_unlock(); - return stream; - -error: - rcu_read_unlock(); - lttng_trace_chunk_put(stream->trace_chunk); - free(stream); -end: - if (alloc_ret) { - *alloc_ret = ret; - } - return NULL; -} - /* * Add a stream to the global list protected by a mutex. */ @@ -1467,7 +1376,7 @@ void lttng_consumer_sync_trace_file(struct lttng_consumer_stream *stream, struct lttng_consumer_local_data *lttng_consumer_create( enum lttng_consumer_type type, ssize_t (*buffer_ready)(struct lttng_consumer_stream *stream, - struct lttng_consumer_local_data *ctx), + struct lttng_consumer_local_data *ctx, bool locked_by_caller), int (*recv_channel)(struct lttng_consumer_channel *channel), int (*recv_stream)(struct lttng_consumer_stream *stream), int (*update_stream)(uint64_t stream_key, uint32_t state)) @@ -1677,8 +1586,7 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( struct lttng_consumer_local_data *ctx, struct lttng_consumer_stream *stream, const struct lttng_buffer_view *buffer, - unsigned long padding, - struct ctf_packet_index *index) + unsigned long padding) { ssize_t ret = 0; off_t orig_offset = stream->out_fd_offset; @@ -1770,10 +1678,6 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( orig_offset = 0; } stream->tracefile_size_current += buffer->size; - if (index) { - index->offset = htobe64(stream->out_fd_offset); - } - write_len = buffer->size; } @@ -1850,8 +1754,7 @@ end: ssize_t lttng_consumer_on_read_subbuffer_splice( struct lttng_consumer_local_data *ctx, struct lttng_consumer_stream *stream, unsigned long len, - unsigned long padding, - struct ctf_packet_index *index) + unsigned long padding) { ssize_t ret = 0, written = 0, ret_splice = 0; loff_t offset = 0; @@ -1955,7 +1858,6 @@ ssize_t lttng_consumer_on_read_subbuffer_splice( orig_offset = 0; } stream->tracefile_size_current += len; - index->offset = htobe64(stream->out_fd_offset); } while (len > 0) { @@ -2522,7 +2424,7 @@ restart: do { health_code_update(); - len = ctx->on_buffer_ready(stream, ctx); + len = ctx->on_buffer_ready(stream, ctx, false); /* * We don't check the return value here since if we get * a negative len, it means an error occurred thus we @@ -2549,7 +2451,7 @@ restart: do { health_code_update(); - len = ctx->on_buffer_ready(stream, ctx); + len = ctx->on_buffer_ready(stream, ctx, false); /* * We don't check the return value here since if we get * a negative len, it means an error occurred thus we @@ -2765,7 +2667,7 @@ void *consumer_thread_data_poll(void *data) if (pollfd[i].revents & POLLPRI) { DBG("Urgent read on fd %d", pollfd[i].fd); high_prio = 1; - len = ctx->on_buffer_ready(local_stream[i], ctx); + len = ctx->on_buffer_ready(local_stream[i], ctx, false); /* it's ok to have an unavailable sub-buffer */ if (len < 0 && len != -EAGAIN && len != -ENODATA) { /* Clean the stream and free it. */ @@ -2796,7 +2698,7 @@ void *consumer_thread_data_poll(void *data) local_stream[i]->hangup_flush_done || local_stream[i]->has_data) { DBG("Normal read on fd %d", pollfd[i].fd); - len = ctx->on_buffer_ready(local_stream[i], ctx); + len = ctx->on_buffer_ready(local_stream[i], ctx, false); /* it's ok to have an unavailable sub-buffer */ if (len < 0 && len != -EAGAIN && len != -ENODATA) { /* Clean the stream and free it. */ @@ -3410,15 +3312,22 @@ error_testpoint: } ssize_t lttng_consumer_read_subbuffer(struct lttng_consumer_stream *stream, - struct lttng_consumer_local_data *ctx) + struct lttng_consumer_local_data *ctx, + bool locked_by_caller) { - ssize_t ret; + ssize_t ret, written_bytes; int rotation_ret; + struct stream_subbuffer subbuffer = {}; - pthread_mutex_lock(&stream->chan->lock); - pthread_mutex_lock(&stream->lock); - if (stream->metadata_flag) { - pthread_mutex_lock(&stream->metadata_rdv_lock); + if (!locked_by_caller) { + stream->read_subbuffer_ops.lock(stream); + } + + if (stream->read_subbuffer_ops.on_wake_up) { + ret = stream->read_subbuffer_ops.on_wake_up(stream); + if (ret) { + goto end; + } } /* @@ -3434,25 +3343,55 @@ ssize_t lttng_consumer_read_subbuffer(struct lttng_consumer_stream *stream, } } - switch (consumer_data.type) { - case LTTNG_CONSUMER_KERNEL: - ret = lttng_kconsumer_read_subbuffer(stream, ctx); - break; - case LTTNG_CONSUMER32_UST: - case LTTNG_CONSUMER64_UST: - ret = lttng_ustconsumer_read_subbuffer(stream, ctx); - break; - default: - ERR("Unknown consumer_data type"); - assert(0); - ret = -ENOSYS; - break; + ret = stream->read_subbuffer_ops.get_next_subbuffer(stream, &subbuffer); + if (ret) { + if (ret == -ENODATA) { + /* Not an error. */ + ret = 0; + } + goto end; } - if (ret < 0) { + ret = stream->read_subbuffer_ops.pre_consume_subbuffer( + stream, &subbuffer); + if (ret) { + goto error_put_subbuf; + } + + written_bytes = stream->read_subbuffer_ops.consume_subbuffer( + ctx, stream, &subbuffer); + /* + * Should write subbuf_size amount of data when network streaming or + * the full padded size when we are not streaming. + */ + if ((written_bytes != subbuffer.info.data.subbuf_size && + stream->net_seq_idx != (uint64_t) -1ULL) || + (written_bytes != subbuffer.info.data.padded_subbuf_size && + stream->net_seq_idx == + (uint64_t) -1ULL)) { + /* + * Display the error but continue processing to try to + * release the subbuffer. This is a DBG statement + * since this can happen without being a critical + * error. + */ + DBG("Failed to write to tracefile (written_bytes: %zd != padded subbuffer size: %lu, subbuffer size: %lu)", + written_bytes, subbuffer.info.data.subbuf_size, + subbuffer.info.data.padded_subbuf_size); + } + + ret = stream->read_subbuffer_ops.put_next_subbuffer(stream, &subbuffer); + if (ret) { goto end; } + if (stream->read_subbuffer_ops.post_consume) { + ret = stream->read_subbuffer_ops.post_consume(stream, &subbuffer, ctx); + if (ret) { + goto end; + } + } + /* * After extracting the packet, we check if the stream is now ready to * be rotated and perform the action immediately. @@ -3474,14 +3413,20 @@ ssize_t lttng_consumer_read_subbuffer(struct lttng_consumer_stream *stream, goto end; } + if (stream->read_subbuffer_ops.on_sleep) { + stream->read_subbuffer_ops.on_sleep(stream, ctx); + } + + ret = written_bytes; end: - if (stream->metadata_flag) { - pthread_cond_broadcast(&stream->metadata_rdv); - pthread_mutex_unlock(&stream->metadata_rdv_lock); + if (!locked_by_caller) { + stream->read_subbuffer_ops.unlock(stream); } - pthread_mutex_unlock(&stream->lock); - pthread_mutex_unlock(&stream->chan->lock); + return ret; +error_put_subbuf: + (void) stream->read_subbuffer_ops.put_next_subbuffer(stream, &subbuffer); + goto end; } int lttng_consumer_on_recv_stream(struct lttng_consumer_stream *stream) diff --git a/src/common/consumer/consumer.h b/src/common/consumer/consumer.h index 000040982..aa8a401a0 100644 --- a/src/common/consumer/consumer.h +++ b/src/common/consumer/consumer.h @@ -28,6 +28,8 @@ #include #include +struct lttng_consumer_local_data; + /* Commands for consumer */ enum lttng_consumer_command { LTTNG_CONSUMER_ADD_CHANNEL, @@ -244,6 +246,142 @@ struct lttng_consumer_channel { bool streams_sent_to_relayd; }; +struct stream_subbuffer { + union { + /* + * CONSUMER_CHANNEL_SPLICE + * No ownership assumed. + */ + int fd; + /* CONSUMER_CHANNEL_MMAP */ + struct lttng_buffer_view buffer; + } buffer; + union { + /* + * Common members are fine to access through either + * union entries (as per C11, Common Initial Sequence). + */ + struct { + unsigned long subbuf_size; + unsigned long padded_subbuf_size; + uint64_t version; + } metadata; + struct { + unsigned long subbuf_size; + unsigned long padded_subbuf_size; + uint64_t packet_size; + uint64_t content_size; + uint64_t timestamp_begin; + uint64_t timestamp_end; + uint64_t events_discarded; + /* Left unset when unsupported. */ + LTTNG_OPTIONAL(uint64_t) sequence_number; + uint64_t stream_id; + /* Left unset when unsupported. */ + LTTNG_OPTIONAL(uint64_t) stream_instance_id; + } data; + } info; +}; + +/* + * Perform any operation required to acknowledge + * the wake-up of a consumer stream (e.g. consume a byte on a wake-up pipe). + * + * Stream and channel locks are acquired during this call. + */ +typedef int (*on_wake_up_cb)(struct lttng_consumer_stream *); + +/* + * Perform any operation required before a consumer stream is put + * to sleep before awaiting a data availability notification. + * + * Stream and channel locks are acquired during this call. + */ +typedef int (*on_sleep_cb)(struct lttng_consumer_stream *, + struct lttng_consumer_local_data *); + +/* + * Acquire the subbuffer at the current 'consumed' position. + * + * Stream and channel locks are acquired during this call. + */ +typedef int (*get_next_subbuffer_cb)(struct lttng_consumer_stream *, + struct stream_subbuffer *); + +/* + * Populate the stream_subbuffer's info member. The info to populate + * depends on the type (metadata/data) of the stream. + * + * Stream and channel locks are acquired during this call. + */ +typedef int (*extract_subbuffer_info_cb)( + struct lttng_consumer_stream *, struct stream_subbuffer *); + +/* + * Invoked after a subbuffer's info has been filled. + * + * Stream and channel locks are acquired during this call. + */ +typedef int (*pre_consume_subbuffer_cb)(struct lttng_consumer_stream *, + const struct stream_subbuffer *); + +/* + * Consume subbuffer contents. + * + * Stream and channel locks are acquired during this call. + */ +typedef ssize_t (*consume_subbuffer_cb)(struct lttng_consumer_local_data *, + struct lttng_consumer_stream *, + const struct stream_subbuffer *); + +/* + * Release the current subbuffer and advance the 'consumed' position by + * one subbuffer. + * + * Stream and channel locks are acquired during this call. + */ +typedef int (*put_next_subbuffer_cb)(struct lttng_consumer_stream *, + struct stream_subbuffer *); + +/* + * Invoked after consuming a subbuffer. + * + * Stream and channel locks are acquired during this call. + */ +typedef int (*post_consume_cb)(struct lttng_consumer_stream *, + const struct stream_subbuffer *, + struct lttng_consumer_local_data *); + +/* + * Send a live beacon if no data is available. + * + * Stream and channel locks are acquired during this call. + */ +typedef int (*send_live_beacon_cb)(struct lttng_consumer_stream *); + +/* + * Lock the stream and channel locks and any other stream-type specific + * lock that need to be acquired during the processing of an + * availability notification. + */ +typedef void (*lock_cb)(struct lttng_consumer_stream *); + +/* + * Unlock the stream and channel locks and any other stream-type specific + * lock before sleeping until the next availability notification. + * + * Stream and channel locks are acquired during this call. + */ +typedef void (*unlock_cb)(struct lttng_consumer_stream *); + +/* + * Invoked when a subbuffer's metadata version does not match the last + * known metadata version. + * + * Stream and channel locks are acquired during this call. + */ +typedef void (*reset_metadata_cb)(struct lttng_consumer_stream *); + /* * Internal representation of the streams, sessiond_key is used to identify * uniquely a stream. @@ -467,6 +605,24 @@ struct lttng_consumer_stream { * file before writing in it (regeneration). */ unsigned int reset_metadata_flag:1; + struct { + /* + * Invoked in the order of declaration. + * See callback type definitions. + */ + lock_cb lock; + on_wake_up_cb on_wake_up; + get_next_subbuffer_cb get_next_subbuffer; + extract_subbuffer_info_cb extract_subbuffer_info; + pre_consume_subbuffer_cb pre_consume_subbuffer; + reset_metadata_cb reset_metadata; + consume_subbuffer_cb consume_subbuffer; + put_next_subbuffer_cb put_next_subbuffer; + post_consume_cb post_consume; + send_live_beacon_cb send_live_beacon; + on_sleep_cb on_sleep; + unlock_cb unlock; + } read_subbuffer_ops; }; /* @@ -523,7 +679,8 @@ struct lttng_consumer_local_data { * Returns the number of bytes read, or negative error value. */ ssize_t (*on_buffer_ready)(struct lttng_consumer_stream *stream, - struct lttng_consumer_local_data *ctx); + struct lttng_consumer_local_data *ctx, + bool locked_by_caller); /* * function to call when we receive a new channel, it receives a * newly allocated channel, depending on the return code of this @@ -790,7 +947,8 @@ void consumer_steal_stream_key(int key, struct lttng_ht *ht); struct lttng_consumer_local_data *lttng_consumer_create( enum lttng_consumer_type type, ssize_t (*buffer_ready)(struct lttng_consumer_stream *stream, - struct lttng_consumer_local_data *ctx), + struct lttng_consumer_local_data *ctx, + bool locked_by_caller), int (*recv_channel)(struct lttng_consumer_channel *channel), int (*recv_stream)(struct lttng_consumer_stream *stream), int (*update_stream)(uint64_t sessiond_key, uint32_t state)); @@ -799,13 +957,11 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( struct lttng_consumer_local_data *ctx, struct lttng_consumer_stream *stream, const struct lttng_buffer_view *buffer, - unsigned long padding, - struct ctf_packet_index *index); + unsigned long padding); ssize_t lttng_consumer_on_read_subbuffer_splice( struct lttng_consumer_local_data *ctx, struct lttng_consumer_stream *stream, unsigned long len, - unsigned long padding, - struct ctf_packet_index *index); + unsigned long padding); int lttng_consumer_sample_snapshot_positions(struct lttng_consumer_stream *stream); int lttng_consumer_take_snapshot(struct lttng_consumer_stream *stream); int lttng_consumer_get_produced_snapshot(struct lttng_consumer_stream *stream, @@ -822,7 +978,8 @@ int lttng_consumer_recv_cmd(struct lttng_consumer_local_data *ctx, int sock, struct pollfd *consumer_sockpoll); ssize_t lttng_consumer_read_subbuffer(struct lttng_consumer_stream *stream, - struct lttng_consumer_local_data *ctx); + struct lttng_consumer_local_data *ctx, + bool locked_by_caller); int lttng_consumer_on_recv_stream(struct lttng_consumer_stream *stream); void consumer_add_relayd_socket(uint64_t net_seq_idx, int sock_type, struct lttng_consumer_local_data *ctx, int sock, diff --git a/src/common/kernel-consumer/kernel-consumer.c b/src/common/kernel-consumer/kernel-consumer.c index 4a2e4e07d..e06f3b3e0 100644 --- a/src/common/kernel-consumer/kernel-consumer.c +++ b/src/common/kernel-consumer/kernel-consumer.c @@ -7,8 +7,6 @@ * */ -#include "common/buffer-view.h" -#include #define _LGPL_SOURCE #include #include @@ -36,6 +34,9 @@ #include #include #include +#include +#include +#include #include "kernel-consumer.h" @@ -288,7 +289,7 @@ static int lttng_kconsumer_snapshot_channel( subbuf_addr, 0, padded_len); read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, stream, &subbuf_view, - padded_len - len, NULL); + padded_len - len); /* * We write the padded len in local tracefiles but the data len * when using a relay. Display the error but continue processing @@ -399,7 +400,7 @@ static int lttng_kconsumer_snapshot_metadata( do { health_code_update(); - ret_read = lttng_kconsumer_read_subbuffer(metadata_stream, ctx); + ret_read = lttng_consumer_read_subbuffer(metadata_stream, ctx, true); if (ret_read < 0) { if (ret_read != -EAGAIN) { ERR("Kernel snapshot reading metadata subbuffer (ret: %zd)", @@ -655,7 +656,7 @@ int lttng_kconsumer_recv_cmd(struct lttng_consumer_local_data *ctx, health_code_update(); pthread_mutex_lock(&channel->lock); - new_stream = consumer_allocate_stream( + new_stream = consumer_stream_create( channel, channel->key, fd, @@ -690,23 +691,6 @@ int lttng_kconsumer_recv_cmd(struct lttng_consumer_local_data *ctx, consumer_stream_update_channel_attributes(new_stream, channel); - switch (channel->output) { - case CONSUMER_CHANNEL_SPLICE: - new_stream->output = LTTNG_EVENT_SPLICE; - ret = utils_create_pipe(new_stream->splice_pipe); - if (ret < 0) { - pthread_mutex_unlock(&channel->lock); - goto error_add_stream_nosignal; - } - break; - case CONSUMER_CHANNEL_MMAP: - new_stream->output = LTTNG_EVENT_MMAP; - break; - default: - ERR("Stream output unknown %d", channel->output); - pthread_mutex_unlock(&channel->lock); - goto error_add_stream_nosignal; - } /* * We've just assigned the channel to the stream so increment the @@ -1358,93 +1342,6 @@ end: return ret; } -/* - * Populate index values of a kernel stream. Values are set in big endian order. - * - * Return 0 on success or else a negative value. - */ -static int get_index_values(struct ctf_packet_index *index, int infd) -{ - int ret; - uint64_t packet_size, content_size, timestamp_begin, timestamp_end, - events_discarded, stream_id, stream_instance_id, - packet_seq_num; - - ret = kernctl_get_timestamp_begin(infd, ×tamp_begin); - if (ret < 0) { - PERROR("kernctl_get_timestamp_begin"); - goto error; - } - - ret = kernctl_get_timestamp_end(infd, ×tamp_end); - if (ret < 0) { - PERROR("kernctl_get_timestamp_end"); - goto error; - } - - ret = kernctl_get_events_discarded(infd, &events_discarded); - if (ret < 0) { - PERROR("kernctl_get_events_discarded"); - goto error; - } - - ret = kernctl_get_content_size(infd, &content_size); - if (ret < 0) { - PERROR("kernctl_get_content_size"); - goto error; - } - - ret = kernctl_get_packet_size(infd, &packet_size); - if (ret < 0) { - PERROR("kernctl_get_packet_size"); - goto error; - } - - ret = kernctl_get_stream_id(infd, &stream_id); - if (ret < 0) { - PERROR("kernctl_get_stream_id"); - goto error; - } - - ret = kernctl_get_instance_id(infd, &stream_instance_id); - if (ret < 0) { - if (ret == -ENOTTY) { - /* Command not implemented by lttng-modules. */ - stream_instance_id = -1ULL; - } else { - PERROR("kernctl_get_instance_id"); - goto error; - } - } - - ret = kernctl_get_sequence_number(infd, &packet_seq_num); - if (ret < 0) { - if (ret == -ENOTTY) { - /* Command not implemented by lttng-modules. */ - packet_seq_num = -1ULL; - ret = 0; - } else { - PERROR("kernctl_get_sequence_number"); - goto error; - } - } - index->packet_seq_num = htobe64(index->packet_seq_num); - - *index = (typeof(*index)) { - .offset = index->offset, - .packet_size = htobe64(packet_size), - .content_size = htobe64(content_size), - .timestamp_begin = htobe64(timestamp_begin), - .timestamp_end = htobe64(timestamp_end), - .events_discarded = htobe64(events_discarded), - .stream_id = htobe64(stream_id), - .stream_instance_id = htobe64(stream_instance_id), - .packet_seq_num = htobe64(packet_seq_num), - }; - -error: - return ret; -} /* * Sync metadata meaning request them to the session daemon and snapshot to the * metadata thread can consumer them. @@ -1483,348 +1380,226 @@ end: } static -int update_stream_stats(struct lttng_consumer_stream *stream) +int extract_common_subbuffer_info(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuf) { int ret; - uint64_t seq, discarded; - - ret = kernctl_get_sequence_number(stream->wait_fd, &seq); - if (ret < 0) { - if (ret == -ENOTTY) { - /* Command not implemented by lttng-modules. */ - seq = -1ULL; - stream->sequence_number_unavailable = true; - } else { - PERROR("kernctl_get_sequence_number"); - goto end; - } - } - /* - * Start the sequence when we extract the first packet in case we don't - * start at 0 (for example if a consumer is not connected to the - * session immediately after the beginning). - */ - if (stream->last_sequence_number == -1ULL) { - stream->last_sequence_number = seq; - } else if (seq > stream->last_sequence_number) { - stream->chan->lost_packets += seq - - stream->last_sequence_number - 1; - } else { - /* seq <= last_sequence_number */ - ERR("Sequence number inconsistent : prev = %" PRIu64 - ", current = %" PRIu64, - stream->last_sequence_number, seq); - ret = -1; + ret = kernctl_get_subbuf_size( + stream->wait_fd, &subbuf->info.data.subbuf_size); + if (ret) { goto end; } - stream->last_sequence_number = seq; - ret = kernctl_get_events_discarded(stream->wait_fd, &discarded); - if (ret < 0) { - PERROR("kernctl_get_events_discarded"); + ret = kernctl_get_padded_subbuf_size( + stream->wait_fd, &subbuf->info.data.padded_subbuf_size); + if (ret) { goto end; } - if (discarded < stream->last_discarded_events) { - /* - * Overflow has occurred. We assume only one wrap-around - * has occurred. - */ - stream->chan->discarded_events += (1ULL << (CAA_BITS_PER_LONG - 1)) - - stream->last_discarded_events + discarded; - } else { - stream->chan->discarded_events += discarded - - stream->last_discarded_events; - } - stream->last_discarded_events = discarded; - ret = 0; end: return ret; } -/* - * Check if the local version of the metadata stream matches with the version - * of the metadata stream in the kernel. If it was updated, set the reset flag - * on the stream. - */ static -int metadata_stream_check_version(int infd, struct lttng_consumer_stream *stream) +int extract_metadata_subbuffer_info(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuf) { int ret; - uint64_t cur_version; - ret = kernctl_get_metadata_version(infd, &cur_version); - if (ret < 0) { - if (ret == -ENOTTY) { - /* - * LTTng-modules does not implement this - * command. - */ - ret = 0; - goto end; - } - ERR("Failed to get the metadata version"); + ret = extract_common_subbuffer_info(stream, subbuf); + if (ret) { goto end; } - if (stream->metadata_version == cur_version) { - ret = 0; + ret = kernctl_get_metadata_version( + stream->wait_fd, &subbuf->info.metadata.version); + if (ret) { goto end; } - DBG("New metadata version detected"); - stream->metadata_version = cur_version; - stream->reset_metadata_flag = 1; - ret = 0; - end: return ret; } -/* - * Consume data on a file descriptor and write it on a trace file. - * The stream and channel locks must be held by the caller. - */ -ssize_t lttng_kconsumer_read_subbuffer(struct lttng_consumer_stream *stream, - struct lttng_consumer_local_data *ctx) +static +int extract_data_subbuffer_info(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuf) { - unsigned long len, subbuf_size, padding; - int err, write_index = 1; - ssize_t ret = 0; - int infd = stream->wait_fd; - struct ctf_packet_index index = {}; - bool in_error_state = false; + int ret; - DBG("In read_subbuffer (infd : %d)", infd); + ret = extract_common_subbuffer_info(stream, subbuf); + if (ret) { + goto end; + } + ret = kernctl_get_packet_size( + stream->wait_fd, &subbuf->info.data.packet_size); + if (ret < 0) { + PERROR("Failed to get sub-buffer packet size"); + goto end; + } - /* Get the next subbuffer */ - err = kernctl_get_next_subbuf(infd); - if (err != 0) { - /* - * This is a debug message even for single-threaded consumer, - * because poll() have more relaxed criterions than get subbuf, - * so get_subbuf may fail for short race windows where poll() - * would issue wakeups. - */ - DBG("Reserving sub buffer failed (everything is normal, " - "it is due to concurrency)"); - ret = err; - goto error; + ret = kernctl_get_content_size( + stream->wait_fd, &subbuf->info.data.content_size); + if (ret < 0) { + PERROR("Failed to get sub-buffer content size"); + goto end; } - /* Get the full subbuffer size including padding */ - err = kernctl_get_padded_subbuf_size(infd, &len); - if (err != 0) { - PERROR("Getting sub-buffer len failed."); - err = kernctl_put_subbuf(infd); - if (err != 0) { - if (err == -EFAULT) { - PERROR("Error in unreserving sub buffer\n"); - } else if (err == -EIO) { - /* Should never happen with newer LTTng versions */ - PERROR("Reader has been pushed by the writer, last sub-buffer corrupted."); - } - ret = err; - goto error; - } - ret = err; - goto error; + ret = kernctl_get_timestamp_begin( + stream->wait_fd, &subbuf->info.data.timestamp_begin); + if (ret < 0) { + PERROR("Failed to get sub-buffer begin timestamp"); + goto end; } - if (!stream->metadata_flag) { - ret = get_index_values(&index, infd); - if (ret < 0) { - err = kernctl_put_subbuf(infd); - if (err != 0) { - if (err == -EFAULT) { - PERROR("Error in unreserving sub buffer\n"); - } else if (err == -EIO) { - /* Should never happen with newer LTTng versions */ - PERROR("Reader has been pushed by the writer, last sub-buffer corrupted."); - } - ret = err; - goto error; - } - goto error; - } - ret = update_stream_stats(stream); - if (ret < 0) { - err = kernctl_put_subbuf(infd); - if (err != 0) { - if (err == -EFAULT) { - PERROR("Error in unreserving sub buffer\n"); - } else if (err == -EIO) { - /* Should never happen with newer LTTng versions */ - PERROR("Reader has been pushed by the writer, last sub-buffer corrupted."); - } - ret = err; - goto error; - } - goto error; + ret = kernctl_get_timestamp_end( + stream->wait_fd, &subbuf->info.data.timestamp_end); + if (ret < 0) { + PERROR("Failed to get sub-buffer end timestamp"); + goto end; + } + + ret = kernctl_get_events_discarded( + stream->wait_fd, &subbuf->info.data.events_discarded); + if (ret) { + PERROR("Failed to get sub-buffer events discarded count"); + goto end; + } + + ret = kernctl_get_sequence_number(stream->wait_fd, + &subbuf->info.data.sequence_number.value); + if (ret) { + /* May not be supported by older LTTng-modules. */ + if (ret != -ENOTTY) { + PERROR("Failed to get sub-buffer sequence number"); + goto end; } } else { - write_index = 0; - ret = metadata_stream_check_version(infd, stream); - if (ret < 0) { - err = kernctl_put_subbuf(infd); - if (err != 0) { - if (err == -EFAULT) { - PERROR("Error in unreserving sub buffer\n"); - } else if (err == -EIO) { - /* Should never happen with newer LTTng versions */ - PERROR("Reader has been pushed by the writer, last sub-buffer corrupted."); - } - ret = err; - goto error; - } - goto error; - } + subbuf->info.data.sequence_number.is_set = true; } - switch (stream->chan->output) { - case CONSUMER_CHANNEL_SPLICE: - /* - * XXX: The lttng-modules splice "actor" does not handle copying - * partial pages hence only using the subbuffer size without the - * padding makes the splice fail. - */ - subbuf_size = len; - padding = 0; + ret = kernctl_get_stream_id( + stream->wait_fd, &subbuf->info.data.stream_id); + if (ret < 0) { + PERROR("Failed to get stream id"); + goto end; + } - /* splice the subbuffer to the tracefile */ - ret = lttng_consumer_on_read_subbuffer_splice(ctx, stream, subbuf_size, - padding, &index); - /* - * XXX: Splice does not support network streaming so the return value - * is simply checked against subbuf_size and not like the mmap() op. - */ - if (ret != subbuf_size) { - /* - * display the error but continue processing to try - * to release the subbuffer - */ - ERR("Error splicing to tracefile (ret: %zd != len: %lu)", - ret, subbuf_size); - write_index = 0; - } - break; - case CONSUMER_CHANNEL_MMAP: - { - const char *subbuf_addr; - struct lttng_buffer_view subbuf_view; - - /* Get subbuffer size without padding */ - err = kernctl_get_subbuf_size(infd, &subbuf_size); - if (err != 0) { - PERROR("Getting sub-buffer len failed."); - err = kernctl_put_subbuf(infd); - if (err != 0) { - if (err == -EFAULT) { - PERROR("Error in unreserving sub buffer\n"); - } else if (err == -EIO) { - /* Should never happen with newer LTTng versions */ - PERROR("Reader has been pushed by the writer, last sub-buffer corrupted."); - } - ret = err; - goto error; - } - ret = err; - goto error; + ret = kernctl_get_instance_id(stream->wait_fd, + &subbuf->info.data.stream_instance_id.value); + if (ret) { + /* May not be supported by older LTTng-modules. */ + if (ret != -ENOTTY) { + PERROR("Failed to get stream instance id"); + goto end; } + } else { + subbuf->info.data.stream_instance_id.is_set = true; + } +end: + return ret; +} - ret = get_current_subbuf_addr(stream, &subbuf_addr); - if (ret) { - goto error_put_subbuf; - } +static +int get_subbuffer_common(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuffer) +{ + int ret; - /* Make sure the tracer is not gone mad on us! */ - assert(len >= subbuf_size); + ret = kernctl_get_next_subbuf(stream->wait_fd); + if (ret) { + goto end; + } - padding = len - subbuf_size; + ret = stream->read_subbuffer_ops.extract_subbuffer_info( + stream, subbuffer); +end: + return ret; +} - subbuf_view = lttng_buffer_view_init(subbuf_addr, 0, len); +static +int get_next_subbuffer_splice(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuffer) +{ + int ret; - /* write the subbuffer to the tracefile */ - ret = lttng_consumer_on_read_subbuffer_mmap( - ctx, stream, &subbuf_view, padding, &index); - /* - * The mmap operation should write subbuf_size amount of data - * when network streaming or the full padding (len) size when we - * are _not_ streaming. - */ - if ((ret != subbuf_size && stream->net_seq_idx != (uint64_t) -1ULL) || - (ret != len && stream->net_seq_idx == (uint64_t) -1ULL)) { - /* - * Display the error but continue processing to try to release the - * subbuffer. This is a DBG statement since this is possible to - * happen without being a critical error. - */ - DBG("Error writing to tracefile " - "(ret: %zd != len: %lu != subbuf_size: %lu)", - ret, len, subbuf_size); - write_index = 0; - } - break; - } - default: - ERR("Unknown output method"); - ret = -EPERM; + ret = get_subbuffer_common(stream, subbuffer); + if (ret) { + goto end; } -error_put_subbuf: - err = kernctl_put_next_subbuf(infd); - if (err != 0) { - if (err == -EFAULT) { - PERROR("Error in unreserving sub buffer\n"); - } else if (err == -EIO) { - /* Should never happen with newer LTTng versions */ - PERROR("Reader has been pushed by the writer, last sub-buffer corrupted."); - } - ret = err; - goto error; - } else if (in_error_state) { - goto error; + + subbuffer->buffer.fd = stream->wait_fd; +end: + return ret; +} + +static +int get_next_subbuffer_mmap(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuffer) +{ + int ret; + const char *addr; + + ret = get_subbuffer_common(stream, subbuffer); + if (ret) { + goto end; } - /* Write index if needed. */ - if (!write_index) { + ret = get_current_subbuf_addr(stream, &addr); + if (ret) { goto end; } - if (stream->chan->live_timer_interval && !stream->metadata_flag) { - /* - * In live, block until all the metadata is sent. - */ - pthread_mutex_lock(&stream->metadata_timer_lock); - assert(!stream->missed_metadata_flush); - stream->waiting_on_metadata = true; - pthread_mutex_unlock(&stream->metadata_timer_lock); - - err = consumer_stream_sync_metadata(ctx, stream->session_id); - - pthread_mutex_lock(&stream->metadata_timer_lock); - stream->waiting_on_metadata = false; - if (stream->missed_metadata_flush) { - stream->missed_metadata_flush = false; - pthread_mutex_unlock(&stream->metadata_timer_lock); - (void) consumer_flush_kernel_index(stream); - } else { - pthread_mutex_unlock(&stream->metadata_timer_lock); - } - if (err < 0) { - goto error; + subbuffer->buffer.buffer = lttng_buffer_view_init( + addr, 0, subbuffer->info.data.padded_subbuf_size); +end: + return ret; +} + +static +int put_next_subbuffer(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuffer) +{ + const int ret = kernctl_put_next_subbuf(stream->wait_fd); + + if (ret) { + if (ret == -EFAULT) { + PERROR("Error in unreserving sub buffer"); + } else if (ret == -EIO) { + /* Should never happen with newer LTTng versions */ + PERROR("Reader has been pushed by the writer, last sub-buffer corrupted"); } } - err = consumer_stream_write_index(stream, &index); - if (err < 0) { - goto error; + return ret; +} + +static void lttng_kconsumer_set_stream_ops( + struct lttng_consumer_stream *stream) +{ + if (stream->chan->output == CONSUMER_CHANNEL_MMAP) { + stream->read_subbuffer_ops.get_next_subbuffer = + get_next_subbuffer_mmap; + } else { + stream->read_subbuffer_ops.get_next_subbuffer = + get_next_subbuffer_splice; } -end: -error: - return ret; + if (stream->metadata_flag) { + stream->read_subbuffer_ops.extract_subbuffer_info = + extract_metadata_subbuffer_info; + } else { + stream->read_subbuffer_ops.extract_subbuffer_info = + extract_data_subbuffer_info; + if (stream->chan->is_live) { + stream->read_subbuffer_ops.send_live_beacon = + consumer_flush_kernel_index; + } + } + + stream->read_subbuffer_ops.put_next_subbuffer = put_next_subbuffer; } int lttng_kconsumer_on_recv_stream(struct lttng_consumer_stream *stream) @@ -1865,6 +1640,8 @@ int lttng_kconsumer_on_recv_stream(struct lttng_consumer_stream *stream) } } + lttng_kconsumer_set_stream_ops(stream); + /* we return 0 to let the library handle the FD internally */ return 0; diff --git a/src/common/kernel-consumer/kernel-consumer.h b/src/common/kernel-consumer/kernel-consumer.h index b397b9cd3..48c787a46 100644 --- a/src/common/kernel-consumer/kernel-consumer.h +++ b/src/common/kernel-consumer/kernel-consumer.h @@ -22,8 +22,6 @@ int lttng_kconsumer_get_consumed_snapshot(struct lttng_consumer_stream *stream, unsigned long *pos); int lttng_kconsumer_recv_cmd(struct lttng_consumer_local_data *ctx, int sock, struct pollfd *consumer_sockpoll); -ssize_t lttng_kconsumer_read_subbuffer(struct lttng_consumer_stream *stream, - struct lttng_consumer_local_data *ctx); int lttng_kconsumer_on_recv_stream(struct lttng_consumer_stream *stream); int lttng_kconsumer_data_pending(struct lttng_consumer_stream *stream); int lttng_kconsumer_sync_metadata(struct lttng_consumer_stream *metadata); diff --git a/src/common/ust-consumer/ust-consumer.c b/src/common/ust-consumer/ust-consumer.c index 2f09eac80..1af9840cd 100644 --- a/src/common/ust-consumer/ust-consumer.c +++ b/src/common/ust-consumer/ust-consumer.c @@ -7,7 +7,6 @@ * */ -#include #define _LGPL_SOURCE #include #include @@ -24,6 +23,7 @@ #include #include #include +#include #include #include @@ -36,6 +36,7 @@ #include #include #include +#include #include "ust-consumer.h" @@ -127,7 +128,7 @@ static struct lttng_consumer_stream *allocate_stream(int cpu, int key, assert(channel); assert(ctx); - stream = consumer_allocate_stream( + stream = consumer_stream_create( channel, channel->key, key, @@ -1040,7 +1041,7 @@ static int snapshot_metadata(struct lttng_consumer_channel *metadata_channel, do { health_code_update(); - ret = lttng_consumer_read_subbuffer(metadata_stream, ctx); + ret = lttng_consumer_read_subbuffer(metadata_stream, ctx, true); if (ret < 0) { goto error_stream; } @@ -1230,8 +1231,7 @@ static int snapshot_channel(struct lttng_consumer_channel *channel, subbuf_view = lttng_buffer_view_init( subbuf_addr, 0, padded_len); read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, - stream, &subbuf_view, padded_len - len, - NULL); + stream, &subbuf_view, padded_len - len); if (use_relayd) { if (read_len != len) { ret = -EPERM; @@ -2428,115 +2428,16 @@ int lttng_ustconsumer_close_wakeup_fd(struct lttng_consumer_stream *stream) return ustctl_stream_close_wakeup_fd(stream->ustream); } -/* - * Populate index values of a UST stream. Values are set in big endian order. - * - * Return 0 on success or else a negative value. - */ -static int get_index_values(struct ctf_packet_index *index, - struct ustctl_consumer_stream *ustream) -{ - int ret; - uint64_t packet_size, content_size, timestamp_begin, timestamp_end, - events_discarded, stream_id, stream_instance_id, - packet_seq_num; - - ret = ustctl_get_timestamp_begin(ustream, ×tamp_begin); - if (ret < 0) { - PERROR("ustctl_get_timestamp_begin"); - goto error; - } - - ret = ustctl_get_timestamp_end(ustream, ×tamp_end); - if (ret < 0) { - PERROR("ustctl_get_timestamp_end"); - goto error; - } - - ret = ustctl_get_events_discarded(ustream, &events_discarded); - if (ret < 0) { - PERROR("ustctl_get_events_discarded"); - goto error; - } - - ret = ustctl_get_content_size(ustream, &content_size); - if (ret < 0) { - PERROR("ustctl_get_content_size"); - goto error; - } - - ret = ustctl_get_packet_size(ustream, &packet_size); - if (ret < 0) { - PERROR("ustctl_get_packet_size"); - goto error; - } - - ret = ustctl_get_stream_id(ustream, &stream_id); - if (ret < 0) { - PERROR("ustctl_get_stream_id"); - goto error; - } - - ret = ustctl_get_instance_id(ustream, &stream_instance_id); - if (ret < 0) { - PERROR("ustctl_get_instance_id"); - goto error; - } - - ret = ustctl_get_sequence_number(ustream, &packet_seq_num); - if (ret < 0) { - PERROR("ustctl_get_sequence_number"); - goto error; - } - - *index = (typeof(*index)) { - .offset = index->offset, - .packet_size = htobe64(packet_size), - .content_size = htobe64(content_size), - .timestamp_begin = htobe64(timestamp_begin), - .timestamp_end = htobe64(timestamp_end), - .events_discarded = htobe64(events_discarded), - .stream_id = htobe64(stream_id), - .stream_instance_id = htobe64(stream_instance_id), - .packet_seq_num = htobe64(packet_seq_num), - }; - -error: - return ret; -} - static -void metadata_stream_reset_cache(struct lttng_consumer_stream *stream, - struct consumer_metadata_cache *cache) +void metadata_stream_reset_cache(struct lttng_consumer_stream *stream) { - DBG("Metadata stream update to version %" PRIu64, - cache->version); + DBG("Reset metadata cache of session %" PRIu64, + stream->chan->session_id); stream->ust_metadata_pushed = 0; - stream->metadata_version = cache->version; + stream->metadata_version = stream->chan->metadata_cache->version; stream->reset_metadata_flag = 1; } -/* - * Check if the version of the metadata stream and metadata cache match. - * If the cache got updated, reset the metadata stream. - * The stream lock and metadata cache lock MUST be held. - * Return 0 on success, a negative value on error. - */ -static -int metadata_stream_check_version(struct lttng_consumer_stream *stream) -{ - int ret = 0; - struct consumer_metadata_cache *cache = stream->chan->metadata_cache; - - if (cache->version == stream->metadata_version) { - goto end; - } - metadata_stream_reset_cache(stream, cache); - -end: - return ret; -} - /* * Write up to one packet from the metadata cache to the channel. * @@ -2550,10 +2451,6 @@ int commit_one_metadata_packet(struct lttng_consumer_stream *stream) int ret; pthread_mutex_lock(&stream->chan->metadata_cache->lock); - ret = metadata_stream_check_version(stream); - if (ret < 0) { - goto end; - } if (stream->chan->metadata_cache->max_offset == stream->ust_metadata_pushed) { ret = 0; @@ -2723,261 +2620,264 @@ end: return ret; } -static -int update_stream_stats(struct lttng_consumer_stream *stream) +static int consumer_stream_ust_on_wake_up(struct lttng_consumer_stream *stream) { - int ret; - uint64_t seq, discarded; + int ret = 0; - ret = ustctl_get_sequence_number(stream->ustream, &seq); - if (ret < 0) { - PERROR("ustctl_get_sequence_number"); - goto end; - } /* - * Start the sequence when we extract the first packet in case we don't - * start at 0 (for example if a consumer is not connected to the - * session immediately after the beginning). + * We can consume the 1 byte written into the wait_fd by + * UST. Don't trigger error if we cannot read this one byte + * (read returns 0), or if the error is EAGAIN or EWOULDBLOCK. + * + * This is only done when the stream is monitored by a thread, + * before the flush is done after a hangup and if the stream + * is not flagged with data since there might be nothing to + * consume in the wait fd but still have data available + * flagged by the consumer wake up pipe. */ - if (stream->last_sequence_number == -1ULL) { - stream->last_sequence_number = seq; - } else if (seq > stream->last_sequence_number) { - stream->chan->lost_packets += seq - - stream->last_sequence_number - 1; - } else { - /* seq <= last_sequence_number */ - ERR("Sequence number inconsistent : prev = %" PRIu64 - ", current = %" PRIu64, - stream->last_sequence_number, seq); - ret = -1; - goto end; + if (stream->monitor && !stream->hangup_flush_done && !stream->has_data) { + char dummy; + ssize_t readlen; + + readlen = lttng_read(stream->wait_fd, &dummy, 1); + if (readlen < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + ret = readlen; + } } - stream->last_sequence_number = seq; - ret = ustctl_get_events_discarded(stream->ustream, &discarded); - if (ret < 0) { - PERROR("kernctl_get_events_discarded"); + return ret; +} + +static int extract_common_subbuffer_info(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuf) +{ + int ret; + + ret = ustctl_get_subbuf_size( + stream->ustream, &subbuf->info.data.subbuf_size); + if (ret) { goto end; } - if (discarded < stream->last_discarded_events) { - /* - * Overflow has occurred. We assume only one wrap-around - * has occurred. - */ - stream->chan->discarded_events += - (1ULL << (CAA_BITS_PER_LONG - 1)) - - stream->last_discarded_events + discarded; - } else { - stream->chan->discarded_events += discarded - - stream->last_discarded_events; + + ret = ustctl_get_padded_subbuf_size( + stream->ustream, &subbuf->info.data.padded_subbuf_size); + if (ret) { + goto end; } - stream->last_discarded_events = discarded; - ret = 0; end: return ret; } -/* - * Read subbuffer from the given stream. - * - * Stream and channel locks MUST be acquired by the caller. - * - * Return 0 on success else a negative value. - */ -int lttng_ustconsumer_read_subbuffer(struct lttng_consumer_stream *stream, - struct lttng_consumer_local_data *ctx) +static int extract_metadata_subbuffer_info(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuf) { - unsigned long len, subbuf_size, padding; - int err, write_index = 1; - long ret = 0; - struct ustctl_consumer_stream *ustream; - struct ctf_packet_index index; - const char *subbuf_addr; - struct lttng_buffer_view subbuf_view; + int ret; - assert(stream); - assert(stream->ustream); - assert(ctx); + ret = extract_common_subbuffer_info(stream, subbuf); + if (ret) { + goto end; + } - DBG("In UST read_subbuffer (wait_fd: %d, name: %s)", stream->wait_fd, - stream->name); + subbuf->info.metadata.version = stream->chan->metadata_cache->version; - /* Ease our life for what's next. */ - ustream = stream->ustream; +end: + return ret; +} - /* - * We can consume the 1 byte written into the wait_fd by UST. Don't trigger - * error if we cannot read this one byte (read returns 0), or if the error - * is EAGAIN or EWOULDBLOCK. - * - * This is only done when the stream is monitored by a thread, before the - * flush is done after a hangup and if the stream is not flagged with data - * since there might be nothing to consume in the wait fd but still have - * data available flagged by the consumer wake up pipe. - */ - if (stream->monitor && !stream->hangup_flush_done && !stream->has_data) { - char dummy; - ssize_t readlen; +static int extract_data_subbuffer_info(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuf) +{ + int ret; - readlen = lttng_read(stream->wait_fd, &dummy, 1); - if (readlen < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { - ret = readlen; - goto error; - } + ret = extract_common_subbuffer_info(stream, subbuf); + if (ret) { + goto end; } -retry: - /* Get the next subbuffer */ - err = ustctl_get_next_subbuf(ustream); - if (err != 0) { - /* - * Populate metadata info if the existing info has - * already been read. - */ - if (stream->metadata_flag) { - ret = commit_one_metadata_packet(stream); - if (ret <= 0) { - goto error; - } - goto retry; - } + ret = ustctl_get_packet_size( + stream->ustream, &subbuf->info.data.packet_size); + if (ret < 0) { + PERROR("Failed to get sub-buffer packet size"); + goto end; + } - ret = err; /* ustctl_get_next_subbuf returns negative, caller expect positive. */ - /* - * This is a debug message even for single-threaded consumer, - * because poll() have more relaxed criterions than get subbuf, - * so get_subbuf may fail for short race windows where poll() - * would issue wakeups. - */ - DBG("Reserving sub buffer failed (everything is normal, " - "it is due to concurrency) [ret: %d]", err); - goto error; + ret = ustctl_get_content_size( + stream->ustream, &subbuf->info.data.content_size); + if (ret < 0) { + PERROR("Failed to get sub-buffer content size"); + goto end; } - assert(stream->chan->output == CONSUMER_CHANNEL_MMAP); - if (!stream->metadata_flag) { - index.offset = htobe64(stream->out_fd_offset); - ret = get_index_values(&index, ustream); - if (ret < 0) { - err = ustctl_put_subbuf(ustream); - assert(err == 0); - goto error; - } + ret = ustctl_get_timestamp_begin( + stream->ustream, &subbuf->info.data.timestamp_begin); + if (ret < 0) { + PERROR("Failed to get sub-buffer begin timestamp"); + goto end; + } - /* Update the stream's sequence and discarded events count. */ - ret = update_stream_stats(stream); - if (ret < 0) { - PERROR("kernctl_get_events_discarded"); - err = ustctl_put_subbuf(ustream); - assert(err == 0); - goto error; + ret = ustctl_get_timestamp_end( + stream->ustream, &subbuf->info.data.timestamp_end); + if (ret < 0) { + PERROR("Failed to get sub-buffer end timestamp"); + goto end; + } + + ret = ustctl_get_events_discarded( + stream->ustream, &subbuf->info.data.events_discarded); + if (ret) { + PERROR("Failed to get sub-buffer events discarded count"); + goto end; + } + + ret = ustctl_get_sequence_number(stream->ustream, + &subbuf->info.data.sequence_number.value); + if (ret) { + /* May not be supported by older LTTng-modules. */ + if (ret != -ENOTTY) { + PERROR("Failed to get sub-buffer sequence number"); + goto end; } } else { - write_index = 0; + subbuf->info.data.sequence_number.is_set = true; } - /* Get the full padded subbuffer size */ - err = ustctl_get_padded_subbuf_size(ustream, &len); - assert(err == 0); + ret = ustctl_get_stream_id( + stream->ustream, &subbuf->info.data.stream_id); + if (ret < 0) { + PERROR("Failed to get stream id"); + goto end; + } - /* Get subbuffer data size (without padding) */ - err = ustctl_get_subbuf_size(ustream, &subbuf_size); - assert(err == 0); + ret = ustctl_get_instance_id(stream->ustream, + &subbuf->info.data.stream_instance_id.value); + if (ret) { + /* May not be supported by older LTTng-modules. */ + if (ret != -ENOTTY) { + PERROR("Failed to get stream instance id"); + goto end; + } + } else { + subbuf->info.data.stream_instance_id.is_set = true; + } +end: + return ret; +} - /* Make sure we don't get a subbuffer size bigger than the padded */ - assert(len >= subbuf_size); +static int get_next_subbuffer_common(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuffer) +{ + int ret; + const char *addr; - padding = len - subbuf_size; + ret = stream->read_subbuffer_ops.extract_subbuffer_info( + stream, subbuffer); + if (ret) { + goto end; + } - ret = get_current_subbuf_addr(stream, &subbuf_addr); + ret = get_current_subbuf_addr(stream, &addr); if (ret) { - write_index = 0; - goto error_put_subbuf; + goto end; } - subbuf_view = lttng_buffer_view_init(subbuf_addr, 0, len); + subbuffer->buffer.buffer = lttng_buffer_view_init( + addr, 0, subbuffer->info.data.padded_subbuf_size); + assert(subbuffer->buffer.buffer.data != NULL); +end: + return ret; +} - /* write the subbuffer to the tracefile */ - ret = lttng_consumer_on_read_subbuffer_mmap( - ctx, stream, &subbuf_view, padding, &index); - /* - * The mmap operation should write subbuf_size amount of data when - * network streaming or the full padding (len) size when we are _not_ - * streaming. - */ - if ((ret != subbuf_size && stream->net_seq_idx != (uint64_t) -1ULL) || - (ret != len && stream->net_seq_idx == (uint64_t) -1ULL)) { - /* - * Display the error but continue processing to try to release the - * subbuffer. This is a DBG statement since any unexpected kill or - * signal, the application gets unregistered, relayd gets closed or - * anything that affects the buffer lifetime will trigger this error. - * So, for the sake of the user, don't print this error since it can - * happen and it is OK with the code flow. - */ - DBG("Error writing to tracefile " - "(ret: %ld != len: %lu != subbuf_size: %lu)", - ret, len, subbuf_size); - write_index = 0; - } -error_put_subbuf: - err = ustctl_put_next_subbuf(ustream); - assert(err == 0); +static int get_next_subbuffer(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuffer) +{ + int ret; - /* - * This will consumer the byte on the wait_fd if and only if there is not - * next subbuffer to be acquired. - */ - if (!stream->metadata_flag) { - ret = notify_if_more_data(stream, ctx); - if (ret < 0) { - goto error; - } + ret = ustctl_get_next_subbuf(stream->ustream); + if (ret) { + goto end; } - /* Write index if needed. */ - if (!write_index) { + ret = get_next_subbuffer_common(stream, subbuffer); + if (ret) { goto end; } +end: + return ret; +} - if (stream->chan->live_timer_interval && !stream->metadata_flag) { - /* - * In live, block until all the metadata is sent. - */ - pthread_mutex_lock(&stream->metadata_timer_lock); - assert(!stream->missed_metadata_flush); - stream->waiting_on_metadata = true; - pthread_mutex_unlock(&stream->metadata_timer_lock); - - err = consumer_stream_sync_metadata(ctx, stream->session_id); - - pthread_mutex_lock(&stream->metadata_timer_lock); - stream->waiting_on_metadata = false; - if (stream->missed_metadata_flush) { - stream->missed_metadata_flush = false; - pthread_mutex_unlock(&stream->metadata_timer_lock); - (void) consumer_flush_ust_index(stream); - } else { - pthread_mutex_unlock(&stream->metadata_timer_lock); +static int get_next_subbuffer_metadata(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuffer) +{ + int ret; + + ret = ustctl_get_next_subbuf(stream->ustream); + if (ret) { + ret = commit_one_metadata_packet(stream); + if (ret < 0) { + goto end; + } else if (ret == 0) { + /* Not an error, the cache is empty. */ + ret = -ENODATA; + goto end; } - if (err < 0) { - goto error; + ret = ustctl_get_next_subbuf(stream->ustream); + if (ret) { + goto end; } } - assert(!stream->metadata_flag); - err = consumer_stream_write_index(stream, &index); - if (err < 0) { - goto error; + ret = get_next_subbuffer_common(stream, subbuffer); + if (ret) { + goto end; } - end: -error: return ret; } +static int put_next_subbuffer(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuffer) +{ + const int ret = ustctl_put_next_subbuf(stream->ustream); + + assert(ret == 0); + return ret; +} + +static int signal_metadata(struct lttng_consumer_stream *stream, + struct lttng_consumer_local_data *ctx) +{ + return pthread_cond_broadcast(&stream->metadata_rdv) ? -errno : 0; +} + +static void lttng_ustconsumer_set_stream_ops( + struct lttng_consumer_stream *stream) +{ + stream->read_subbuffer_ops.on_wake_up = consumer_stream_ust_on_wake_up; + if (stream->metadata_flag) { + stream->read_subbuffer_ops.get_next_subbuffer = + get_next_subbuffer_metadata; + stream->read_subbuffer_ops.extract_subbuffer_info = + extract_metadata_subbuffer_info; + stream->read_subbuffer_ops.reset_metadata = + metadata_stream_reset_cache; + stream->read_subbuffer_ops.on_sleep = signal_metadata; + } else { + stream->read_subbuffer_ops.get_next_subbuffer = + get_next_subbuffer; + stream->read_subbuffer_ops.extract_subbuffer_info = + extract_data_subbuffer_info; + stream->read_subbuffer_ops.on_sleep = notify_if_more_data; + if (stream->chan->is_live) { + stream->read_subbuffer_ops.send_live_beacon = + consumer_flush_ust_index; + } + } + + stream->read_subbuffer_ops.put_next_subbuffer = put_next_subbuffer; +} + /* * Called when a stream is created. * @@ -3000,6 +2900,8 @@ int lttng_ustconsumer_on_recv_stream(struct lttng_consumer_stream *stream) goto error; } } + + lttng_ustconsumer_set_stream_ops(stream); ret = 0; error: -- 2.34.1 From f5ba75b4f0c0b44092c76bc931b25b24a2e62718 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Thu, 14 May 2020 14:24:17 -0400 Subject: [PATCH 14/16] Fix: consumerd: live client receives incomplete metadata MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Observed issue ============== Babeltrace 1.5.x and Babeltrace 2.x can both report errors (albeit differently) when using the "lttng-live" protocol that imply that the metadata they received is incomplete. For instance, babeltrace 1.5.3 reports the following error: ``` [error] Error creating AST [error] [Context] Cannot open_mmap_trace of format ctf. [error] Error adding trace [warning] [Context] Cannot open_trace of format lttng-live at path net://localhost:xxxx/host/session/live_session. [warning] [Context] cannot open trace "net://localhost:xxxx/host/session/live_session" for reading. [error] opening trace "net://localhost:xxxx/host/session/live_session" for reading. [error] none of the specified trace paths could be opened. ``` While debugging both viewers, I noticed that both were attempting to receive the available metadata before consuming the "data" streams' content. Typically, the following exchange between the relay daemon and the lttng-live client occurs when the problem is observed: bt lttng-live: emits LTTNG_VIEWER_GET_METADATA command relayd: returns LTTNG_VIEWER_METADATA_OK, len = 4096 (default packet size) bt lttng-live: consume 4096 bytes of metadata emits LTTNG_VIEWER_GET_METADATA command relayd: returns LTTNG_VIEWER_NO_NEW_METADATA When the lttng-live client receives the LTTNG_VIEWER_NO_NEW_METADATA status code, it attempts to parse all the metadata it has received since the last LTTNG_VIEWER_NO_NEW_METADATA reply. In effect, it is expected that this forms a logical unit of metadata that is parseable on its own. If this is the first time metadata is received for that trace, the metadata is expected to contain a trace declaration, packet header declaration, etc. If metadata was already received, it is expected that the newly parsed declarations can be "appended" to the existing trace schema. It appears that the relay daemon sends the LTTNG_VIEWER_NO_NEW_METADATA while the metadata it has sent up to that point is not parseable on its own. The live protocol description does not require or imply that a viewer should attempt to parse metadata packets until it hopefully succeeds at some point. Anyhow: 1) This would make it impossible for a live viewer to correctly handle a corrupted metadata stream beyond retrying forever, 2) This behaviour is not implemented by the two reference implementations of the protocol. Cause ===== The relay daemon provides a guarantee that it will send any available metadata before allowing a data stream packet to be served to the client. In other words, a client requesting a data packet will receive the LTTNG_VIEWER_FLAG_NEW_METADATA status code (and no data) if it attempts to get a data stream packet while the relay daemon has metadata already available. This guarantee is properly enforced as far as I can tell. However, looking at the consumer daemon implementation, it appears that metadata packets are sent as soon as they are available. A metadata packet is not guaranteed to be parseable on its own. For instance, it can end in the middle the an event declaration. Hence, this hints at a race involving the tracer, the consumer daemon, the relay daemon, and the lttng-live client. Consider the following scenario: - Metadata packets (sub-buffers) are configured to be 4kB in size, - a large number of kernel events are enabled (e.g. --kernel --all), - the network connection between the consumer and relay daemons is slow 1) The kernel tracer will produce enough TSDL metadata to fill the first sub-buffer of the "metadata" ring-buffer and signal the consumer daemon that a buffer is ready. The tracer then starts writing the remaining data in the following available sub-buffers. 2) The consumer daemon metadata thread is woken up and consumes the first metadata sub-buffer and sends it to the relay daemon. 3) A live client establishes an lttng-live connection to the relay daemon and attempts to consume the available metadata. It receives the first packet and, since the relay daemon doesn't know about any follow-up metadata, receives LTTNG_VIEWER_NO_NEW_METADATA on the next attempt. 4) Having received LTTNG_VIEWER_NO_NEW_METADATA, the lttng-live client attempts to parse the metadata it has received and fails. This scenario is easy to reproduce by inserting a "sleep(1)" at src/bin/lttng-relayd/main.c:1978 (as of this revision). This simulates a relay daemon that would be slow to receive/process metadata packets from the consumer daemon. This problem similarly applies to the user space tracer. Solution ======== Having no knowledge of TSDL, the relay daemon can't "bundle" packets of metadata until they form a "parseable unit" to send to the consumer daemon. To provide the parseability guarantee expected by the viewers, and by the relay daemon implicitly, we need to ensure that the consumer daemons only send "parseable units" of metadata to the relay daemon. Unfortunately, the consumer daemons do not know how to parse TSDL either. In fact, only the metadata producers are able to provide the boundaries of the "parseable units" of metadata. The general idea of the fix is to accumulate metadata up to a point where a "parseable unit" boundary has been identified and send that content in one request to the relay daemon. Note that the solution described here only concerns the live mode. In other cases, the mechanisms described are simply bypassed. A "metadata bucket" is added to lttng_consumer_stream when it is created from a live channel. This bucket is filled until the consumption position reaches the "parseable unit" end position. A refresher about the handling of metadata in live mode ------------------------------------------------------- Three "events" are of interest here and can cause metadata to be consumed more or less indirectly: 1) A metadata packet is closed, causing the metadata thread to wake up 2) The live timer expires 3) A data sub-buffer is closed, causing the data thread to wake-up 1) The first case is simple and happens regardless of whether or not the tracing session is in live mode or not. Metadata is always consumed by the metadata thread in the same way. However, this scenario can be "caused" by (2) and (3). See [1]. A sub-buffer is "acquired" from the metadata ring-buffer and sent to the relayd daemon as the payload of a "RELAYD_SEND_METADATA" command. 2) When the live timer expires [2], the 'check_stream' function is called on all data streams of the session. As its name clearly implies, this function is responsible for flushing all streams or sending a "live beacon" (called an "empty index" in the code) if there is no data to flush. Any flushed data will result in (3). 3) When a data sub-buffer is ready to be consumed, [1] is invoked by the data thread. This function acquires a sub-buffer and sends it to the relay daemon through the data connection. Then, an important synchronization step takes place. The index of the newly-sent packet will be sent through the control connection. The relay daemon waits for both the data packet and its matching index before making the new packet visible to live viewers. Since a data packet could contain data that requires "newer" metadata to be decoded, the data thread flushes the metadata stream and enters a "waiting" phase to pause until all metadata present in the metadata ring buffer has been consumed [3]. At the end of this waiting phase, the data thread sends the data packet's index to the relay daemon, allowing the relayd to make it visible to its live clients. How to identify a "parseable unit" boundary? -------------------------------------------- In the case of the kernel domain, the kernel tracer produces the actual TSDL descriptions directly. The TSDL metadata is serialized to a metadata cache and is flushed "just in time" to the metadata ring-buffer when a "get next" operation is performed. There is no way, from user space, to query whether or not the metadata cache of the kernel tracer is empty. Hence, a new RING_RING_BUFFER_GET_NEXT_SUBBUF_METADATA_CHECK command was added to query whether or not the kernel tracer's metadata cache is empty when acquiring a sub-buffer. This allows the consumer daemon to identify a "coherent" position in the metadata stream that is safe to use as a "parseable unit" boundary. As for the user space domain, since the session daemon is responsible for generating the TSDL representation of the metadata, there is no need to change LTTng-ust APIs. The session daemon generates coherent units of metadata and adds them to its "registry" at once (protected by the registry's lock). It then flushes the contents to the consumer daemon and waits for that data to be consumed before proceeding further. On the consumer daemon side, the metadata cache is filled with the newly-produced contents. This is done atomically with respect to accesses to the metadata cache as all accesses happen through a dedicated metadata cache lock. When the consumer's metadata polling thread is woken-up, it will attempt to acquire (`get_next`) a sub-buffer from the metadata stream ring-buffer. If it fails, it will flush a sub-buffer's worth of metadata to the ring-buffer and attempt to acquire a sub-buffer again. At this point, it is possible to determine if that sub-buffer is the last one of a parseable metadata unit: the cache must be empty and the ring-buffer must be empty following the consumption of this sub-buffer. When those conditions are met, the resulting metadata `stream_subbuffer` is tagged as being `coherent`. Metadata bucket --------------- A helper interface, metadata_bucket, is introduced as part of this fix. A metadata_bucket is `fill`ed with `stream_subbuffer`s, and is eventually `flushed` when it is filled by a `coherent` sub-buffer. As older versions of LTTng-modules must remain supported, this new helper is not used when the RING_RING_BUFFER_GET_NEXT_SUBBUF_METADATA_CHECK operation is not available. When the operation is available, the metadata stream's bucketization is enabled, causing a bucket to be created and the `consume` callback to be swapped. The `consume` callback of the metadata streams is replaced by a new implementation when the metadata bucketization is activated on the stream. This implementation returns the padded size of the consumed sub-buffer when they could be added to the bucket. When the bucket is flushed, the regular `mmap`-based consumption function is called with the bucket's contents. Known drawbacks =============== This implementation causes the consumer daemon to buffer the whole initial unit of metadata before sending it. In practice, this is not expected to be a problem since the largest metadata files we have seen in real use are a couple of megabytes wide. Beyond the (temporary) memory use, this causes the metadata thread to block while this potentially large chunk of metadata is sent (rather than blocking while sending 4kb at a time). The second point is just a consequence of existing shortcomings of the consumerd; slow IO should not affect other unrelated streams. The fundamental problem is that blocking IO is used and we should switch to non-blocking communication if this is a problem (as is done in the relay daemon). The first point is more problematic given the existing tracer APIs. If the tracer could provide the boundary of a "parseable unit" of metadata, we could send the header of the RELAYD_SEND_METADATA command with that size and send the various metadata packets as they are made available. This would make no difference to the relay daemon as it is not blocking on that socket and will not make the metadata size change visible to the "live server" until it has all been received. This size can't be determined right now since it could exceed the total size of the "metadata" ring buffer. In other words, we can't wait for the production of metadata to complete before starting to consume. Finally, while implementing this fix, I also realized that the computation of the rotation position of the metadata streams is erroneous. The rotation code makes use of the ring-buffer's positions to determine the rotation position. However, since both user space and kernel domains make use of a "cache" behind the ring-buffer, that cached content must be taken into account when computing the metadata stream's rotation position. References ========== [1] https://github.com/lttng/lttng-tools/blob/d5ccf8fe0/src/common/consumer/consumer.c#L3433 [2] https://github.com/lttng/lttng-tools/blob/d5ccf8fe0/src/common/consumer/consumer-timer.c#L312 [3] https://github.com/lttng/lttng-tools/blob/d5ccf8fe0/src/common/consumer/consumer-stream.c#L492 Signed-off-by: Jérémie Galarneau Change-Id: I40ee07e5c344c72d9aae2b9b15dc36c00b21e5fa --- src/common/consumer/Makefile.am | 3 +- src/common/consumer/consumer-stream.c | 64 +++++++- src/common/consumer/consumer-stream.h | 9 ++ src/common/consumer/consumer.c | 1 - src/common/consumer/consumer.h | 10 +- src/common/consumer/metadata-bucket.c | 150 +++++++++++++++++++ src/common/consumer/metadata-bucket.h | 34 +++++ src/common/kernel-consumer/kernel-consumer.c | 100 +++++++++++-- src/common/ust-consumer/ust-consumer.c | 106 ++++++++++--- 9 files changed, 446 insertions(+), 31 deletions(-) create mode 100644 src/common/consumer/metadata-bucket.c create mode 100644 src/common/consumer/metadata-bucket.h diff --git a/src/common/consumer/Makefile.am b/src/common/consumer/Makefile.am index 296301e6f..55e47b210 100644 --- a/src/common/consumer/Makefile.am +++ b/src/common/consumer/Makefile.am @@ -6,7 +6,8 @@ noinst_HEADERS = consumer-metadata-cache.h consumer-timer.h \ consumer-testpoint.h libconsumer_la_SOURCES = consumer.c consumer.h consumer-metadata-cache.c \ - consumer-timer.c consumer-stream.c consumer-stream.h + consumer-timer.c consumer-stream.c consumer-stream.h \ + metadata-bucket.c metadata-bucket.h libconsumer_la_LIBADD = \ $(top_builddir)/src/common/sessiond-comm/libsessiond-comm.la \ diff --git a/src/common/consumer/consumer-stream.c b/src/common/consumer/consumer-stream.c index 5dc380e5e..deebb58fe 100644 --- a/src/common/consumer/consumer-stream.c +++ b/src/common/consumer/consumer-stream.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "consumer-stream.h" @@ -153,7 +154,7 @@ static ssize_t consumer_stream_consume_mmap( subbuffer->info.data.subbuf_size; return lttng_consumer_on_read_subbuffer_mmap( - ctx, stream, &subbuffer->buffer.buffer, padding_size); + stream, &subbuffer->buffer.buffer, padding_size); } static ssize_t consumer_stream_consume_splice( @@ -395,6 +396,10 @@ int metadata_stream_check_version(struct lttng_consumer_stream *stream, stream->metadata_version = subbuffer->info.metadata.version; stream->reset_metadata_flag = 1; + if (stream->metadata_bucket) { + metadata_bucket_reset(stream->metadata_bucket); + } + if (stream->read_subbuffer_ops.reset_metadata) { stream->read_subbuffer_ops.reset_metadata(stream); } @@ -726,6 +731,7 @@ void consumer_stream_free(struct lttng_consumer_stream *stream) { assert(stream); + metadata_bucket_destroy(stream->metadata_bucket); call_rcu(&stream->node.head, free_stream_rcu); } @@ -991,3 +997,59 @@ bool consumer_stream_is_deleted(struct lttng_consumer_stream *stream) assert(stream); return cds_lfht_is_node_deleted(&stream->node.node); } + +static ssize_t metadata_bucket_flush( + const struct stream_subbuffer *buffer, void *data) +{ + ssize_t ret; + struct lttng_consumer_stream *stream = data; + + ret = consumer_stream_consume_mmap(NULL, stream, buffer); + if (ret < 0) { + goto end; + } +end: + return ret; +} + +static ssize_t metadata_bucket_consume( + struct lttng_consumer_local_data *unused, + struct lttng_consumer_stream *stream, + const struct stream_subbuffer *subbuffer) +{ + ssize_t ret; + enum metadata_bucket_status status; + + status = metadata_bucket_fill(stream->metadata_bucket, subbuffer); + switch (status) { + case METADATA_BUCKET_STATUS_OK: + /* Return consumed size. */ + ret = subbuffer->buffer.buffer.size; + break; + default: + ret = -1; + } + + return ret; +} + +int consumer_stream_enable_metadata_bucketization( + struct lttng_consumer_stream *stream) +{ + int ret = 0; + + assert(stream->metadata_flag); + assert(!stream->metadata_bucket); + assert(stream->chan->output == CONSUMER_CHANNEL_MMAP); + + stream->metadata_bucket = metadata_bucket_create( + metadata_bucket_flush, stream); + if (!stream->metadata_bucket) { + ret = -1; + goto end; + } + + stream->read_subbuffer_ops.consume_subbuffer = metadata_bucket_consume; +end: + return ret; +} diff --git a/src/common/consumer/consumer-stream.h b/src/common/consumer/consumer-stream.h index eb00dac78..cb1dafe39 100644 --- a/src/common/consumer/consumer-stream.h +++ b/src/common/consumer/consumer-stream.h @@ -111,4 +111,13 @@ int consumer_stream_rotate_output_files(struct lttng_consumer_stream *stream); */ bool consumer_stream_is_deleted(struct lttng_consumer_stream *stream); +/* + * Enable metadata bucketization. This must only be enabled if the tracer + * provides a reliable metadata `coherent` flag. + * + * This must be called on initialization before any subbuffer is consumed. + */ +int consumer_stream_enable_metadata_bucketization( + struct lttng_consumer_stream *stream); + #endif /* LTTNG_CONSUMER_STREAM_H */ diff --git a/src/common/consumer/consumer.c b/src/common/consumer/consumer.c index 5c211339d..52b15867b 100644 --- a/src/common/consumer/consumer.c +++ b/src/common/consumer/consumer.c @@ -1583,7 +1583,6 @@ end: * Returns the number of bytes written */ ssize_t lttng_consumer_on_read_subbuffer_mmap( - struct lttng_consumer_local_data *ctx, struct lttng_consumer_stream *stream, const struct lttng_buffer_view *buffer, unsigned long padding) diff --git a/src/common/consumer/consumer.h b/src/common/consumer/consumer.h index aa8a401a0..4770671c0 100644 --- a/src/common/consumer/consumer.h +++ b/src/common/consumer/consumer.h @@ -265,6 +265,14 @@ struct stream_subbuffer { unsigned long subbuf_size; unsigned long padded_subbuf_size; uint64_t version; + /* + * Left unset when unsupported. + * + * Indicates that this is the last sub-buffer of + * a series of sub-buffer that makes-up a coherent + * (parseable) unit of metadata. + */ + LTTNG_OPTIONAL(bool) coherent; } metadata; struct { unsigned long subbuf_size; @@ -623,6 +631,7 @@ struct lttng_consumer_stream { on_sleep_cb on_sleep; unlock_cb unlock; } read_subbuffer_ops; + struct metadata_bucket *metadata_bucket; }; /* @@ -954,7 +963,6 @@ struct lttng_consumer_local_data *lttng_consumer_create( int (*update_stream)(uint64_t sessiond_key, uint32_t state)); void lttng_consumer_destroy(struct lttng_consumer_local_data *ctx); ssize_t lttng_consumer_on_read_subbuffer_mmap( - struct lttng_consumer_local_data *ctx, struct lttng_consumer_stream *stream, const struct lttng_buffer_view *buffer, unsigned long padding); diff --git a/src/common/consumer/metadata-bucket.c b/src/common/consumer/metadata-bucket.c new file mode 100644 index 000000000..1ee5022e5 --- /dev/null +++ b/src/common/consumer/metadata-bucket.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "metadata-bucket.h" + +#include +#include +#include +#include +#include + +struct metadata_bucket { + struct lttng_dynamic_buffer content; + struct { + metadata_bucket_flush_cb fn; + void *data; + } flush; + unsigned int buffer_count; +}; + +struct metadata_bucket *metadata_bucket_create( + metadata_bucket_flush_cb flush, void *data) +{ + struct metadata_bucket *bucket; + + bucket = zmalloc(sizeof(typeof(*bucket))); + if (!bucket) { + PERROR("Failed to allocate buffer bucket"); + goto end; + } + + bucket->flush.fn = flush; + bucket->flush.data = data; + lttng_dynamic_buffer_init(&bucket->content); +end: + return bucket; +} + +void metadata_bucket_destroy(struct metadata_bucket *bucket) +{ + if (!bucket) { + return; + } + + if (bucket->content.size > 0) { + WARN("Stream metadata bucket destroyed with remaining data: size = %zu, buffer count = %u", + bucket->content.size, bucket->buffer_count); + } + + lttng_dynamic_buffer_reset(&bucket->content); + free(bucket); +} + +void metadata_bucket_reset(struct metadata_bucket *bucket) +{ + lttng_dynamic_buffer_reset(&bucket->content); + lttng_dynamic_buffer_init(&bucket->content); + bucket->buffer_count = 0; +} + +enum metadata_bucket_status metadata_bucket_fill(struct metadata_bucket *bucket, + const struct stream_subbuffer *buffer) +{ + ssize_t ret; + struct lttng_buffer_view flushed_view; + struct stream_subbuffer flushed_subbuffer; + enum metadata_bucket_status status; + const bool should_flush = + LTTNG_OPTIONAL_GET(buffer->info.metadata.coherent); + const size_t padding_this_buffer = + buffer->info.metadata.padded_subbuf_size - + buffer->info.metadata.subbuf_size; + size_t flush_size; + + DBG("Metadata bucket filled with %zu bytes buffer view, sub-buffer size: %lu, padded sub-buffer size: %lu, coherent: %s", + buffer->buffer.buffer.size, + buffer->info.metadata.subbuf_size, + buffer->info.metadata.padded_subbuf_size, + buffer->info.metadata.coherent.value ? "true" : "false"); + /* + * If no metadata was accumulated and this buffer should be + * flushed, don't copy it unecessarily; just flush it directly. + */ + if (!should_flush || bucket->buffer_count != 0) { + /* + * Append the _padded_ subbuffer since they are combined + * into a single "virtual" subbuffer that will be + * flushed at once. + * + * This means that some padding will be sent over the + * network, but should not represent a large amount + * of data as incoherent subbuffers are typically + * pretty full. + * + * The padding of the last subbuffer (coherent) added to + * the bucket is not sent, which is what really matters + * from an efficiency point of view. + */ + ret = lttng_dynamic_buffer_append_view( + &bucket->content, &buffer->buffer.buffer); + if (ret) { + status = METADATA_BUCKET_STATUS_ERROR; + goto end; + } + } + + bucket->buffer_count++; + if (!should_flush) { + status = METADATA_BUCKET_STATUS_OK; + goto end; + } + + flushed_view = bucket->content.size != 0 ? + lttng_buffer_view_from_dynamic_buffer(&bucket->content, 0, -1) : + lttng_buffer_view_from_view(&buffer->buffer.buffer, 0, -1); + + /* + * The flush is done with the size of all padded sub-buffers, except + * for the last one which we can safely "trim". The padding of the last + * packet will be reconstructed by the relay daemon. + */ + flush_size = flushed_view.size - padding_this_buffer; + + flushed_subbuffer = (typeof(flushed_subbuffer)) { + .buffer.buffer = flushed_view, + .info.metadata.subbuf_size = flush_size, + .info.metadata.padded_subbuf_size = flushed_view.size, + .info.metadata.version = buffer->info.metadata.version, + .info.metadata.coherent = buffer->info.metadata.coherent, + }; + + DBG("Metadata bucket flushing %zu bytes (%u sub-buffer%s)", + flushed_view.size, bucket->buffer_count, + bucket->buffer_count > 1 ? "s" : ""); + ret = bucket->flush.fn(&flushed_subbuffer, bucket->flush.data); + if (ret >= 0) { + status = METADATA_BUCKET_STATUS_OK; + } else { + status = METADATA_BUCKET_STATUS_ERROR; + } + + metadata_bucket_reset(bucket); + +end: + return status; +} diff --git a/src/common/consumer/metadata-bucket.h b/src/common/consumer/metadata-bucket.h new file mode 100644 index 000000000..0355eb3c0 --- /dev/null +++ b/src/common/consumer/metadata-bucket.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#ifndef METADATA_BUCKET_H +#define METADATA_BUCKET_H + +#include + +struct metadata_bucket; + +typedef ssize_t (*metadata_bucket_flush_cb)( + const struct stream_subbuffer *buffer, void *data); + +enum metadata_bucket_status { + METADATA_BUCKET_STATUS_OK, + METADATA_BUCKET_STATUS_ERROR, +}; + +struct metadata_bucket *metadata_bucket_create( + metadata_bucket_flush_cb flush, void *data); + +void metadata_bucket_destroy(struct metadata_bucket *bucket); + +enum metadata_bucket_status metadata_bucket_fill(struct metadata_bucket *bucket, + const struct stream_subbuffer *buffer); + +void metadata_bucket_reset(struct metadata_bucket *bucket); + +#endif /* METADATA_BUCKET_H */ + diff --git a/src/common/kernel-consumer/kernel-consumer.c b/src/common/kernel-consumer/kernel-consumer.c index e06f3b3e0..dd5cf1761 100644 --- a/src/common/kernel-consumer/kernel-consumer.c +++ b/src/common/kernel-consumer/kernel-consumer.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -36,7 +37,7 @@ #include #include #include -#include +#include #include "kernel-consumer.h" @@ -287,7 +288,7 @@ static int lttng_kconsumer_snapshot_channel( subbuf_view = lttng_buffer_view_init( subbuf_addr, 0, padded_len); - read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, + read_len = lttng_consumer_on_read_subbuffer_mmap( stream, &subbuf_view, padded_len - len); /* @@ -1558,6 +1559,42 @@ end: return ret; } +static +int get_next_subbuffer_metadata_check(struct lttng_consumer_stream *stream, + struct stream_subbuffer *subbuffer) +{ + int ret; + const char *addr; + bool coherent; + + ret = kernctl_get_next_subbuf_metadata_check(stream->wait_fd, + &coherent); + if (ret) { + goto end; + } + + ret = stream->read_subbuffer_ops.extract_subbuffer_info( + stream, subbuffer); + if (ret) { + goto end; + } + + LTTNG_OPTIONAL_SET(&subbuffer->info.metadata.coherent, coherent); + + ret = get_current_subbuf_addr(stream, &addr); + if (ret) { + goto end; + } + + subbuffer->buffer.buffer = lttng_buffer_view_init( + addr, 0, subbuffer->info.data.padded_subbuf_size); + DBG("Got metadata packet with padded_subbuf_size = %lu, coherent = %s", + subbuffer->info.metadata.padded_subbuf_size, + coherent ? "true" : "false"); +end: + return ret; +} + static int put_next_subbuffer(struct lttng_consumer_stream *stream, struct stream_subbuffer *subbuffer) @@ -1576,15 +1613,53 @@ int put_next_subbuffer(struct lttng_consumer_stream *stream, return ret; } -static void lttng_kconsumer_set_stream_ops( +static +bool is_get_next_check_metadata_available(int tracer_fd) +{ + return kernctl_get_next_subbuf_metadata_check(tracer_fd, NULL) != + -ENOTTY; +} + +static +int lttng_kconsumer_set_stream_ops( struct lttng_consumer_stream *stream) { - if (stream->chan->output == CONSUMER_CHANNEL_MMAP) { - stream->read_subbuffer_ops.get_next_subbuffer = - get_next_subbuffer_mmap; - } else { - stream->read_subbuffer_ops.get_next_subbuffer = - get_next_subbuffer_splice; + int ret = 0; + + if (stream->metadata_flag && stream->chan->is_live) { + DBG("Attempting to enable metadata bucketization for live consumers"); + if (is_get_next_check_metadata_available(stream->wait_fd)) { + DBG("Kernel tracer supports get_next_subbuffer_metadata_check, metadata will be accumulated until a coherent state is reached"); + stream->read_subbuffer_ops.get_next_subbuffer = + get_next_subbuffer_metadata_check; + ret = consumer_stream_enable_metadata_bucketization( + stream); + if (ret) { + goto end; + } + } else { + /* + * The kernel tracer version is too old to indicate + * when the metadata stream has reached a "coherent" + * (parseable) point. + * + * This means that a live viewer may see an incoherent + * sequence of metadata and fail to parse it. + */ + WARN("Kernel tracer does not support get_next_subbuffer_metadata_check which may cause live clients to fail to parse the metadata stream"); + metadata_bucket_destroy(stream->metadata_bucket); + stream->metadata_bucket = NULL; + } + } + + if (!stream->read_subbuffer_ops.get_next_subbuffer) { + if (stream->chan->output == CONSUMER_CHANNEL_MMAP) { + stream->read_subbuffer_ops.get_next_subbuffer = + get_next_subbuffer_mmap; + } else { + stream->read_subbuffer_ops.get_next_subbuffer = + get_next_subbuffer_splice; + } } if (stream->metadata_flag) { @@ -1600,6 +1675,8 @@ static void lttng_kconsumer_set_stream_ops( } stream->read_subbuffer_ops.put_next_subbuffer = put_next_subbuffer; +end: + return ret; } int lttng_kconsumer_on_recv_stream(struct lttng_consumer_stream *stream) @@ -1640,7 +1717,10 @@ int lttng_kconsumer_on_recv_stream(struct lttng_consumer_stream *stream) } } - lttng_kconsumer_set_stream_ops(stream); + ret = lttng_kconsumer_set_stream_ops(stream); + if (ret) { + goto error_close_fd; + } /* we return 0 to let the library handle the FD internally */ return 0; diff --git a/src/common/ust-consumer/ust-consumer.c b/src/common/ust-consumer/ust-consumer.c index 1af9840cd..6d6690a32 100644 --- a/src/common/ust-consumer/ust-consumer.c +++ b/src/common/ust-consumer/ust-consumer.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "ust-consumer.h" @@ -1230,7 +1231,7 @@ static int snapshot_channel(struct lttng_consumer_channel *channel, subbuf_view = lttng_buffer_view_init( subbuf_addr, 0, padded_len); - read_len = lttng_consumer_on_read_subbuffer_mmap(ctx, + read_len = lttng_consumer_on_read_subbuffer_mmap( stream, &subbuf_view, padded_len - len); if (use_relayd) { if (read_len != len) { @@ -2464,7 +2465,7 @@ int commit_one_metadata_packet(struct lttng_consumer_stream *stream) assert(write_len != 0); if (write_len < 0) { ERR("Writing one metadata packet"); - ret = -1; + ret = write_len; goto end; } stream->ust_metadata_pushed += write_len; @@ -2810,28 +2811,88 @@ static int get_next_subbuffer_metadata(struct lttng_consumer_stream *stream, struct stream_subbuffer *subbuffer) { int ret; + bool cache_empty; + bool got_subbuffer; + bool coherent; + bool buffer_empty; + unsigned long consumed_pos, produced_pos; - ret = ustctl_get_next_subbuf(stream->ustream); - if (ret) { - ret = commit_one_metadata_packet(stream); - if (ret < 0) { - goto end; - } else if (ret == 0) { - /* Not an error, the cache is empty. */ - ret = -ENODATA; - goto end; + do { + ret = ustctl_get_next_subbuf(stream->ustream); + if (ret == 0) { + got_subbuffer = true; + } else { + got_subbuffer = false; + if (ret != -EAGAIN) { + /* Fatal error. */ + goto end; + } } - ret = ustctl_get_next_subbuf(stream->ustream); - if (ret) { - goto end; + /* + * Determine if the cache is empty and ensure that a sub-buffer + * is made available if the cache is not empty. + */ + if (!got_subbuffer) { + ret = commit_one_metadata_packet(stream); + if (ret < 0 && ret != -ENOBUFS) { + goto end; + } else if (ret == 0) { + /* Not an error, the cache is empty. */ + cache_empty = true; + ret = -ENODATA; + goto end; + } else { + cache_empty = false; + } + } else { + pthread_mutex_lock(&stream->chan->metadata_cache->lock); + cache_empty = stream->chan->metadata_cache->max_offset == + stream->ust_metadata_pushed; + pthread_mutex_unlock(&stream->chan->metadata_cache->lock); } - } + } while (!got_subbuffer); + /* Populate sub-buffer infos and view. */ ret = get_next_subbuffer_common(stream, subbuffer); if (ret) { goto end; } + + ret = lttng_ustconsumer_sample_snapshot_positions(stream); + if (ret < 0) { + /* + * -EAGAIN is not expected since we got a sub-buffer and haven't + * pushed the consumption position yet (on put_next). + */ + PERROR("Failed to take a snapshot of metadata buffer positions"); + goto end; + } + + ret = lttng_ustconsumer_get_consumed_snapshot(stream, &consumed_pos); + if (ret) { + PERROR("Failed to get metadata consumed position"); + goto end; + } + + ret = lttng_ustconsumer_get_produced_snapshot(stream, &produced_pos); + if (ret) { + PERROR("Failed to get metadata produced position"); + goto end; + } + + /* Last sub-buffer of the ring buffer ? */ + buffer_empty = (consumed_pos + stream->max_sb_size) == produced_pos; + + /* + * The sessiond registry lock ensures that coherent units of metadata + * are pushed to the consumer daemon at once. Hence, if a sub-buffer is + * acquired, the cache is empty, and it is the only available sub-buffer + * available, it is safe to assume that it is "coherent". + */ + coherent = got_subbuffer && cache_empty && buffer_empty; + + LTTNG_OPTIONAL_SET(&subbuffer->info.metadata.coherent, coherent); end: return ret; } @@ -2851,9 +2912,11 @@ static int signal_metadata(struct lttng_consumer_stream *stream, return pthread_cond_broadcast(&stream->metadata_rdv) ? -errno : 0; } -static void lttng_ustconsumer_set_stream_ops( +static int lttng_ustconsumer_set_stream_ops( struct lttng_consumer_stream *stream) { + int ret = 0; + stream->read_subbuffer_ops.on_wake_up = consumer_stream_ust_on_wake_up; if (stream->metadata_flag) { stream->read_subbuffer_ops.get_next_subbuffer = @@ -2862,7 +2925,14 @@ static void lttng_ustconsumer_set_stream_ops( extract_metadata_subbuffer_info; stream->read_subbuffer_ops.reset_metadata = metadata_stream_reset_cache; - stream->read_subbuffer_ops.on_sleep = signal_metadata; + if (stream->chan->is_live) { + stream->read_subbuffer_ops.on_sleep = signal_metadata; + ret = consumer_stream_enable_metadata_bucketization( + stream); + if (ret) { + goto end; + } + } } else { stream->read_subbuffer_ops.get_next_subbuffer = get_next_subbuffer; @@ -2876,6 +2946,8 @@ static void lttng_ustconsumer_set_stream_ops( } stream->read_subbuffer_ops.put_next_subbuffer = put_next_subbuffer; +end: + return ret; } /* -- 2.34.1 From e2d1190b9ea09c54e5d7373643d62e2034bc1531 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Wed, 27 May 2020 11:27:26 -0400 Subject: [PATCH 15/16] Fix: incorrect specifier %lu used with size_t argument MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Fixes the following warning on 32-bit targets: libtool: compile: gcc -DHAVE_CONFIG_H -I../../../include -I../../../include -I../../../src -include config.h -I/build/include -I/home/jenkins/workspace/lttng-tools_master_portbuild/arch/armhf/babeltrace_version/stable-1.5/build/std/conf/std/liburcu_version/master/test_type/base/deps/build/include -Wall -Wno-incomplete-setjmp-declaration -Wdiscarded-qualifiers -Wmissing-declarations -Wmissing-prototypes -Wmissing-parameter-type -fno-strict-aliasing -pthread -g -O2 -MT consumer-stream.lo -MD -MP -MF .deps/consumer-stream.Tpo -c consumer-stream.c -fPIC -DPIC -o .libs/consumer-stream.o In file included from ../../../src/common/common.h:12:0, from consumer.c:25: consumer.c: In function ‘lttng_consumer_on_read_subbuffer_mmap’: ../../../src/common/error.h:161:35: warning: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 7 has type ‘size_t {aka unsigned int}’ [-Wformat=] #define DBG(fmt, args...) _ERRMSG("DEBUG1", PRINT_DBG, fmt, ## args) ^ ../../../src/common/error.h:136:51: note: in definition of macro ‘__lttng_print’ fprintf((type) == PRINT_MSG ? stdout : stderr, fmt, ## args); \ ^~~ ../../../src/common/error.h:161:27: note: in expansion of macro ‘_ERRMSG’ #define DBG(fmt, args...) _ERRMSG("DEBUG1", PRINT_DBG, fmt, ## args) ^~~~~~~ consumer.c:1688:2: note: in expansion of macro ‘DBG’ DBG("Consumer mmap write() ret %zd (len %lu)", ret, write_len); ^~~ Signed-off-by: Jérémie Galarneau Change-Id: Id9a571d8e94105428833baa053c6463b91484a03 --- src/common/consumer/consumer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/consumer/consumer.c b/src/common/consumer/consumer.c index 52b15867b..50951fd28 100644 --- a/src/common/consumer/consumer.c +++ b/src/common/consumer/consumer.c @@ -1685,7 +1685,7 @@ ssize_t lttng_consumer_on_read_subbuffer_mmap( * receive a ret value that is bigger than len. */ ret = lttng_write(outfd, buffer->data, write_len); - DBG("Consumer mmap write() ret %zd (len %lu)", ret, write_len); + DBG("Consumer mmap write() ret %zd (len %zu)", ret, write_len); if (ret < 0 || ((size_t) ret != write_len)) { /* * Report error to caller if nothing was written else at least send the -- 2.34.1 From 5024c2ac433f5c5feec034cb7d8d485d25cf14e7 Mon Sep 17 00:00:00 2001 From: Jonathan Rajotte Date: Mon, 20 Apr 2020 14:27:45 -0400 Subject: [PATCH 16/16] SoW-2019-0007-2: Dynamic Snapshot: Triggers send partial event payload with notifications Revision 1 Change-Id: I4ad2710d14d3e0677c346bb8cdcaa666f09ea948 --- .clang-format | 2 +- .gitignore | 17 +- DO_NO_MERGE.txt | 0 configure.ac | 13 +- doc/examples/trigger-on-event/Makefile | 56 + doc/examples/trigger-on-event/README.md | 82 + doc/examples/trigger-on-event/demo.sh | 22 + .../trigger-on-event/instrumented-app.c | 36 + .../trigger-on-event/notification-client.c | 448 +++ .../trigger-on-event/performance/Makefile | 57 + .../trigger-on-event/performance/README.md | 83 + .../performance/bt_plugin_plot.py | 260 ++ .../trigger-on-event/performance/consumer.c | 185 ++ .../performance/generate-data.sh | 50 + .../performance/generate-graph.sh | 108 + .../performance/perform-experience.sh | 61 + .../performance/performance.c | 10 + .../performance/performance.h | 39 + .../trigger-on-event/performance/producer.c | 57 + .../tracepoint-trigger-example.c | 10 + .../tracepoint-trigger-example.h | 28 + doc/man/Makefile.am | 6 +- doc/man/lttng-add-trigger.1.txt | 207 ++ doc/man/lttng-list-triggers.1.txt | 37 + doc/man/lttng-remove-trigger.1.txt | 38 + include/Makefile.am | 33 +- include/lttng/action/action-internal.h | 19 + include/lttng/action/action.h | 15 +- include/lttng/action/group-internal.h | 38 + include/lttng/action/group.h | 45 + .../lttng/action/rotate-session-internal.h | 29 + include/lttng/action/rotate-session.h | 47 + .../lttng/action/snapshot-session-internal.h | 29 + include/lttng/action/snapshot-session.h | 67 + include/lttng/action/start-session-internal.h | 29 + include/lttng/action/start-session.h | 47 + include/lttng/action/stop-session-internal.h | 29 + include/lttng/action/stop-session.h | 47 + include/lttng/condition/condition-internal.h | 9 +- include/lttng/condition/condition.h | 2 + include/lttng/condition/evaluation-internal.h | 5 +- include/lttng/condition/event-rule-internal.h | 107 + include/lttng/condition/event-rule.h | 166 + include/lttng/domain-internal.h | 35 + include/lttng/event-expr-internal.h | 66 + include/lttng/event-expr.h | 243 ++ include/lttng/event-field-value-internal.h | 192 ++ include/lttng/event-field-value.h | 229 ++ include/lttng/event-internal.h | 15 + .../lttng/event-rule/event-rule-internal.h | 143 + include/lttng/event-rule/event-rule.h | 58 + include/lttng/event-rule/kprobe-internal.h | 53 + include/lttng/event-rule/kprobe.h | 68 + include/lttng/event-rule/kretprobe-internal.h | 50 + include/lttng/event-rule/kretprobe.h | 62 + include/lttng/event-rule/syscall-internal.h | 40 + include/lttng/event-rule/syscall.h | 83 + .../lttng/event-rule/tracepoint-internal.h | 71 + include/lttng/event-rule/tracepoint.h | 187 ++ include/lttng/event-rule/uprobe-internal.h | 39 + include/lttng/event-rule/uprobe.h | 83 + include/lttng/lttng.h | 14 + include/lttng/snapshot-internal.h | 2 +- include/lttng/snapshot.h | 40 +- include/lttng/trigger/trigger-internal.h | 137 +- include/lttng/trigger/trigger.h | 113 +- include/lttng/userspace-probe-internal.h | 19 + src/bin/lttng-sessiond/Makefile.am | 5 +- src/bin/lttng-sessiond/action-executor.c | 715 ++++ src/bin/lttng-sessiond/action-executor.h | 35 + src/bin/lttng-sessiond/agent-thread.c | 10 + src/bin/lttng-sessiond/agent.c | 155 +- src/bin/lttng-sessiond/agent.h | 30 +- src/bin/lttng-sessiond/client.c | 117 +- src/bin/lttng-sessiond/cmd.c | 290 +- src/bin/lttng-sessiond/cmd.h | 11 +- src/bin/lttng-sessiond/dispatch.c | 41 +- src/bin/lttng-sessiond/event.c | 238 +- src/bin/lttng-sessiond/event.h | 13 +- src/bin/lttng-sessiond/globals.c | 2 + src/bin/lttng-sessiond/health-sessiond.h | 1 + src/bin/lttng-sessiond/kernel.c | 539 +++- src/bin/lttng-sessiond/kernel.h | 9 +- src/bin/lttng-sessiond/lttng-sessiond.h | 1 + src/bin/lttng-sessiond/main.c | 12 +- src/bin/lttng-sessiond/modprobe.c | 5 + .../notification-thread-commands.c | 240 +- .../notification-thread-commands.h | 54 + .../notification-thread-events.c | 2185 +++++++++---- .../notification-thread-events.h | 4 + .../notification-thread-internal.h | 187 +- src/bin/lttng-sessiond/notification-thread.c | 137 +- src/bin/lttng-sessiond/notification-thread.h | 73 +- src/bin/lttng-sessiond/rotate.c | 3 + src/bin/lttng-sessiond/thread.c | 23 +- src/bin/lttng-sessiond/trace-kernel.c | 222 +- src/bin/lttng-sessiond/trace-kernel.h | 34 +- src/bin/lttng-sessiond/trace-ust.c | 4 +- src/bin/lttng-sessiond/trace-ust.h | 12 +- src/bin/lttng-sessiond/ust-abi-internal.h | 37 + src/bin/lttng-sessiond/ust-app.c | 749 ++++- src/bin/lttng-sessiond/ust-app.h | 42 +- src/bin/lttng-sessiond/ust-ctl-internal.h | 10 + src/bin/lttng/Makefile.am | 8 +- src/bin/lttng/command.h | 3 + src/bin/lttng/commands/add_trigger.c | 2031 ++++++++++++ src/bin/lttng/commands/enable_events.c | 428 +-- src/bin/lttng/commands/list_triggers.c | 627 ++++ src/bin/lttng/commands/remove_trigger.c | 131 + src/bin/lttng/lttng.c | 3 + src/bin/lttng/uprobe.c | 387 +++ src/bin/lttng/uprobe.h | 19 + src/bin/lttng/utils.c | 19 + src/bin/lttng/utils.h | 2 + src/common/Makefile.am | 43 +- src/common/actions/action.c | 96 +- src/common/actions/group.c | 339 ++ src/common/actions/notify.c | 9 + src/common/actions/rotate-session.c | 255 ++ src/common/actions/snapshot-session.c | 408 +++ src/common/actions/start-session.c | 256 ++ src/common/actions/stop-session.c | 273 ++ src/common/argpar/Makefile.am | 3 + src/common/argpar/argpar.c | 746 +++++ src/common/argpar/argpar.h | 330 ++ src/common/bytecode/Makefile.am | 6 + src/common/bytecode/bytecode.c | 264 ++ src/common/bytecode/bytecode.h | 266 ++ src/common/{ => conditions}/buffer-usage.c | 8 +- src/common/{ => conditions}/condition.c | 39 +- src/common/conditions/event-rule.c | 1583 +++++++++ .../{ => conditions}/session-consumed-size.c | 8 +- .../{ => conditions}/session-rotation.c | 11 +- src/common/credentials.c | 31 + src/common/credentials.h | 5 + src/common/domain.c | 46 + src/common/dynamic-array.h | 17 + src/common/error.c | 30 +- src/common/error.h | 58 +- src/common/evaluation.c | 16 + src/common/event-expr-to-bytecode.c | 201 ++ src/common/event-expr-to-bytecode.h | 20 + src/common/event-expr.c | 449 +++ src/common/event-field-value.c | 592 ++++ src/common/event-rule/event-rule.c | 325 ++ src/common/event-rule/kprobe.c | 507 +++ src/common/event-rule/kretprobe.c | 153 + src/common/event-rule/syscall.c | 476 +++ src/common/event-rule/tracepoint.c | 1133 +++++++ src/common/event-rule/uprobe.c | 404 +++ src/common/filter.c | 2 +- src/common/filter.h | 2 +- .../lttng-ctl => common}/filter/Makefile.am | 5 +- .../lttng-ctl => common}/filter/filter-ast.h | 7 +- .../filter/filter-grammar-test.c | 2 +- .../lttng-ctl => common}/filter/filter-ir.h | 65 + .../filter/filter-lexer.l | 0 .../filter/filter-parser.y | 133 + .../filter/filter-symbols.h | 0 .../filter/filter-visitor-generate-bytecode.c | 272 +- .../filter/filter-visitor-generate-ir.c | 0 ...ilter-visitor-ir-check-binary-comparator.c | 0 ...ilter-visitor-ir-check-binary-op-nesting.c | 0 ...ilter-visitor-ir-normalize-glob-patterns.c | 0 .../filter-visitor-ir-validate-globbing.c | 0 .../filter-visitor-ir-validate-string.c | 0 .../filter/filter-visitor-xml.c | 0 .../lttng-ctl => common}/filter/memstream.h | 0 src/common/kernel-ctl/kernel-ctl.c | 36 +- src/common/kernel-ctl/kernel-ctl.h | 9 +- src/common/kernel-ctl/kernel-ioctl.h | 8 + src/common/lttng-kernel.h | 33 + src/common/notification.c | 7 +- src/common/runas.c | 121 + src/common/runas.h | 6 + src/common/sessiond-comm/sessiond-comm.h | 3 +- src/common/snapshot.c | 184 ++ src/common/snapshot.h | 47 + src/common/trigger.c | 659 +++- src/common/unix.c | 18 +- src/common/userspace-probe.c | 226 +- src/common/utils.c | 37 + src/common/utils.h | 12 + src/lib/lttng-ctl/Makefile.am | 5 +- src/lib/lttng-ctl/channel.c | 2 +- src/lib/lttng-ctl/filter/filter-bytecode.h | 243 -- src/lib/lttng-ctl/lttng-ctl-health.c | 1 + src/lib/lttng-ctl/lttng-ctl-helper.h | 17 + src/lib/lttng-ctl/lttng-ctl.c | 289 +- src/lib/lttng-ctl/snapshot.c | 133 +- tests/regression/Makefile.am | 19 +- tests/regression/kernel/test_callstack | 7 +- tests/regression/kernel/test_syscall | 2 +- tests/regression/tools/Makefile.am | 2 +- .../regression/tools/notification/Makefile.am | 45 +- .../tools/notification/notification.c | 2867 ++++++++++++++--- .../test_notification_kernel_buffer_usage | 88 + .../test_notification_kernel_capture | 55 + .../test_notification_kernel_error | 55 + .../test_notification_kernel_instrumentation | 51 + .../test_notification_kernel_syscall | 52 + .../test_notification_kernel_userspace_probe | 49 + .../notification/test_notification_multi_app | 72 +- .../test_notification_ust_buffer_usage | 74 + .../test_notification_ust_capture | 40 + .../notification/test_notification_ust_error | 39 + ...ication_ust_event_rule_condition_exclusion | 41 + .../notification/util_event_generator.sh | 195 ++ tests/regression/tools/trigger/Makefile.am | 56 + tests/regression/tools/trigger/base_client.c | 252 ++ .../tools/trigger/consumer_testpoints.c | 147 + .../tools/trigger/start-stop/Makefile.am | 19 + .../tools/trigger/start-stop/test_start_stop | 198 ++ .../tools/trigger/test_add_trigger_cli | 403 +++ .../tools/trigger/test_list_triggers_cli | 335 ++ .../tools/trigger/test_remove_trigger_cli | 110 + .../test_trigger_kernel} | 20 +- .../test_trigger_ust} | 22 +- tests/regression/tools/trigger/trigger.c | 729 +++++ .../tools/trigger/utils/Makefile.am | 9 + .../tools/trigger/utils/notification-client.c | 242 ++ .../test_relayd_working_directory | 16 +- .../stress/test_multi_sessions_per_uid_10app | 4 +- ...test_multi_sessions_per_uid_5app_streaming | 8 +- ...essions_per_uid_5app_streaming_kill_relayd | 8 +- tests/unit/Makefile.am | 22 +- tests/unit/test_condition.c | 94 + tests/unit/test_event_expr_to_bytecode.c | 90 + tests/unit/test_event_rule.c | 154 + .../gen-syscall-events/gen-syscall-events.c | 60 +- .../testapp/gen-ust-events/gen-ust-events.c | 8 +- tests/utils/testapp/gen-ust-events/tp.h | 27 + tests/utils/utils.sh | 64 +- 233 files changed, 30906 insertions(+), 2554 deletions(-) create mode 100644 DO_NO_MERGE.txt create mode 100644 doc/examples/trigger-on-event/Makefile create mode 100644 doc/examples/trigger-on-event/README.md create mode 100755 doc/examples/trigger-on-event/demo.sh create mode 100644 doc/examples/trigger-on-event/instrumented-app.c create mode 100644 doc/examples/trigger-on-event/notification-client.c create mode 100644 doc/examples/trigger-on-event/performance/Makefile create mode 100644 doc/examples/trigger-on-event/performance/README.md create mode 100644 doc/examples/trigger-on-event/performance/bt_plugin_plot.py create mode 100644 doc/examples/trigger-on-event/performance/consumer.c create mode 100755 doc/examples/trigger-on-event/performance/generate-data.sh create mode 100755 doc/examples/trigger-on-event/performance/generate-graph.sh create mode 100755 doc/examples/trigger-on-event/performance/perform-experience.sh create mode 100644 doc/examples/trigger-on-event/performance/performance.c create mode 100644 doc/examples/trigger-on-event/performance/performance.h create mode 100644 doc/examples/trigger-on-event/performance/producer.c create mode 100644 doc/examples/trigger-on-event/tracepoint-trigger-example.c create mode 100644 doc/examples/trigger-on-event/tracepoint-trigger-example.h create mode 100644 doc/man/lttng-add-trigger.1.txt create mode 100644 doc/man/lttng-list-triggers.1.txt create mode 100644 doc/man/lttng-remove-trigger.1.txt create mode 100644 include/lttng/action/group-internal.h create mode 100644 include/lttng/action/group.h create mode 100644 include/lttng/action/rotate-session-internal.h create mode 100644 include/lttng/action/rotate-session.h create mode 100644 include/lttng/action/snapshot-session-internal.h create mode 100644 include/lttng/action/snapshot-session.h create mode 100644 include/lttng/action/start-session-internal.h create mode 100644 include/lttng/action/start-session.h create mode 100644 include/lttng/action/stop-session-internal.h create mode 100644 include/lttng/action/stop-session.h create mode 100644 include/lttng/condition/event-rule-internal.h create mode 100644 include/lttng/condition/event-rule.h create mode 100644 include/lttng/domain-internal.h create mode 100644 include/lttng/event-expr-internal.h create mode 100644 include/lttng/event-expr.h create mode 100644 include/lttng/event-field-value-internal.h create mode 100644 include/lttng/event-field-value.h create mode 100644 include/lttng/event-rule/event-rule-internal.h create mode 100644 include/lttng/event-rule/event-rule.h create mode 100644 include/lttng/event-rule/kprobe-internal.h create mode 100644 include/lttng/event-rule/kprobe.h create mode 100644 include/lttng/event-rule/kretprobe-internal.h create mode 100644 include/lttng/event-rule/kretprobe.h create mode 100644 include/lttng/event-rule/syscall-internal.h create mode 100644 include/lttng/event-rule/syscall.h create mode 100644 include/lttng/event-rule/tracepoint-internal.h create mode 100644 include/lttng/event-rule/tracepoint.h create mode 100644 include/lttng/event-rule/uprobe-internal.h create mode 100644 include/lttng/event-rule/uprobe.h create mode 100644 src/bin/lttng-sessiond/action-executor.c create mode 100644 src/bin/lttng-sessiond/action-executor.h create mode 100644 src/bin/lttng/commands/add_trigger.c create mode 100644 src/bin/lttng/commands/list_triggers.c create mode 100644 src/bin/lttng/commands/remove_trigger.c create mode 100644 src/bin/lttng/uprobe.c create mode 100644 src/bin/lttng/uprobe.h create mode 100644 src/common/actions/group.c create mode 100644 src/common/actions/rotate-session.c create mode 100644 src/common/actions/snapshot-session.c create mode 100644 src/common/actions/start-session.c create mode 100644 src/common/actions/stop-session.c create mode 100644 src/common/argpar/Makefile.am create mode 100644 src/common/argpar/argpar.c create mode 100644 src/common/argpar/argpar.h create mode 100644 src/common/bytecode/Makefile.am create mode 100644 src/common/bytecode/bytecode.c create mode 100644 src/common/bytecode/bytecode.h rename src/common/{ => conditions}/buffer-usage.c (99%) rename src/common/{ => conditions}/condition.c (79%) create mode 100644 src/common/conditions/event-rule.c rename src/common/{ => conditions}/session-consumed-size.c (99%) rename src/common/{ => conditions}/session-rotation.c (98%) create mode 100644 src/common/credentials.c create mode 100644 src/common/domain.c create mode 100644 src/common/event-expr-to-bytecode.c create mode 100644 src/common/event-expr-to-bytecode.h create mode 100644 src/common/event-expr.c create mode 100644 src/common/event-field-value.c create mode 100644 src/common/event-rule/event-rule.c create mode 100644 src/common/event-rule/kprobe.c create mode 100644 src/common/event-rule/kretprobe.c create mode 100644 src/common/event-rule/syscall.c create mode 100644 src/common/event-rule/tracepoint.c create mode 100644 src/common/event-rule/uprobe.c rename src/{lib/lttng-ctl => common}/filter/Makefile.am (96%) rename src/{lib/lttng-ctl => common}/filter/filter-ast.h (94%) rename src/{lib/lttng-ctl => common}/filter/filter-grammar-test.c (98%) rename src/{lib/lttng-ctl => common}/filter/filter-ir.h (60%) rename src/{lib/lttng-ctl => common}/filter/filter-lexer.l (100%) rename src/{lib/lttng-ctl => common}/filter/filter-parser.y (83%) rename src/{lib/lttng-ctl => common}/filter/filter-symbols.h (100%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-generate-bytecode.c (68%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-generate-ir.c (100%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-ir-check-binary-comparator.c (100%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-ir-check-binary-op-nesting.c (100%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-ir-normalize-glob-patterns.c (100%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-ir-validate-globbing.c (100%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-ir-validate-string.c (100%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-xml.c (100%) rename src/{lib/lttng-ctl => common}/filter/memstream.h (100%) create mode 100644 src/common/snapshot.c create mode 100644 src/common/snapshot.h delete mode 100644 src/lib/lttng-ctl/filter/filter-bytecode.h create mode 100755 tests/regression/tools/notification/test_notification_kernel_buffer_usage create mode 100755 tests/regression/tools/notification/test_notification_kernel_capture create mode 100755 tests/regression/tools/notification/test_notification_kernel_error create mode 100755 tests/regression/tools/notification/test_notification_kernel_instrumentation create mode 100755 tests/regression/tools/notification/test_notification_kernel_syscall create mode 100755 tests/regression/tools/notification/test_notification_kernel_userspace_probe create mode 100755 tests/regression/tools/notification/test_notification_ust_buffer_usage create mode 100755 tests/regression/tools/notification/test_notification_ust_capture create mode 100755 tests/regression/tools/notification/test_notification_ust_error create mode 100755 tests/regression/tools/notification/test_notification_ust_event_rule_condition_exclusion create mode 100644 tests/regression/tools/notification/util_event_generator.sh create mode 100644 tests/regression/tools/trigger/Makefile.am create mode 100644 tests/regression/tools/trigger/base_client.c create mode 100644 tests/regression/tools/trigger/consumer_testpoints.c create mode 100644 tests/regression/tools/trigger/start-stop/Makefile.am create mode 100755 tests/regression/tools/trigger/start-stop/test_start_stop create mode 100755 tests/regression/tools/trigger/test_add_trigger_cli create mode 100755 tests/regression/tools/trigger/test_list_triggers_cli create mode 100755 tests/regression/tools/trigger/test_remove_trigger_cli rename tests/regression/tools/{notification/test_notification_kernel => trigger/test_trigger_kernel} (72%) rename tests/regression/tools/{notification/test_notification_ust => trigger/test_trigger_ust} (67%) create mode 100644 tests/regression/tools/trigger/trigger.c create mode 100644 tests/regression/tools/trigger/utils/Makefile.am create mode 100644 tests/regression/tools/trigger/utils/notification-client.c create mode 100644 tests/unit/test_condition.c create mode 100644 tests/unit/test_event_expr_to_bytecode.c create mode 100644 tests/unit/test_event_rule.c diff --git a/.clang-format b/.clang-format index 3fc5c6737..91a23c9b0 100644 --- a/.clang-format +++ b/.clang-format @@ -2,7 +2,7 @@ AlignAfterOpenBracket: DontAlign AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left -AlignOperands: true +AlignOperands: false AlignTrailingComments: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false diff --git a/.gitignore b/.gitignore index 8f29a8623..7d135e549 100644 --- a/.gitignore +++ b/.gitignore @@ -61,11 +61,11 @@ compile_commands.json /src/bin/lttng-crash/lttng-crash /src/bin/lttng-relayd/lttng-relayd /src/lib/lttng-ctl/lttng-ctl.pc -/src/lib/lttng-ctl/filter/filter-grammar-test -/src/lib/lttng-ctl/filter/filter-lexer.c -/src/lib/lttng-ctl/filter/filter-parser.c -/src/lib/lttng-ctl/filter/filter-parser.h -/src/lib/lttng-ctl/filter/filter-parser.output +/src/common/filter/filter-grammar-test +/src/common/filter/filter-lexer.c +/src/common/filter/filter-parser.c +/src/common/filter/filter-parser.h +/src/common/filter/filter-parser.output /extras/bindings/swig/python/lttng.i /extras/bindings/swig/python/lttng.py @@ -88,6 +88,8 @@ compile_commands.json /tests/unit/test_directory_handle /tests/unit/test_relayd_backward_compat_group_by_session /tests/unit/test_fd_tracker +/tests/unit/test_event_rule +/tests/unit/test_condition kernel_all_events_basic kernel_event_basic ust_global_event_wildcard @@ -105,6 +107,8 @@ health_check /tests/regression/tools/notification/notification /tests/regression/tools/rotation/schedule_api /tests/regression/tools/notification/rotation +/tests/regression/tools/trigger/base_client +/tests/regression/tools/trigger/trigger /tests/regression/ust/overlap/demo/demo /tests/regression/ust/linking/demo_builtin /tests/regression/ust/linking/demo_static @@ -159,6 +163,9 @@ health_check # examples /doc/examples/rotation/rotate-client +/doc/examples/trigger-on-event/performance/consumer +/doc/examples/trigger-on-event/performance/producer +/doc/examples/trigger-on-event/performance/performance.a /benchmark/ diff --git a/DO_NO_MERGE.txt b/DO_NO_MERGE.txt new file mode 100644 index 000000000..e69de29bb diff --git a/configure.ac b/configure.ac index 4e186d471..accd4723b 100644 --- a/configure.ac +++ b/configure.ac @@ -474,6 +474,7 @@ PKG_CHECK_MODULES([POPT], [popt], AC_SUBST(POPT_LIBS) PKG_CHECK_MODULES([libxml2], [libxml-2.0 >= 2.7.6]) +PKG_CHECK_MODULES([msgpack], [msgpack]) AC_CHECK_FUNC([clock_gettime], [AC_DEFINE_UNQUOTED([LTTNG_HAVE_CLOCK_GETTIME], 1, [Has clock_gettime() support.])]) @@ -1081,7 +1082,7 @@ AC_SUBST(AM_LDFLAGS) # In a scenario where lttng-tools is built from a distribution tarball and in a # out-of-tree manner, the generated "version.i" has priority on the one from # the source (distribution tarball) and must be found first. -AM_CPPFLAGS="-I\$(top_builddir)/include -I\$(top_srcdir)/include -I\$(top_srcdir)/src -include config.h $AM_CPPFLAGS" +AM_CPPFLAGS="-I\$(top_builddir)/include -I\$(top_srcdir)/include -I\$(top_builddir)/src -I\$(top_srcdir)/src -include config.h $AM_CPPFLAGS" AC_SUBST(AM_CPPFLAGS) lttngincludedir="${includedir}/lttng" @@ -1099,6 +1100,9 @@ AC_SUBST(lttngnotificationincludedir) lttngtriggerincludedir="${includedir}/lttng/trigger" AC_SUBST(lttngtriggerincludedir) +lttngeventruleincludedir="${includedir}/lttng/event-rule" +AC_SUBST(lttngeventruleincludedir) + lttnglibexecdir="${libdir}/lttng/libexec" AC_SUBST(lttnglibexecdir) @@ -1115,6 +1119,8 @@ AC_CONFIG_FILES([ extras/core-handler/Makefile src/Makefile src/common/Makefile + src/common/argpar/Makefile + src/common/bytecode/Makefile src/common/kernel-ctl/Makefile src/common/kernel-consumer/Makefile src/common/consumer/Makefile @@ -1129,9 +1135,9 @@ AC_CONFIG_FILES([ src/common/config/Makefile src/common/string-utils/Makefile src/common/fd-tracker/Makefile + src/common/filter/Makefile src/lib/Makefile src/lib/lttng-ctl/Makefile - src/lib/lttng-ctl/filter/Makefile src/lib/lttng-ctl/lttng-ctl.pc src/bin/Makefile src/bin/lttng-consumerd/Makefile @@ -1166,6 +1172,9 @@ AC_CONFIG_FILES([ tests/regression/tools/working-directory/Makefile tests/regression/tools/relayd-grouping/Makefile tests/regression/tools/clear/Makefile + tests/regression/tools/trigger/Makefile + tests/regression/tools/trigger/start-stop/Makefile + tests/regression/tools/trigger/utils/Makefile tests/regression/ust/Makefile tests/regression/ust/nprocesses/Makefile tests/regression/ust/high-throughput/Makefile diff --git a/doc/examples/trigger-on-event/Makefile b/doc/examples/trigger-on-event/Makefile new file mode 100644 index 000000000..f758a05cf --- /dev/null +++ b/doc/examples/trigger-on-event/Makefile @@ -0,0 +1,56 @@ +# Copyright (C) 2020 Jérémie Galarneau +# +# THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED +# OR IMPLIED. ANY USE IS AT YOUR OWN RISK. +# +# Permission is hereby granted to use or copy this program for any +# purpose, provided the above notices are retained on all copies. +# Permission to modify the code and to distribute modified code is +# granted, provided the above notices are retained, and a notice that +# the code was modified is included with the above copyright notice. +# +# This Makefile is not using automake so that users may see how to build +# a program with tracepoint provider probes compiled as static libraries. +# +# This makefile is purposefully kept simple to support GNU and BSD make. + +LOCAL_CPPFLAGS += -I. +LIBS_INSTRUMENTED_APP = -ldl -llttng-ust +LIBS_NOTIFICATION_CLIENT = -ldl -llttng-ctl +AM_V_P := : +AR ?= ar + +all: instrumented-app notification-client + +tracepoint-trigger-example.o: tracepoint-trigger-example.c tracepoint-trigger-example.h + @if $(AM_V_P); then set -x; else echo " CC $@"; fi; \ + $(CC) $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(AM_CFLAGS) $(AM_CPPFLAGS) \ + $(CFLAGS) -c -o $@ $< + +tracepoint-trigger-example.a: tracepoint-trigger-example.o + @if $(AM_V_P); then set -x; else echo " AR $@"; fi; \ + $(AR) -rc $@ tracepoint-trigger-example.o + +instrumented-app.o: instrumented-app.c + @if $(AM_V_P); then set -x; else echo " CC $@"; fi; \ + $(CC) $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(AM_CFLAGS) $(AM_CPPFLAGS) \ + $(CFLAGS) -c -o $@ $< + +instrumented-app: instrumented-app.o tracepoint-trigger-example.a + @if $(AM_V_P); then set -x; else echo " CCLD $@"; fi; \ + $(CC) -o $@ $(LDFLAGS) $(CPPFLAGS) $(AM_LDFLAGS) $(AM_CFLAGS) \ + $(CFLAGS) instrumented-app.o tracepoint-trigger-example.a $(LIBS_INSTRUMENTED_APP) + +notification-client.o: notification-client.c + @if $(AM_V_P); then set -x; else echo " CC $@"; fi; \ + $(CC) $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(AM_CFLAGS) $(AM_CPPFLAGS) \ + $(CFLAGS) -c -o $@ $< + +notification-client: notification-client.o + @if $(AM_V_P); then set -x; else echo " CCLD $@"; fi; \ + $(CC) -o $@ $(LDFLAGS) $(CPPFLAGS) $(AM_LDFLAGS) $(AM_CFLAGS) \ + $(CFLAGS) notification-client.o tracepoint-trigger-example.a $(LIBS_NOTIFICATION_CLIENT) + +.PHONY: clean +clean: + rm -f *.o *.a instrumented-app notification-client diff --git a/doc/examples/trigger-on-event/README.md b/doc/examples/trigger-on-event/README.md new file mode 100644 index 000000000..1b7eefd74 --- /dev/null +++ b/doc/examples/trigger-on-event/README.md @@ -0,0 +1,82 @@ +# Trigger notification example + +## Description +This example is made-up of three executables. + +### `notification-client` + +``` +Usage: notification-client TRIGGER_NAME TRIGGER_NAME2 ... +``` + +A simple client that subscribes to the notifications emitted by the `TRIGGER_NAME` trigger. + +Multiple trigger names can be passed and subscribed to. + + +### `instrumented-app` + +An application that emits the `trigger_example:my_event` event every 2 seconds. + +### `demo.sh` + +This script adds a trigger named `demo_trigger` which emits a notification when +the user-space `trigger_example:my_event` event occurs. + +This script also adds a trigger named `demo_trigger_capture` which emits a +notification when the user-space `trigger_example:my_event` event occurs and +provides captured fields if present. + +Once the triggers have been setup, the notification-client is launched to print +all notifications emitted by the `demo_trigger` and `demo_trigger_capture` +trigger. + +## Building + +Simply run the included Makefile. + +## Running the example + +1) Launch a session daemon using: + ``` + $ lttng-sessiond + ``` +2) Launch the `demo.sh` script +3) Launch the `instrumented-app` + +The following output should be produced: + +``` +$ ./demo.sh +Registering a notification trigger named "demo_trigger" for the trigger_example:my_event user-space event +Trigger registered successfully. +Subscribed to notifications of trigger "demo_trigger" +[02-14-2020] 18:13:34.779652 - Received notification of event rule trigger "demo_trigger_capture" +Captured field values: + Unsigned int: 0, + CAPTURE UNAVAILABE +[02-14-2020] 18:13:34.779766 - Received notification of event rule trigger "demo_trigger" +[02-14-2020] 18:13:36.779798 - Received notification of event rule trigger "demo_trigger_capture" +Captured field values: + Unsigned int: 1, + CAPTURE UNAVAILABE +[02-14-2020] 18:13:36.779888 - Received notification of event rule trigger "demo_trigger" +[02-14-2020] 18:13:38.780234 - Received notification of event rule trigger "demo_trigger_capture" +Captured field values: + Unsigned int: 2, + CAPTURE UNAVAILABE +[02-14-2020] 18:13:38.780514 - Received notification of event rule trigger "demo_trigger" +[02-14-2020] 18:13:40.780574 - Received notification of event rule trigger "demo_trigger_capture" +Captured field values: + Unsigned int: 3, + CAPTURE UNAVAILABE +[02-14-2020] 18:13:40.780656 - Received notification of event rule trigger "demo_trigger" +``` + +``` +$ ./instrumented-app +[02-14-2020] 18:13:34.779433 - Tracing event "trigger_example:my_event" +[02-14-2020] 18:13:36.779693 - Tracing event "trigger_example:my_event" +[02-14-2020] 18:13:38.780010 - Tracing event "trigger_example:my_event" +[02-14-2020] 18:13:40.780286 - Tracing event "trigger_example:my_event" +``` diff --git a/doc/examples/trigger-on-event/demo.sh b/doc/examples/trigger-on-event/demo.sh new file mode 100755 index 000000000..0e6d9fa4b --- /dev/null +++ b/doc/examples/trigger-on-event/demo.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Copyright (C) 2020 Jérémie Galarneau +# +# SPDX-License-Identifier: MIT + +EVENT_NAME=trigger_example:my_event +TRIGGER_NAME=demo_trigger +TRIGGER_NAME_CAPTURE=demo_trigger_capture + +lttng list > /dev/null 2>&1 +if [ $? -ne 0 ]; then + echo "Could not connect to session daemon, are you sure it is running?" + exit 1 +fi + +echo "Registering a notification trigger named \"$TRIGGER_NAME\" for the $EVENT_NAME user-space event" +lttng add-trigger --id $TRIGGER_NAME --condition on-event --userspace $EVENT_NAME --action notify +lttng add-trigger --id $TRIGGER_NAME_CAPTURE --condition on-event --userspace $EVENT_NAME --capture 'iteration' --capture 'does_not_exist' --action notify + +./notification-client $TRIGGER_NAME $TRIGGER_NAME_CAPTURE + diff --git a/doc/examples/trigger-on-event/instrumented-app.c b/doc/examples/trigger-on-event/instrumented-app.c new file mode 100644 index 000000000..f6fd67de3 --- /dev/null +++ b/doc/examples/trigger-on-event/instrumented-app.c @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#include "tracepoint-trigger-example.h" + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + uint64_t i; + + for (i = 0; i < UINT64_MAX; i++) { + char time_str[64]; + struct timeval tv; + time_t the_time; + + gettimeofday(&tv, NULL); + the_time = tv.tv_sec; + + strftime(time_str, sizeof(time_str), "[%m-%d-%Y] %T", + localtime(&the_time)); + printf("%s.%ld - Tracing event \"trigger_example:my_event\"\n", time_str, tv.tv_usec); + + tracepoint(trigger_example, my_event, i); + sleep(2); + } + return 0; +} diff --git a/doc/examples/trigger-on-event/notification-client.c b/doc/examples/trigger-on-event/notification-client.c new file mode 100644 index 000000000..56facc026 --- /dev/null +++ b/doc/examples/trigger-on-event/notification-client.c @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int print_capture(const struct lttng_event_field_value *capture, + unsigned int indent_level); +static int print_array(const struct lttng_event_field_value *array, + unsigned int indent_level); + +static void indent(unsigned int indentation_level) +{ + unsigned int i; + for (i = 0; i < indentation_level; i++) { + printf(" "); + } +} + +static bool action_group_contains_notify( + const struct lttng_action *action_group) +{ + unsigned int i, count; + enum lttng_action_status status = + lttng_action_group_get_count(action_group, &count); + + if (status != LTTNG_ACTION_STATUS_OK) { + printf("Failed to get action count from action group\n"); + exit(1); + } + + for (i = 0; i < count; i++) { + const struct lttng_action *action = + lttng_action_group_get_at_index_const( + action_group, i); + const enum lttng_action_type action_type = + lttng_action_get_type(action); + + if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { + return true; + } + } + return false; +} + +static int print_capture(const struct lttng_event_field_value *capture, + unsigned int indent_level) +{ + int ret = 0; + enum lttng_event_field_value_status event_field_status; + enum lttng_event_field_value_type type; + uint64_t u_val; + int64_t s_val; + double d_val; + const char *string_val = NULL; + + indent(indent_level); + + switch (lttng_event_field_value_get_type(capture)) { + case LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT: + { + event_field_status = + lttng_event_field_value_unsigned_int_get_value( + capture, &u_val); + if (event_field_status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + ret = 1; + goto end; + } + + printf("Unsigned int: %" PRIu64, u_val); + break; + } + case LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_INT: + { + event_field_status = + lttng_event_field_value_signed_int_get_value( + capture, &s_val); + if (event_field_status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + ret = 1; + goto end; + } + + printf("Signed int: %" PRId64, s_val); + break; + } + case LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM: + { + event_field_status = + lttng_event_field_value_unsigned_int_get_value( + capture, &u_val); + if (event_field_status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + ret = 1; + goto end; + } + + printf("Unsigned enum: %" PRIu64, u_val); + break; + } + case LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM: + { + event_field_status = + lttng_event_field_value_signed_int_get_value( + capture, &s_val); + if (event_field_status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + ret = 1; + goto end; + } + + printf("Signed enum: %" PRId64, s_val); + break; + } + case LTTNG_EVENT_FIELD_VALUE_TYPE_REAL: + { + event_field_status = lttng_event_field_value_real_get_value( + capture, &d_val); + if (event_field_status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + ret = 1; + goto end; + } + + printf("Real: %lf", d_val); + break; + } + case LTTNG_EVENT_FIELD_VALUE_TYPE_STRING: + { + string_val = lttng_event_field_value_string_get_value(capture); + if (string_val == NULL) { + ret = 1; + goto end; + } + + printf("String: %s", string_val); + break; + } + case LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY: + printf("Array: [\n"); + print_array(capture, indent_level); + indent(indent_level); + printf("]\n"); + break; + case LTTNG_EVENT_FIELD_VALUE_TYPE_UNKNOWN: + case LTTNG_EVENT_FIELD_VALUE_TYPE_INVALID: + default: + ret = 1; + break; + } + +end: + return ret; +} + +static void print_unavailabe(unsigned int indent_level) +{ + indent(indent_level); + printf("CAPTURE UNAVAILABE"); +} + +static int print_array(const struct lttng_event_field_value *array, + unsigned int indent_level) +{ + int ret = 0; + enum lttng_event_field_value_status event_field_status; + unsigned int captured_field_count; + + event_field_status = lttng_event_field_value_array_get_length( + array, &captured_field_count); + if (event_field_status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + ret = 1; + goto end; + } + + for (unsigned int i = 0; i < captured_field_count; i++) { + const struct lttng_event_field_value *captured_field = NULL; + event_field_status = + lttng_event_field_value_array_get_element_at_index( + array, i, &captured_field); + if (event_field_status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + if (event_field_status == + LTTNG_EVENT_FIELD_VALUE_STATUS_UNAVAILABLE) { + print_unavailabe(indent_level + 1); + } else { + ret = 1; + goto end; + } + } + print_capture(captured_field, indent_level + 1); + + if (i + 1 < captured_field_count) { + printf(","); + } + printf("\n"); + } + +end: + return ret; +} + +static int print_captures(struct lttng_notification *notification) +{ + int ret = 0; + const struct lttng_evaluation *evaluation = + lttng_notification_get_evaluation(notification); + const struct lttng_condition *condition = + lttng_notification_get_condition(notification); + + /* Status */ + enum lttng_condition_status condition_status; + enum lttng_evaluation_status evaluation_status; + enum lttng_event_field_value_status event_field_status; + + const struct lttng_event_field_value *captured_field_array = NULL; + unsigned int expected_capture_field_count; + unsigned int captured_field_count; + + assert(lttng_evaluation_get_type(evaluation) == + LTTNG_CONDITION_TYPE_EVENT_RULE_HIT); + + condition_status = + lttng_condition_event_rule_get_capture_descriptor_count( + condition, + &expected_capture_field_count); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ret = 1; + goto end; + } + + if (expected_capture_field_count == 0) { + ret = 0; + goto end; + } + + evaluation_status = lttng_evaluation_get_captured_values( + evaluation, &captured_field_array); + if (evaluation_status != LTTNG_EVALUATION_STATUS_OK) { + ret = 1; + goto end; + } + + printf("Captured field values:\n"); + print_array(captured_field_array, 1); +end: + return ret; +} + +static int print_notification(struct lttng_notification *notification) +{ + int ret = 0; + const struct lttng_evaluation *evaluation = + lttng_notification_get_evaluation(notification); + const enum lttng_condition_type type = + lttng_evaluation_get_type(evaluation); + + switch (type) { + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + printf("Received consumed size notification\n"); + break; + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + printf("Received buffer usage notification\n"); + break; + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: + printf("Received session rotation ongoing notification\n"); + break; + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: + printf("Received session rotation completed notification\n"); + break; + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + { + const char *trigger_name; + enum lttng_evaluation_status evaluation_status; + char time_str[64]; + struct timeval tv; + time_t the_time; + + gettimeofday(&tv, NULL); + the_time = tv.tv_sec; + + strftime(time_str, sizeof(time_str), "[%m-%d-%Y] %T", + localtime(&the_time)); + printf("%s.%ld - ", time_str, tv.tv_usec); + + evaluation_status = + lttng_evaluation_event_rule_get_trigger_name( + evaluation, &trigger_name); + if (evaluation_status != LTTNG_EVALUATION_STATUS_OK) { + fprintf(stderr, "Failed to get trigger name of event rule notification\n"); + ret = -1; + break; + } + + printf("Received notification of event rule trigger \"%s\"\n", + trigger_name); + ret = print_captures(notification); + break; + } + default: + fprintf(stderr, "Unknown notification type (%d)\n", type); + } + + return ret; +} + +int main(int argc, char **argv) +{ + int ret; + struct lttng_triggers *triggers = NULL; + unsigned int count, i, j, subcription_count = 0, trigger_count; + enum lttng_trigger_status trigger_status; + struct lttng_notification_channel *notification_channel = NULL; + + if (argc < 2) { + fprintf(stderr, "Missing trigger name(s)\n"); + fprintf(stderr, "Usage: notification-client TRIGGER_NAME ..."); + ret = -1; + goto end; + } + + trigger_count = argc - 1; + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + if (!notification_channel) { + fprintf(stderr, "Failed to create notification channel\n"); + ret = -1; + goto end; + } + + ret = lttng_list_triggers(&triggers); + if (ret) { + fprintf(stderr, "Failed to list triggers\n"); + goto end; + } + + trigger_status = lttng_triggers_get_count(triggers, &count); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + fprintf(stderr, "Failed to get trigger count\n"); + ret = -1; + goto end; + } + + for (i = 0; i < count; i++) { + const struct lttng_trigger *trigger = + lttng_triggers_get_at_index(triggers, i); + const struct lttng_condition *condition = + lttng_trigger_get_const_condition(trigger); + const struct lttng_action *action = + lttng_trigger_get_const_action(trigger); + const enum lttng_action_type action_type = + lttng_action_get_type(action); + enum lttng_notification_channel_status channel_status; + const char *trigger_name = NULL; + bool subscribe = false; + + lttng_trigger_get_name(trigger, &trigger_name); + for (j = 0; j < trigger_count; j++) { + if (!strcmp(trigger_name, argv[j + 1])) { + subscribe = true; + break; + } + } + + if (!subscribe) { + continue; + } + + if (!((action_type == LTTNG_ACTION_TYPE_GROUP && + action_group_contains_notify(action)) || + action_type == LTTNG_ACTION_TYPE_NOTIFY)) { + printf("The action of trigger \"%s\" is not \"notify\", skipping.\n", + trigger_name); + continue; + } + + channel_status = lttng_notification_channel_subscribe( + notification_channel, condition); + if (channel_status == + LTTNG_NOTIFICATION_CHANNEL_STATUS_ALREADY_SUBSCRIBED) { + continue; + } + if (channel_status) { + fprintf(stderr, "Failed to subscribe to notifications of trigger \"%s\"\n", + trigger_name); + ret = -1; + goto end; + } + + printf("Subscribed to notifications of trigger \"%s\"\n", + trigger_name); + subcription_count++; + } + + if (subcription_count == 0) { + printf("No matching trigger with a notify action found.\n"); + ret = 0; + goto end; + } + + for (;;) { + struct lttng_notification *notification; + enum lttng_notification_channel_status channel_status; + + channel_status = + lttng_notification_channel_get_next_notification( + notification_channel, + ¬ification); + switch (channel_status) { + case LTTNG_NOTIFICATION_CHANNEL_STATUS_NOTIFICATIONS_DROPPED: + printf("Dropped notification\n"); + break; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_INTERRUPTED: + ret = 0; + goto end; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_OK: + break; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_CLOSED: + printf("Notification channel was closed by peer.\n"); + break; + default: + fprintf(stderr, "A communication error occurred on the notification channel.\n"); + ret = -1; + goto end; + } + + ret = print_notification(notification); + lttng_notification_destroy(notification); + if (ret) { + goto end; + } + } +end: + lttng_triggers_destroy(triggers); + lttng_notification_channel_destroy(notification_channel); + return !!ret; +} diff --git a/doc/examples/trigger-on-event/performance/Makefile b/doc/examples/trigger-on-event/performance/Makefile new file mode 100644 index 000000000..751548b4a --- /dev/null +++ b/doc/examples/trigger-on-event/performance/Makefile @@ -0,0 +1,57 @@ +# Copyright (C) 2020 Jérémie Galarneau +# +# THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED +# OR IMPLIED. ANY USE IS AT YOUR OWN RISK. +# +# Permission is hereby granted to use or copy this program for any +# purpose, provided the above notices are retained on all copies. +# Permission to modify the code and to distribute modified code is +# granted, provided the above notices are retained, and a notice that +# the code was modified is included with the above copyright notice. +# +# This Makefile is not using automake so that users may see how to build +# a program with tracepoint provider probes compiled as static libraries. +# +# This makefile is purposefully kept simple to support GNU and BSD make. + +LOCAL_CPPFLAGS += -I. +LIBS_INSTRUMENTED_APP = -ldl -llttng-ust +LIBS_NOTIFICATION_CLIENT = -ldl -llttng-ctl +LIBS_PERFORMANCE_CLIENT = -ldl -llttng-ust -llttng-ctl +AM_V_P := : +AR ?= ar + +all: producer consumer + +performance.o: performance.c performance.h + @if $(AM_V_P); then set -x; else echo " CC $@"; fi; \ + $(CC) $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(AM_CFLAGS) $(AM_CPPFLAGS) \ + $(CFLAGS) -c -o $@ $< + +performance.a: performance.o + @if $(AM_V_P); then set -x; else echo " AR $@"; fi; \ + $(AR) -rc $@ performance.o + +producer.o: producer.c + @if $(AM_V_P); then set -x; else echo " CC $@"; fi; \ + $(CC) $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(AM_CFLAGS) $(AM_CPPFLAGS) \ + $(CFLAGS) -c -o $@ $< + +consumer.o: consumer.c + @if $(AM_V_P); then set -x; else echo " CC $@"; fi; \ + $(CC) $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(AM_CFLAGS) $(AM_CPPFLAGS) \ + $(CFLAGS) -c -o $@ $< + +producer: producer.o performance.a + @if $(AM_V_P); then set -x; else echo " CCLD $@"; fi; \ + $(CC) -o $@ $(LDFLAGS) $(CPPFLAGS) $(AM_LDFLAGS) $(AM_CFLAGS) \ + $(CFLAGS) producer.o performance.a $(LIBS_PERFORMANCE_CLIENT) + +consumer: consumer.o performance.a + @if $(AM_V_P); then set -x; else echo " CCLD $@"; fi; \ + $(CC) -o $@ $(LDFLAGS) $(CPPFLAGS) $(AM_LDFLAGS) $(AM_CFLAGS) \ + $(CFLAGS) consumer.o performance.a $(LIBS_PERFORMANCE_CLIENT) + +.PHONY: clean +clean: + rm -f *.o *.a producer consumer diff --git a/doc/examples/trigger-on-event/performance/README.md b/doc/examples/trigger-on-event/performance/README.md new file mode 100644 index 000000000..8592f4139 --- /dev/null +++ b/doc/examples/trigger-on-event/performance/README.md @@ -0,0 +1,83 @@ +# Trigger notification end-to-end latency analysis + +## Description +This analysis is made-up of five executables. + +### `producer` + +``` +Usage: producer UNIQUE_ID NB_EVENT DELAY_MS +``` + +An application that emits `NB_EVENT` times the `performance:hit` event every +`DELAY_MS` milliseconds. + + +### `consumer` +``` +Usage: consumer UNIQUE_ID NB_EVENT TRIGGER_NAME +``` + +A simple notification client that subscribes to the notifications emitted by the +`TRIGGER_NAME` trigger. The consumer expects `NB_EVENT` notification and on each +valid reception emits a `performance:receive` event. + + +### `perform-experience.sh` + +``` +Usage: perform-experience.sh SOURCE_ID TRACE_DIRECTORY_NAME DELAY_MS NB_EVENT` +``` + +This script performs a complete end-to-end trigger latency experience with +`DELAY_MS` between each trigger hit and `NB_EVENT` times. + +The resulting lttng-ust trace is stored inside `$(pwd)/trace/TRACE_DIRECTORY_NAME` + +### `generate-data.sh` + +``` +Usage: generate-data.sh +``` + +This script performs all configured experiences and apply the customs workload +as necessary. + +The resulting traces are stored inside `$(pwd)/trace/` + +This script in its current form will run for about 25 hours. + +This script depends on `perform-experience.sh`. + +### `generate-graph.sh` + +``` +Usage: generate-graph.sh +``` + +This script generate all histograms and saved them individually as pdf files. It +also generate a `summary.pdf` files that contains all pdfs in order of trigger frequency. + +This script does not have to run on the machine that produced the data. + +This script requires the presence of the "trace/" folder to work. + +This script depends on the `bt_plugin_plot.py` babeltrace 2 plugins. Hence this +script requires Babeltrace 2 with python bindings and python plugin support. + +The `bt_plugin_plot.py` requires `matplotlib`. + + +## Building + +Simply run the included Makefile. + +## Running the complete + +1) Launch a session daemon using: + ``` + $ lttng-sessiond + ``` +2) Launch `generate-data.sh` +3) Wait ~25 hours +3) Launch `generate-graph.sh` diff --git a/doc/examples/trigger-on-event/performance/bt_plugin_plot.py b/doc/examples/trigger-on-event/performance/bt_plugin_plot.py new file mode 100644 index 000000000..626a9a11a --- /dev/null +++ b/doc/examples/trigger-on-event/performance/bt_plugin_plot.py @@ -0,0 +1,260 @@ +import bt2 +import itertools +import matplotlib.pyplot as plt +import sys +import statistics +import csv +from collections import defaultdict + + +class DataLogger(object): + def __init__(self, name="Untitled"): + self._name = name + + def get_name(self): + return self._name + + def get_x_data(self): + raise NotImplementedError + + def get_y_data(self): + raise NotImplementedError + + def received_event(self, ts, event): + raise NotImplementedError + + +class DurationDataLogger(DataLogger): + """ + This class allow to create a duration histogram for the given pair of + event and unique tuple key generator. + + """ + def __init__(self, start_event, end_event, *args, **kwargs): + super(DurationDataLogger, self).__init__(*args, **kwargs) + + (self._event_start, self._start_fields) = start_event + (self._event_end, self._end_fields) = end_event + + self._durations = [] + self._pair = dict() + + def get_x_data(self): + return self._durations + + def received_event(self, ts, event): + if event.name == self._event_start: + key = () + for field in self._start_fields: + value = event.payload_field[str(field)] + key = key + (value,) + self._pair[key] = ts + return + + if event.name == self._event_end: + key = () + for field in self._end_fields: + value = event.payload_field[str(field)] + key = key + (value,) + + if key not in self._pair: + print("unmatched end event") + return + + start_ts = self._pair[key] + duration = (ts - start_ts) / 1000000.0 + self._durations.append(duration) + +class DurationCSVDataLogger(DataLogger): + """ + This class allow to create a duration histogram for the given csv. + """ + def __init__(self, filepath, *args, **kwargs): + super(DurationCSVDataLogger, self).__init__(*args, **kwargs) + + self._filepath = filepath + + + self._durations = [] + with open(filepath, newline='') as file: + reader = csv.reader(file, quoting=csv.QUOTE_NONE) + next(reader) + for row in reader: + self._durations.append(float(row[0])) + + def get_x_data(self): + return self._durations + + def received_event(self, ts, event): + return + + +class Plot(object): + def __init__( + self, loggers, title="Untitled", x_label="Untitled", y_label="Untitled" + ): + self._loggers = loggers + self._title = title + self._x_label = x_label + self._y_label = y_label + + def received_event(self, ts, event): + for logger in self._loggers: + logger.received_event(ts, event) + + def plot(self): + raise NotImplementedError + + def generate_csv(self): + raise NotImplementedError + + @staticmethod + def _format_filename(title, ext): + title = title.lower() + title = "".join("-" if not c.isalnum() else c for c in title) + title = "".join( + ["".join(j) if i != "-" else i for (i, j) in itertools.groupby(title)] + ) + return f"{title}.{ext}" + +class HistogramPlot(Plot): + def __init__(self, *args, **kwargs): + super(HistogramPlot, self).__init__(*args, **kwargs) + + @staticmethod + def get_statistics_header(): + return ["minimum", "maximum", "mean", "pstdev", "count"] + + @staticmethod + def get_statistics(samples): + stats = [] + stats.append('%f' % min(samples)) + stats.append('%f' % max(samples)) + stats.append('%f' % statistics.mean(samples)) + stats.append('%f' % statistics.pstdev(samples)) + stats.append('%d' % len(samples)) + return stats + + def plot(self): + sys.argv = [''] + complete_set = []; + logger_statistic = defaultdict(dict) + + figure = plt.figure() + plt.title(self._title) + plt.xlabel(self._x_label, figure=figure) + plt.ylabel(self._y_label, figure=figure) + plt.yscale('log', nonposy='clip') + + table_rows_label = [] + table_celltext = [] + for logger in self._loggers: + x = logger.get_x_data() + table_rows_label.append(logger.get_name()) + table_celltext.append(HistogramPlot.get_statistics(x)) + + complete_set +=x; + plt.hist(x, bins='auto', alpha=0.5, figure=figure, label=logger.get_name()) + + table_rows_label.append("all") + table_celltext.append(HistogramPlot.get_statistics(complete_set)) + the_table = plt.table(cellText=table_celltext, + rowLabels=table_rows_label, + colLabels=HistogramPlot.get_statistics_header(), + loc='bottom', + bbox=[0.0,-0.45,1,.28], + ) + + the_table.auto_set_font_size(False) + the_table.set_fontsize(8) + + plt.subplots_adjust(bottom=0.20) + plt.legend(loc='center left', bbox_to_anchor=(1, 0.5)) + plt.savefig(Plot._format_filename(self._title, "pdf"), bbox_inches="tight") + + def generate_csv(self): + for logger in self._loggers: + x_data = logger.get_x_data() + with open(Plot._format_filename(self._title, "%s.csv" % logger.get_name()), 'w', newline='') as export: + wr = csv.writer(export, quoting=csv.QUOTE_NONE) + wr.writerow([self._x_label]) + for x in x_data: + wr.writerow([x]) + + +@bt2.plugin_component_class +class PlotSink(bt2._UserSinkComponent): + def __init__(self, config, params, obj): + self._plots = [] + + if "histograms" in params: + for plot in params["histograms"]: + self._plots.append(PlotSink.create_histogram(plot)) + + self._add_input_port("in") + + def _user_consume(self): + msg = next(self._iter) + if type(msg) in [ + bt2._PacketBeginningMessageConst, + bt2._PacketEndMessageConst, + bt2._StreamBeginningMessageConst, + bt2._StreamEndMessageConst, + ]: + return + + ts = msg.default_clock_snapshot.value + for plot in self._plots: + plot.received_event(ts, msg.event) + + def _user_finalize(self): + {plot.plot() for plot in self._plots} + {plot.generate_csv () for plot in self._plots} + return + + def _user_graph_is_configured(self): + self._iter = self._create_message_iterator(self._input_ports["in"]) + + @staticmethod + def create_histogram(params): + loggers = [] + for logger in params[3]: + if logger[0] == "duration": + logger = PlotSink.create_duration_logger(logger) + elif logger[0] == "duration-csv": + logger = PlotSink.create_duration_logger_csv(logger) + else: + raise ValueError + + loggers.append(logger) + + title = str(params[0]) + x_label = str(params[1]) + y_label = str(params[2]) + + return HistogramPlot(loggers, title=title, x_label=x_label, + y_label=y_label) + + @staticmethod + def create_duration_logger(params): + return DurationDataLogger( + (str(params[2]), params[3]), + (str(params[4]), params[5]), + name=str(params[1]), + ) + + def create_duration_logger_csv(params): + return DurationCSVDataLogger( + str(params[2]), + name=str(params[1]), + ) + + +bt2.register_plugin( + module_name=__name__, + name="plot", + description="Plot Sink", + author="EfficiOS inc.", + license="GPL", + version=(1, 0, 0), +) diff --git a/doc/examples/trigger-on-event/performance/consumer.c b/doc/examples/trigger-on-event/performance/consumer.c new file mode 100644 index 000000000..65d2b59d5 --- /dev/null +++ b/doc/examples/trigger-on-event/performance/consumer.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * Copyright (C) 2020 Jonathan Rajotte-Julien + * + * + * SPDX-License-Identifier: MIT + * + */ +#include "performance.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool action_group_contains_notify(const struct lttng_action *action_group) +{ + unsigned int i, count; + enum lttng_action_status status = + lttng_action_group_get_count(action_group, &count); + + if (status != LTTNG_ACTION_STATUS_OK) { + printf("Failed to get action count from action group\n"); + exit(1); + } + + for (i = 0; i < count; i++) { + const struct lttng_action *action = + lttng_action_group_get_at_index_const( + action_group, i); + const enum lttng_action_type action_type = + lttng_action_get_type(action); + + if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { + return true; + } + } + return false; +} + +int main(int argc, char **argv) +{ + int ret; + int id, nb_reception; + struct lttng_triggers *triggers = NULL; + unsigned int count, i, subcription_count = 0; + enum lttng_trigger_status trigger_status; + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_notification *notification; + enum lttng_notification_channel_status channel_status; + const struct lttng_evaluation *evaluation; + enum lttng_condition_type type; + + if (argc != 4) { + fprintf(stderr, "Missing unique_id\n"); + fprintf(stderr, "Missing nb_event\n"); + fprintf(stderr, "Missing trigger name\n"); + fprintf(stderr, "Usage: notification-client TRIGGER_NAME\n"); + ret = 1; + goto end; + } + + id = atoi(argv[1]); + nb_reception = atoi(argv[2]); + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + if (!notification_channel) { + fprintf(stderr, "Failed to create notification channel\n"); + ret = -1; + goto end; + } + + ret = lttng_list_triggers(&triggers); + if (ret) { + fprintf(stderr, "Failed to list triggers\n"); + goto end; + } + + trigger_status = lttng_triggers_get_count(triggers, &count); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + fprintf(stderr, "Failed to get trigger count\n"); + ret = -1; + goto end; + } + + for (i = 0; i < count; i++) { + const struct lttng_trigger *trigger = + lttng_triggers_get_at_index(triggers, i); + const struct lttng_condition *condition = + lttng_trigger_get_const_condition(trigger); + const struct lttng_action *action = + lttng_trigger_get_const_action(trigger); + const enum lttng_action_type action_type = + lttng_action_get_type(action); + enum lttng_notification_channel_status channel_status; + const char *trigger_name = NULL; + + lttng_trigger_get_name(trigger, &trigger_name); + if (strcmp(trigger_name, argv[3])) { + continue; + } + + if (!((action_type == LTTNG_ACTION_TYPE_GROUP && + action_group_contains_notify(action)) || + action_type == LTTNG_ACTION_TYPE_NOTIFY)) { + printf("The action of trigger \"%s\" is not \"notify\", skipping.\n", + trigger_name); + continue; + } + + channel_status = lttng_notification_channel_subscribe( + notification_channel, condition); + if (channel_status) { + fprintf(stderr, "Failed to subscribe to notifications of trigger \"%s\"\n", + trigger_name); + ret = -1; + goto end; + } + + printf("Subscribed to notifications of trigger \"%s\"\n", + trigger_name); + subcription_count++; + } + + if (subcription_count == 0) { + printf("No matching trigger with a notify action found.\n"); + ret = 0; + goto end; + } + + for (int i = 0; i < nb_reception; i++) { + channel_status = + lttng_notification_channel_get_next_notification( + notification_channel, + ¬ification); + switch (channel_status) { + case LTTNG_NOTIFICATION_CHANNEL_STATUS_NOTIFICATIONS_DROPPED: + printf("Dropped notification\n"); + sleep(1); + continue; + break; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_INTERRUPTED: + ret = 0; + goto end; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_OK: + break; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_CLOSED: + printf("Notification channel was closed by peer.\n"); + break; + default: + fprintf(stderr, "A communication error occurred on the notification channel.\n"); + ret = -1; + goto end; + } + + evaluation = lttng_notification_get_evaluation(notification); + type = lttng_evaluation_get_type(evaluation); + + if (type != LTTNG_CONDITION_TYPE_EVENT_RULE_HIT) { + assert(0); + } + + tracepoint(performance, receive, id, i); + lttng_notification_destroy(notification); + if (ret) { + goto end; + } + } + +end: + lttng_triggers_destroy(triggers); + lttng_notification_channel_destroy(notification_channel); + return !!ret; +} diff --git a/doc/examples/trigger-on-event/performance/generate-data.sh b/doc/examples/trigger-on-event/performance/generate-data.sh new file mode 100755 index 000000000..58065a57d --- /dev/null +++ b/doc/examples/trigger-on-event/performance/generate-data.sh @@ -0,0 +1,50 @@ +#!/bin/bash -x +# +# Copyright (C) 2020 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: MIT + +cpu_stressor=$(nproc) + +while read -r load delay count; do + stress-ng --cpu-load "$load" --cpu "$cpu_stressor" & + stress_ng_id=$! + + # Let the workload stabilize + sleep 5 + + ./perform-experience.sh 0 "${load}_cpuload_${delay}ms" "$delay" "$count" + kill "$stress_ng_id" + wait +done << EOF +0 1 10000 +25 1 10000 +50 1 10000 +75 1 10000 +100 1 10000 +0 10 5000 +25 10 5000 +50 10 5000 +75 10 5000 +100 10 5000 +0 100 6000 +25 100 6000 +50 100 6000 +75 100 6000 +100 100 6000 +0 1000 6000 +25 1000 6000 +50 1000 6000 +75 1000 6000 +100 1000 6000 +0 10000 600 +25 10000 600 +50 10000 600 +75 10000 600 +100 10000 600 +0 60000 100 +25 60000 100 +50 60000 100 +75 60000 100 +100 60000 60 +EOF diff --git a/doc/examples/trigger-on-event/performance/generate-graph.sh b/doc/examples/trigger-on-event/performance/generate-graph.sh new file mode 100755 index 000000000..3140eb57c --- /dev/null +++ b/doc/examples/trigger-on-event/performance/generate-graph.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# +# Copyright (C) 2020 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: MIT + +plugin_path=$(dirname "$0") + +DATA1="[\"duration\", \"D1\", \"performance:hit\", [\"source\", \"iteration\"], \"performance:receive\", [\"source\", \"iteration\"]]" + +while read -r load delay count; do + S=$(echo "scale=3; 1 / ( $delay / 1000 )" | bc | awk '{printf "%.3f", $0}' ); + hz=${S/.000/} + hz_title=${hz/./-} + + echo "Graphing Hz: ${hz} CPU: ${load}" + + PLOT1="[\"Trigger latency, Freq:${hz}Hz, CPU load: ${load}%\", \"T (ms)\", \"count\", [$DATA1]]" + + babeltrace2 --plugin-path="$plugin_path" --component sink.plot.PlotSink \ + --params="histograms=[$PLOT1]" \ + "./trace/${load}_cpuload_${delay}ms" +done << EOF +0 1 10000 +25 1 10000 +50 1 10000 +75 1 10000 +100 1 10000 +0 10 5000 +25 10 5000 +50 10 5000 +75 10 5000 +100 10 5000 +0 100 6000 +25 100 6000 +50 100 6000 +75 100 6000 +100 100 6000 +0 1000 1500 +25 1000 1500 +50 1000 1500 +75 1000 1500 +100 1000 1500 +0 10000 300 +25 10000 300 +50 10000 300 +75 10000 300 +100 10000 300 +0 60000 50 +25 60000 50 +50 60000 50 +75 60000 50 +100 60000 50 +EOF + +pdf_unite="" +csvs="" +# Generate united graph and base pdf list to unite +while read -r delay ; do + S=$(echo "scale=3; 1 / ( $delay / 1000 )" | bc | awk '{printf "%.3f", $0}' ); + hz=${S/.000/} + hz_title=${hz/./-} + local_pdf_unite="" + echo "Combining graphs for Hz: ${hz} hz_title: $hz_title" + + loggers="" + for load in 100 75 50 25 0; do + path=trigger-latency-freq-${hz_title}hz-cpu-load-${load}-.D1.csv + csvs="$csvs $path" + loggers="[\"duration-csv\", \"${load}% CPU\", \"${path}\"], $loggers" + local_pdf_unite="${local_pdf_unite} trigger-latency-freq-${hz_title}hz-cpu-load-${load}-.pdf" + done + pdf_unite="$pdf_unite $local_pdf_unite" + + PLOT1="[\"Trigger latency, Freq:${hz}Hz\", \"T (ms)\", \"count\", [$loggers]]" + babeltrace2 --plugin-path="$plugin_path" --component sink.plot.PlotSink \ + --params="histograms=[$PLOT1]" \ + "./trace/0_cpuload_${delay}ms" +done << EOF +1 +10 +100 +1000 +10000 +60000 +EOF + +# Add united graphs to the pdfunite cmd +while read -r delay ; do + S=$(echo "scale=3; 1 / ( $delay / 1000 )" | bc | awk '{printf "%.3f", $0}' ); + hz=${S/.000/} + hz_title=${hz/./-} + + pdf_unite="trigger-latency-freq-${hz_title}hz.pdf $pdf_unite" + +done << EOF +60000 +10000 +1000 +100 +10 +1 +EOF + +rm -rf $csvs + +pdfunite $pdf_unite summary.pdf + diff --git a/doc/examples/trigger-on-event/performance/perform-experience.sh b/doc/examples/trigger-on-event/performance/perform-experience.sh new file mode 100755 index 000000000..02e2cd448 --- /dev/null +++ b/doc/examples/trigger-on-event/performance/perform-experience.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# +# Copyright (C) 2020 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: MIT + +EVENT_NAME_HIT=performance:hit +EVENT_NAME_RECEIVE=performance:receive +TRIGGER_NAME=performance_hit + +if [ -z "$1" ]; then + echo "missing source id int value" + exit 1 +fi + +if [ -z "$2" ]; then + echo "missing trace directory name" + exit 1 +fi + +if [ -z "$3" ]; then + echo "missing loop delay" + exit 1 +fi + +if [ -z "$4" ]; then + echo "missing loop count" + exit 1 +fi + +key_id="$1" +trace_directory="$(pwd)/trace/$2" +delay=$3 +count=$4 + +if ! lttng list > /dev/null 2>&1 ; then + echo "Could not connect to session daemon, are you sure it is running?" + exit 1 +fi + +lttng create performance --output="$trace_directory" +lttng enable-event -u $EVENT_NAME_HIT,$EVENT_NAME_RECEIVE -s performance +lttng start + +filter="source==$key_id" +lttng add-trigger --id ${TRIGGER_NAME} --condition on-event --userspace $EVENT_NAME_HIT --filter="$filter" --action notify + +./consumer "$key_id" "$count" $TRIGGER_NAME & + +# Cheap way to synchronize and ensure that the consumer is ready to consume +sleep 2 + +./producer "$key_id" "$count" "$delay" & + +wait + +lttng remove-trigger ${TRIGGER_NAME} + +lttng stop +lttng destroy performance + diff --git a/doc/examples/trigger-on-event/performance/performance.c b/doc/examples/trigger-on-event/performance/performance.c new file mode 100644 index 000000000..311b0349d --- /dev/null +++ b/doc/examples/trigger-on-event/performance/performance.c @@ -0,0 +1,10 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#define TRACEPOINT_DEFINE +#define TRACEPOINT_CREATE_PROBES +#include "performance.h" diff --git a/doc/examples/trigger-on-event/performance/performance.h b/doc/examples/trigger-on-event/performance/performance.h new file mode 100644 index 000000000..79e0a36a8 --- /dev/null +++ b/doc/examples/trigger-on-event/performance/performance.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#undef TRACEPOINT_PROVIDER +#define TRACEPOINT_PROVIDER performance + +#undef TRACEPOINT_INCLUDE +#define TRACEPOINT_INCLUDE "./performance.h" + +#if !defined(_TRACEPOINT_PERFORMANCE_H) || defined(TRACEPOINT_HEADER_MULTI_READ) +#define _TRACEPOINT_PERFORMANCE_H + +#include + +TRACEPOINT_EVENT(performance, hit, + TP_ARGS(int, source, + int, iteration), + TP_FIELDS( + ctf_integer(uint64_t, source, source) + ctf_integer(uint64_t, iteration, iteration) + ) +) + +TRACEPOINT_EVENT(performance, receive, + TP_ARGS(int, source, + int, iteration), + TP_FIELDS( + ctf_integer(uint64_t, source, source) + ctf_integer(uint64_t, iteration, iteration) + ) +) + +#endif /* _TRACEPOINT_PERFORMANCE_H */ + +#include diff --git a/doc/examples/trigger-on-event/performance/producer.c b/doc/examples/trigger-on-event/performance/producer.c new file mode 100644 index 000000000..3aefaae20 --- /dev/null +++ b/doc/examples/trigger-on-event/performance/producer.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * Copyright (C) 2020 Jonathan Rajotte-Julien + * + * SPDX-License-Identifier: MIT + * + */ +#include "performance.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + int ret = 0; + int nb_hit; + int id; + long long sleep_ms; + struct timespec sleep_time; + struct timespec sleep_rm; + + if (argc != 4) { + fprintf(stderr, "Missing unique_id\n"); + fprintf(stderr, "Missing number of event \n"); + fprintf(stderr, "Missing delay between event in ms \n"); + fprintf(stderr, "Usage: producer id bn_event delay_ms\n"); + ret = 1; + goto end; + } + + id = atoi(argv[1]); + nb_hit = atoi(argv[2]); + sleep_ms = atoll(argv[3]); + + sleep_time.tv_sec = sleep_ms / 1000; + sleep_time.tv_nsec = (sleep_ms % 1000) * 1000000; + + for (int i = 0; i < nb_hit; i++) { + tracepoint(performance, hit, 0, i); + nanosleep(&sleep_time, &sleep_rm); + } + +end: + return !!ret; +} diff --git a/doc/examples/trigger-on-event/tracepoint-trigger-example.c b/doc/examples/trigger-on-event/tracepoint-trigger-example.c new file mode 100644 index 000000000..3f781e3b8 --- /dev/null +++ b/doc/examples/trigger-on-event/tracepoint-trigger-example.c @@ -0,0 +1,10 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#define TRACEPOINT_DEFINE +#define TRACEPOINT_CREATE_PROBES +#include "tracepoint-trigger-example.h" diff --git a/doc/examples/trigger-on-event/tracepoint-trigger-example.h b/doc/examples/trigger-on-event/tracepoint-trigger-example.h new file mode 100644 index 000000000..e82e3c704 --- /dev/null +++ b/doc/examples/trigger-on-event/tracepoint-trigger-example.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#undef TRACEPOINT_PROVIDER +#define TRACEPOINT_PROVIDER trigger_example + +#undef TRACEPOINT_INCLUDE +#define TRACEPOINT_INCLUDE "./tracepoint-trigger-example.h" + +#if !defined(_TRACEPOINT_TRIGGER_EXAMPLE_H) || defined(TRACEPOINT_HEADER_MULTI_READ) +#define _TRACEPOINT_TRIGGER_EXAMPLE_H + +#include + +TRACEPOINT_EVENT(trigger_example, my_event, + TP_ARGS(int, iteration), + TP_FIELDS( + ctf_integer(uint64_t, iteration, iteration) + ) +) + +#endif /* _TRACEPOINT_TRIGGER_EXAMPLE_H */ + +#include diff --git a/doc/man/Makefile.am b/doc/man/Makefile.am index 80bedbadf..5ae6ffbe3 100644 --- a/doc/man/Makefile.am +++ b/doc/man/Makefile.am @@ -36,7 +36,11 @@ MAN1_NAMES = \ lttng-rotate \ lttng-enable-rotation \ lttng-disable-rotation \ - lttng-clear + lttng-clear \ + lttng-add-trigger \ + lttng-remove-trigger \ + lttng-list-triggers + MAN3_NAMES = MAN8_NAMES = lttng-sessiond lttng-relayd MAN1_NO_ASCIIDOC_NAMES = diff --git a/doc/man/lttng-add-trigger.1.txt b/doc/man/lttng-add-trigger.1.txt new file mode 100644 index 000000000..5e2e90e43 --- /dev/null +++ b/doc/man/lttng-add-trigger.1.txt @@ -0,0 +1,207 @@ +lttng-add-trigger(1) +===================== +:revdate: 27 May 2020 + + +NAME +---- +lttng-add-trigger - Create LTTng triggers + + +SYNOPSIS +-------- + +[verse] +*lttng* ['linkgenoptions:(GENERAL OPTIONS)'] *add-trigger* [--id ID] + [--fire-every N] [--fire-once-after N] + --condition CONDITION-NAME CONDITION-ARGS + --action ACTION-NAME ACTION-ARGS + [--action ACTION-NAME ACTION-ARGS]... + + +DESCRIPTION +----------- + +The `lttng add-trigger` command is used to create triggers. A +trigger is an association between a *condition* and one or more +*actions*. When the condition associated to a trigger is met, the +actions associated to that trigger are executed. The tracing does not +have to be active for the conditions to be met, and triggers are +independent from tracing sessions. + +By default, a trigger fires every time its condition is met. The +'--fire-every' and '--fire-once-after' options can be used to change +this mode. + +The syntax by which conditions and actions are specified is described +below. + +[[conditions]] +Conditions +~~~~~~~~~~ + +Conditions are specified with the `--condition` option, followed by a +condition name, and possibly some more arguments, depending on the +specific condition. There must be exactly one condition given in the +`lttng add-trigger` invocation. + +The available conditions are: + +Event rule: `on-event [event rule arguments]`:: + This type of condition is met when the tracer encounters an event + matching the given even rule. The arguments describing the event + rule are the same as those describing the event rules of the + man:lttng-enable-event(1) command, with these exceptions: + + - It is not possible to use filter operands that use values from + the context. + ++ +Fields to capture can be specified with the option:--capture option, followed by +a capture expression. Zero or more captures can be configured. See the +<> section below for more information. + +[[actions]] +Actions +~~~~~~~ + +Actions are specified with the `--action` option, followed by an action +name, and possibly some more arguments, depending on the specific +action. There must be at least one action given in the +`lttng add-trigger` invocation. + +The available actions are: + +Notify: *notify*:: + This action causes the LTTng session daemon to send a notification, + through its notification mechanism (see man:lttng-sessiond(8)). + Some details about the condition evaluation are sent along with the + notification. + +Start session: *start-session* session-name:: + This action causes the LTTng session daemon to start tracing for the + session with the given name. If no session with the given name exist + at the time the condition is met, nothing is done. + +Stop session: *stop-session* session-name:: + This action causes the LTTng session daemon to stop tracing for the + session with the given name. If no session with the given name exist + at the time the condition is met, nothing is done. + +Rotate session: *rotate-session* session-name:: + This action causes the LTTng session daemon to rotate the session + with the given name. See man:lttng-rotate(1) for more information + about the session rotation concept. If no session with the given + name exist at the time the condition is met, nothing is done. + +Snapshot session: *snapshot-session* session-name:: + This action causes the LTTng session daemon to take a snapshot of the + session with the given name. See man:lttng-snapshot(1) for more + information about the session snapshot concept. If no session with + the given name exist at the time the condition is met, nothing is + done. + + +[[capture-expr]] +Capture expression +~~~~~~~~~~~~~~~~~~ + +A capture expression can be specified with the option:--capture option when +creating a new on-event condition. If the capture expression corresponds with an +event's field when tracing, the runtime dynamic value corresponding to the +capture expression is captured. + +NOTE: Make sure to **single-quote** the capture expression when running +the command from a shell, as capture expressions typically include +characters having a special meaning for most shells. + +* Supported field types: + - integer, + - unsigned integer, + - floating point value, + - fixed-size array of integers, + - variable-size array of integers (sequence), + - enumeration, + - text string, + - element of any allowing previous type. + +* The dynamic value of an event field is captured by using its name as a C + identifier. ++ +The square bracket notation is available, like in the C +language, to access array/sequence field. +Only a constant, positive integer number can be used within square +brackets. If the index is out of bounds, the capture expression +evaluates to `unavailable`. ++ +An enumeration field's value is an integer. ++ +When the capture's field does not exist, the capture expression +evaluates to `unavailable`. ++ +Examples: `my_field`, `target_cpu`, `seq[7]` + +* The dynamic value of a statically-known context field is captured by + prefixing its name with `$ctx.`. See man:lttng-add-context(1) to get a list of + available contexts. ++ +When the expression's statically-known context field does not exist, +the capture expression evaluates to `unavailable`. ++ +Examples: `$ctx.prio`, `$ctx.preemptible`, +`$ctx.perf:cpu:stalled-cycles-frontend`. ++ +NOTE: The statically-known context field does NOT need to be added using the +man:lttng-add-context(1) command. The statically-known context fields are +always available in the context of triggers. + +* The dynamic value of an application-specific context field is captured by + prefixing its name with `$app.` (follows the format used to add such a context + field with the man:lttng-add-context(1) command). ++ +When the expression's application-specific context field does not exist, +the capture expression evaluates to `unavailable`. ++ +Example: `$app.server:cur_user`. ++ +NOTE: The application-specific context field does NOT need to be added using the +man:lttng-add-context(1) command. The application-specific context fields fields +are always available in the context of triggers. + + +OPTIONS +------- + +option:--condition:: + Define the condition for the trigger. See the + <> section for more details. + +option:--action:: + Define an action for the trigger. See the <> + section for more details. + +option:--id='ID':: + Set the id of the trigger to 'ID'. If omitted, an id will + automatically be assigned to the trigger by the session daemon. ++ +If a trigger with the specified 'ID' already exists, the trigger +creation will fail. + +option:--fire-every 'N':: + Execute the trigger's actions every 'N' times the condition is met. + +option:--fire-once-after 'N':: + Execute the trigger's actions once after 'N' times the condition is + met, then never after that. + +include::common-cmd-help-options.txt[] + + +include::common-cmd-footer.txt[] + + +SEE ALSO +-------- +man:lttng-list-triggers(1), +man:lttng-remove-trigger(1), +man:lttng(1) diff --git a/doc/man/lttng-list-triggers.1.txt b/doc/man/lttng-list-triggers.1.txt new file mode 100644 index 000000000..2ab3085cd --- /dev/null +++ b/doc/man/lttng-list-triggers.1.txt @@ -0,0 +1,37 @@ +lttng-list-triggers(1) +====================== +:revdate: 20 January 2020 + + +NAME +---- +lttng-list-triggers - List LTTng triggers + + +SYNOPSIS +-------- + +[verse] +*lttng* ['linkgenoptions:(GENERAL OPTIONS)'] *list-triggers* + + +DESCRIPTION +----------- + +The `lttng list-triggers` command list the existing triggers. + + +OPTIONS +------- + +include::common-cmd-help-options.txt[] + + +include::common-cmd-footer.txt[] + + +SEE ALSO +-------- +man:lttng-add-trigger(1), +man:lttng-remove-trigger(1), +man:lttng(1) diff --git a/doc/man/lttng-remove-trigger.1.txt b/doc/man/lttng-remove-trigger.1.txt new file mode 100644 index 000000000..645ef152f --- /dev/null +++ b/doc/man/lttng-remove-trigger.1.txt @@ -0,0 +1,38 @@ +lttng-remove-trigger(1) +======================== +:revdate: 20 January 2020 + + +NAME +---- +lttng-remove-trigger - Remove LTTng triggers + + +SYNOPSIS +-------- + +[verse] +*lttng* ['linkgenoptions:(GENERAL OPTIONS)'] *remove-trigger* 'ID' + + +DESCRIPTION +----------- + +The `lttng remove-trigger` command removes the trigger with the given +'ID'. + + +OPTIONS +------- + +include::common-cmd-help-options.txt[] + + +include::common-cmd-footer.txt[] + + +SEE ALSO +-------- +man:lttng-add-trigger(1), +man:lttng-list-trigger(1), +man:lttng(1) diff --git a/include/Makefile.am b/include/Makefile.am index 1f89b1bf6..f192865ae 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -103,6 +103,8 @@ lttnginclude_HEADERS = \ lttng/channel.h \ lttng/domain.h \ lttng/event.h \ + lttng/event-expr.h \ + lttng/event-field-value.h \ lttng/handle.h \ lttng/session.h \ lttng/lttng-error.h \ @@ -121,11 +123,17 @@ lttnginclude_HEADERS = \ lttngactioninclude_HEADERS= \ lttng/action/action.h \ - lttng/action/notify.h + lttng/action/group.h \ + lttng/action/notify.h \ + lttng/action/rotate-session.h \ + lttng/action/snapshot-session.h \ + lttng/action/start-session.h \ + lttng/action/stop-session.h lttngconditioninclude_HEADERS= \ lttng/condition/condition.h \ lttng/condition/buffer-usage.h \ + lttng/condition/event-rule.h \ lttng/condition/session-consumed-size.h \ lttng/condition/session-rotation.h \ lttng/condition/evaluation.h @@ -137,15 +145,29 @@ lttngnotificationinclude_HEADERS= \ lttngtriggerinclude_HEADERS= \ lttng/trigger/trigger.h +lttngeventruleinclude_HEADERS= \ + lttng/event-rule/event-rule.h \ + lttng/event-rule/kprobe.h \ + lttng/event-rule/kretprobe.h \ + lttng/event-rule/syscall.h \ + lttng/event-rule/uprobe.h \ + lttng/event-rule/tracepoint.h + noinst_HEADERS = \ lttng/snapshot-internal.h \ lttng/health-internal.h \ lttng/save-internal.h \ lttng/load-internal.h \ lttng/action/action-internal.h \ + lttng/action/group-internal.h \ lttng/action/notify-internal.h \ + lttng/action/rotate-session-internal.h \ + lttng/action/snapshot-session-internal.h \ + lttng/action/start-session-internal.h \ + lttng/action/stop-session-internal.h \ lttng/condition/condition-internal.h \ lttng/condition/buffer-usage-internal.h \ + lttng/condition/event-rule-internal.h \ lttng/condition/session-consumed-size-internal.h \ lttng/condition/evaluation-internal.h \ lttng/condition/session-rotation-internal.h \ @@ -154,12 +176,21 @@ noinst_HEADERS = \ lttng/endpoint-internal.h \ lttng/notification/channel-internal.h \ lttng/channel-internal.h \ + lttng/domain-internal.h \ lttng/event-internal.h \ + lttng/event-expr-internal.h \ + lttng/event-field-value-internal.h \ lttng/rotate-internal.h \ lttng/ref-internal.h \ lttng/location-internal.h \ lttng/userspace-probe-internal.h \ lttng/session-internal.h \ lttng/session-descriptor-internal.h \ + lttng/event-rule/event-rule-internal.h \ + lttng/event-rule/kprobe-internal.h \ + lttng/event-rule/kretprobe-internal.h \ + lttng/event-rule/uprobe-internal.h \ + lttng/event-rule/syscall-internal.h \ + lttng/event-rule/tracepoint-internal.h \ version.h \ version.i diff --git a/include/lttng/action/action-internal.h b/include/lttng/action/action-internal.h index d1868b06b..af52a07ff 100644 --- a/include/lttng/action/action-internal.h +++ b/include/lttng/action/action-internal.h @@ -14,19 +14,24 @@ #include #include #include +#include typedef bool (*action_validate_cb)(struct lttng_action *action); typedef void (*action_destroy_cb)(struct lttng_action *action); typedef int (*action_serialize_cb)(struct lttng_action *action, struct lttng_dynamic_buffer *buf); +typedef bool (*action_equal_cb)(const struct lttng_action *a, + const struct lttng_action *b); typedef ssize_t (*action_create_from_buffer_cb)( const struct lttng_buffer_view *view, struct lttng_action **action); struct lttng_action { + struct urcu_ref ref; enum lttng_action_type type; action_validate_cb validate; action_serialize_cb serialize; + action_equal_cb equal; action_destroy_cb destroy; }; @@ -40,6 +45,7 @@ void lttng_action_init(struct lttng_action *action, enum lttng_action_type type, action_validate_cb validate, action_serialize_cb serialize, + action_equal_cb equal, action_destroy_cb destroy); LTTNG_HIDDEN @@ -57,4 +63,17 @@ LTTNG_HIDDEN enum lttng_action_type lttng_action_get_type_const( const struct lttng_action *action); +LTTNG_HIDDEN +bool lttng_action_is_equal(const struct lttng_action *a, + const struct lttng_action *b); + +LTTNG_HIDDEN +void lttng_action_get(struct lttng_action *action); + +LTTNG_HIDDEN +void lttng_action_put(struct lttng_action *action); + +LTTNG_HIDDEN +const char* lttng_action_type_string(enum lttng_action_type action_type); + #endif /* LTTNG_ACTION_INTERNAL_H */ diff --git a/include/lttng/action/action.h b/include/lttng/action/action.h index f3f8d9dee..be7e397d0 100644 --- a/include/lttng/action/action.h +++ b/include/lttng/action/action.h @@ -17,6 +17,19 @@ extern "C" { enum lttng_action_type { LTTNG_ACTION_TYPE_UNKNOWN = -1, LTTNG_ACTION_TYPE_NOTIFY = 0, + LTTNG_ACTION_TYPE_START_SESSION = 1, + LTTNG_ACTION_TYPE_STOP_SESSION = 2, + LTTNG_ACTION_TYPE_ROTATE_SESSION = 3, + LTTNG_ACTION_TYPE_SNAPSHOT_SESSION = 4, + LTTNG_ACTION_TYPE_GROUP = 5, +}; + +enum lttng_action_status { + LTTNG_ACTION_STATUS_OK = 0, + LTTNG_ACTION_STATUS_ERROR = -1, + LTTNG_ACTION_STATUS_UNKNOWN = -2, + LTTNG_ACTION_STATUS_INVALID = -3, + LTTNG_ACTION_STATUS_UNSET = -4, }; /* @@ -25,7 +38,7 @@ enum lttng_action_type { * Returns the type of an action on success, LTTNG_ACTION_TYPE_UNKNOWN on error. */ extern enum lttng_action_type lttng_action_get_type( - struct lttng_action *action); + const struct lttng_action *action); /* * Destroy (frees) an action object. diff --git a/include/lttng/action/group-internal.h b/include/lttng/action/group-internal.h new file mode 100644 index 000000000..9d79ef393 --- /dev/null +++ b/include/lttng/action/group-internal.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 EfficiOS, Inc. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LTTNG_ACTION_GROUP_INTERNAL_H +#define LTTNG_ACTION_GROUP_INTERNAL_H + +#include + +#include + +struct lttng_action; +struct lttng_buffer_view; + +/* + * Create an action group from a buffer view. + * + * On success, return the number of bytes consumed from `view`, and the created + * group in `*group`. On failure, return -1. + */ +LTTNG_HIDDEN +extern ssize_t lttng_action_group_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **group); +#endif /* LTTNG_ACTION_GROUP_INTERNAL_H */ diff --git a/include/lttng/action/group.h b/include/lttng/action/group.h new file mode 100644 index 000000000..b09913d9d --- /dev/null +++ b/include/lttng/action/group.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_ACTION_GROUP_H +#define LTTNG_ACTION_GROUP_H + +struct lttng_action; +struct lttng_action_group; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a newly allocated action group object. + * + * Returns a new action group on success, NULL on failure. This action group + * must be destroyed using lttng_action_group_destroy(). + */ +extern struct lttng_action *lttng_action_group_create(void); + +/* + * Add an action to an lttng_action object of type LTTNG_ACTION_GROUP. + * + * The group takes ownership of the action. + */ +extern enum lttng_action_status lttng_action_group_add_action( + struct lttng_action *group, struct lttng_action *action); + +extern enum lttng_action_status lttng_action_group_get_count( + const struct lttng_action *group, unsigned int *count); + +extern const struct lttng_action *lttng_action_group_get_at_index_const( + const struct lttng_action *group, + unsigned int index); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_ACTION_GROUP_H */ diff --git a/include/lttng/action/rotate-session-internal.h b/include/lttng/action/rotate-session-internal.h new file mode 100644 index 000000000..b17f0b480 --- /dev/null +++ b/include/lttng/action/rotate-session-internal.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_ACTION_ROTATE_SESSION_INTERNAL_H +#define LTTNG_ACTION_ROTATE_SESSION_INTERNAL_H + +#include + +#include + +struct lttng_action; +struct lttng_buffer_view; + +/* + * Create a "rotate session" action from a buffer view. + * + * On success, return the number of bytes consumed from `view`, and the created + * action in `*action`. On failure, return -1. + */ +LTTNG_HIDDEN +extern ssize_t lttng_action_rotate_session_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **action); + +#endif /* LTTNG_ACTION_ROTATE_SESSION_INTERNAL_H */ diff --git a/include/lttng/action/rotate-session.h b/include/lttng/action/rotate-session.h new file mode 100644 index 000000000..750c0739a --- /dev/null +++ b/include/lttng/action/rotate-session.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_ACTION_ROTATE_SESSION_H +#define LTTNG_ACTION_ROTATE_SESSION_H + +struct lttng_action; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a newly allocated rotate-session action object. + * + * A rotate session action object must have a session name set to be considered + * valid when used with a trigger object (lttng_trigger). A name can be set + * using `lttng_action_rotate_session_set_session_name`. + * + * Returns a new action on success, NULL on failure. This action must be + * destroyed using lttng_action_destroy(). + */ +extern struct lttng_action *lttng_action_rotate_session_create(void); + +/* + * Set the session name of an lttng_action object of type + * LTTNG_ACTION_TYPE_ROTATE_SESSION. + */ +extern enum lttng_action_status lttng_action_rotate_session_set_session_name( + struct lttng_action *action, const char *session_name); + +/* + * Get the session name of an lttng_action object of type + * LTTNG_ACTION_TYPE_ROTATE_SESSION. + */ +extern enum lttng_action_status lttng_action_rotate_session_get_session_name( + const struct lttng_action *action, const char **session_name); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_ACTION_ROTATE_SESSION_H */ diff --git a/include/lttng/action/snapshot-session-internal.h b/include/lttng/action/snapshot-session-internal.h new file mode 100644 index 000000000..be79f429b --- /dev/null +++ b/include/lttng/action/snapshot-session-internal.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_ACTION_SNAPSHOT_SESSION_INTERNAL_H +#define LTTNG_ACTION_SNAPSHOT_SESSION_INTERNAL_H + +#include + +#include + +struct lttng_action; +struct lttng_buffer_view; + +/* + * Create a "snapshot session" action from a buffer view. + * + * On success, return the number of bytes consumed from `view`, and the created + * action in `*action`. On failure, return -1. + */ +LTTNG_HIDDEN +extern ssize_t lttng_action_snapshot_session_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **action); + +#endif /* LTTNG_ACTION_SNAPSHOT_SESSION_INTERNAL_H */ diff --git a/include/lttng/action/snapshot-session.h b/include/lttng/action/snapshot-session.h new file mode 100644 index 000000000..f9b33a38f --- /dev/null +++ b/include/lttng/action/snapshot-session.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_ACTION_SNAPSHOT_SESSION_H +#define LTTNG_ACTION_SNAPSHOT_SESSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct lttng_action; +struct lttng_snapshot_output; + +/* + * Create a newly allocated snapshot-session action object. + * + * A snapshot session action object must have a session name set to be + * considered valid when used with a trigger object (lttng_trigger). A name can + * be set using `lttng_action_snapshot_session_set_session_name`. + * + * Returns a new action on success, NULL on failure. This action must be + * destroyed using lttng_action_destroy(). + */ +extern struct lttng_action *lttng_action_snapshot_session_create(void); + +/* + * Set the session name of an lttng_action object of type + * LTTNG_ACTION_TYPE_SNAPSHOT_SESSION. + */ +extern enum lttng_action_status lttng_action_snapshot_session_set_session_name( + struct lttng_action *action, const char *session_name); + +/* + * Get the session name of an lttng_action object of type + * LTTNG_ACTION_TYPE_SNAPSHOT_SESSION. + */ +extern enum lttng_action_status lttng_action_snapshot_session_get_session_name( + const struct lttng_action *action, const char **session_name); + +/* + * Set an explicit snapshot output for this snapshot session action. + * + * The given snapshot output will be used instead of the session's + * default snapshot output. + * + * This function takes ownership of the given snapshot output. + */ +extern enum lttng_action_status lttng_action_snapshot_session_set_output( + struct lttng_action *action, + struct lttng_snapshot_output *output); + +/* + * Get the explicit snapshot output for this snapshot session action. + */ +extern enum lttng_action_status lttng_action_snapshot_session_get_output_const( + const struct lttng_action *action, + const struct lttng_snapshot_output **output); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_ACTION_SNAPSHOT_SESSION_H */ diff --git a/include/lttng/action/start-session-internal.h b/include/lttng/action/start-session-internal.h new file mode 100644 index 000000000..e95d92e8f --- /dev/null +++ b/include/lttng/action/start-session-internal.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_ACTION_START_SESSION_INTERNAL_H +#define LTTNG_ACTION_START_SESSION_INTERNAL_H + +#include + +#include + +struct lttng_action; +struct lttng_buffer_view; + +/* + * Create a "start session" action from a buffer view. + * + * On success, return the number of bytes consumed from `view`, and the created + * action in `*action`. On failure, return -1. + */ +LTTNG_HIDDEN +extern ssize_t lttng_action_start_session_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **action); + +#endif /* LTTNG_ACTION_START_SESSION_INTERNAL_H */ diff --git a/include/lttng/action/start-session.h b/include/lttng/action/start-session.h new file mode 100644 index 000000000..b2ba470a2 --- /dev/null +++ b/include/lttng/action/start-session.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_ACTION_START_SESSION_H +#define LTTNG_ACTION_START_SESSION_H + +struct lttng_action; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a newly allocated start-session action object. + * + * A start session action object must have a session name set to be considered + * valid when used with a trigger object (lttng_trigger). A name can be set + * using `lttng_action_start_session_set_session_name`. + * + * Returns a new action on success, NULL on failure. This action must be + * destroyed using lttng_action_destroy(). + */ +extern struct lttng_action *lttng_action_start_session_create(void); + +/* + * Set the session name of an lttng_action object of type + * LTTNG_ACTION_TYPE_START_SESSION. + */ +extern enum lttng_action_status lttng_action_start_session_set_session_name( + struct lttng_action *action, const char *session_name); + +/* + * Get the session name of an lttng_action object of type + * LTTNG_ACTION_TYPE_START_SESSION. + */ +extern enum lttng_action_status lttng_action_start_session_get_session_name( + const struct lttng_action *action, const char **session_name); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_ACTION_START_SESSION_H */ diff --git a/include/lttng/action/stop-session-internal.h b/include/lttng/action/stop-session-internal.h new file mode 100644 index 000000000..09f5c222c --- /dev/null +++ b/include/lttng/action/stop-session-internal.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_ACTION_STOP_SESSION_INTERNAL_H +#define LTTNG_ACTION_STOP_SESSION_INTERNAL_H + +#include + +#include + +struct lttng_action; +struct lttng_buffer_view; + +/* + * Create a "stop session" action from a buffer view. + * + * On success, return the number of bytes consumed from `view`, and the created + * action in `*action`. On failure, return -1. + */ +LTTNG_HIDDEN +extern ssize_t lttng_action_stop_session_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **action); + +#endif /* LTTNG_ACTION_STOP_SESSION_INTERNAL_H */ diff --git a/include/lttng/action/stop-session.h b/include/lttng/action/stop-session.h new file mode 100644 index 000000000..5c1673137 --- /dev/null +++ b/include/lttng/action/stop-session.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_ACTION_STOP_SESSION_H +#define LTTNG_ACTION_STOP_SESSION_H + +struct lttng_action; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a newly allocated stop-session action object. + * + * A stop session action object must have a session name set to be considered + * valid when used with a trigger object (lttng_trigger). A name can be set + * using `lttng_action_stop_session_set_session_name`. + * + * Returns a new action on success, NULL on failure. This action must be + * destroyed using lttng_action_destroy(). + */ +extern struct lttng_action *lttng_action_stop_session_create(void); + +/* + * Set the session name of an lttng_action object of type + * LTTNG_ACTION_TYPE_STOP_SESSION. + */ +extern enum lttng_action_status lttng_action_stop_session_set_session_name( + struct lttng_action *action, const char *session_name); + +/* + * Get the session name of an lttng_action object of type + * LTTNG_ACTION_TYPE_STOP_SESSION. + */ +extern enum lttng_action_status lttng_action_stop_session_get_session_name( + const struct lttng_action *action, const char **session_name); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_ACTION_STOP_SESSION_H */ diff --git a/include/lttng/condition/condition-internal.h b/include/lttng/condition/condition-internal.h index 60686d29f..2eaf9292f 100644 --- a/include/lttng/condition/condition-internal.h +++ b/include/lttng/condition/condition-internal.h @@ -21,7 +21,8 @@ typedef void (*condition_destroy_cb)(struct lttng_condition *condition); typedef bool (*condition_validate_cb)(const struct lttng_condition *condition); typedef int (*condition_serialize_cb)( const struct lttng_condition *condition, - struct lttng_dynamic_buffer *buf); + struct lttng_dynamic_buffer *buf, + int *fd_to_send); typedef bool (*condition_equal_cb)(const struct lttng_condition *a, const struct lttng_condition *b); typedef ssize_t (*condition_create_from_buffer_cb)( @@ -56,10 +57,14 @@ ssize_t lttng_condition_create_from_buffer( LTTNG_HIDDEN int lttng_condition_serialize(const struct lttng_condition *condition, - struct lttng_dynamic_buffer *buf); + struct lttng_dynamic_buffer *buf, + int *fd_to_send); LTTNG_HIDDEN bool lttng_condition_is_equal(const struct lttng_condition *a, const struct lttng_condition *b); +LTTNG_HIDDEN +const char *lttng_condition_type_str(enum lttng_condition_type type); + #endif /* LTTNG_CONDITION_INTERNAL_H */ diff --git a/include/lttng/condition/condition.h b/include/lttng/condition/condition.h index 877ccd1a3..e1b732398 100644 --- a/include/lttng/condition/condition.h +++ b/include/lttng/condition/condition.h @@ -21,6 +21,7 @@ enum lttng_condition_type { LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW = 102, LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING = 103, LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED = 104, + LTTNG_CONDITION_TYPE_EVENT_RULE_HIT = 105, }; enum lttng_condition_status { @@ -29,6 +30,7 @@ enum lttng_condition_status { LTTNG_CONDITION_STATUS_UNKNOWN = -2, LTTNG_CONDITION_STATUS_INVALID = -3, LTTNG_CONDITION_STATUS_UNSET = -4, + LTTNG_CONDITION_STATUS_UNSUPPORTED = -4, }; /* diff --git a/include/lttng/condition/evaluation-internal.h b/include/lttng/condition/evaluation-internal.h index a2cb432fc..d85ce3af0 100644 --- a/include/lttng/condition/evaluation-internal.h +++ b/include/lttng/condition/evaluation-internal.h @@ -9,6 +9,7 @@ #define LTTNG_EVALUATION_INTERNAL_H #include +#include #include #include #include @@ -37,7 +38,9 @@ void lttng_evaluation_init(struct lttng_evaluation *evaluation, enum lttng_condition_type type); LTTNG_HIDDEN -ssize_t lttng_evaluation_create_from_buffer(const struct lttng_buffer_view *view, +ssize_t lttng_evaluation_create_from_buffer( + const struct lttng_condition *condition, + const struct lttng_buffer_view *view, struct lttng_evaluation **evaluation); LTTNG_HIDDEN diff --git a/include/lttng/condition/event-rule-internal.h b/include/lttng/condition/event-rule-internal.h new file mode 100644 index 000000000..a4204da34 --- /dev/null +++ b/include/lttng/condition/event-rule-internal.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_CONDITION_event_rule_INTERNAL_H +#define LTTNG_CONDITION_event_rule_INTERNAL_H + +#include +#include +#include +#include +#include +#include + +struct lttng_capture_descriptor { + /* The index at which the capture for this descriptor in the received + * payload from the tracer. This is populated on sessiond side. + * -1 is uninitialized. + * This is necessary since a single trigger can have multiple notify + * action, only an ordered set of capture desciptor is passed to the tracer. + */ + int32_t capture_index; + struct lttng_event_expr *event_expression; +}; + +struct lttng_condition_event_rule { + struct lttng_condition parent; + struct lttng_event_rule *rule; + + /* Array of `struct lttng_capture_descriptor *` */ + struct lttng_dynamic_pointer_array capture_descriptors; +}; + +struct lttng_evaluation_event_rule { + struct lttng_evaluation parent; + char *name; + + /* MessagePack-encoded captured event field values */ + struct lttng_dynamic_buffer capture_payload; + + /* + * The content of this array event field value is the decoded + * version of `capture_payload` above. + * + * This is a cache: it's not serialized/deserialized in + * communications from/to the library and the session daemon. + */ + struct lttng_event_field_value *captured_values; +}; + +struct lttng_evaluation_event_rule_comm { + uint32_t trigger_name_length; + /* Trigger name */ + char payload[]; +} LTTNG_PACKED; + +struct lttng_condition_event_rule_capture_bytecode_element +{ + struct lttng_event_expr *expression; + struct lttng_bytecode *bytecode; +}; + +LTTNG_HIDDEN +ssize_t lttng_condition_event_rule_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_condition **condition); + +LTTNG_HIDDEN +enum lttng_condition_status +lttng_condition_event_rule_get_rule_no_const( + const struct lttng_condition *condition, + struct lttng_event_rule **rule); + +LTTNG_HIDDEN +struct lttng_evaluation *lttng_evaluation_event_rule_create( + const struct lttng_condition_event_rule *condition, + const char* trigger_name, + const char *capture_payload, size_t capture_payload_size, + bool decode_capture_payload); + +LTTNG_HIDDEN +ssize_t lttng_evaluation_event_rule_create_from_buffer( + const struct lttng_condition_event_rule *condition, + const struct lttng_buffer_view *view, + struct lttng_evaluation **_evaluation); + +/* + * The returned `lttng_dynamic_pointer_array` contains a index ordered set of + * `lttng_condition_event_rule_capture_bytecode_element`. + * This ensure that minimal work will be done by the tracer for cases where + * multiple identical capture expression are present. + */ +LTTNG_HIDDEN +enum lttng_error_code +lttng_condition_event_rule_generate_capture_descriptor_bytecode_set( + struct lttng_condition *condition, + struct lttng_dynamic_pointer_array *bytecode_set); + +LTTNG_HIDDEN +struct lttng_capture_descriptor * +lttng_condition_event_rule_get_internal_capture_descriptor_at_index( + const struct lttng_condition *condition, unsigned int index); + +#endif /* LTTNG_CONDITION_event_rule_INTERNAL_H */ diff --git a/include/lttng/condition/event-rule.h b/include/lttng/condition/event-rule.h new file mode 100644 index 000000000..d72926b37 --- /dev/null +++ b/include/lttng/condition/event-rule.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_CONDITION_EVENT_RULE_H +#define LTTNG_CONDITION_EVENT_RULE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct lttng_event_expr; +struct lttng_event_field_value; + +/* + * TODO: overall desciption of an event rule condition. + */ + +/* + * Create a newly allocated event rule condition. + * + * Rule will be copied internally. + * + * Returns a new condition on success, NULL on failure. This condition must be + * destroyed using lttng_condition_destroy(). + */ +extern struct lttng_condition *lttng_condition_event_rule_create( + struct lttng_event_rule *rule); + +/* + * Get the rule property of a event rule condition. + * + * The caller does not assume the ownership of the returned rule. The + * rule shall only be used for the duration of the condition's + * lifetime. + * + * Returns LTTNG_CONDITION_STATUS_OK and a pointer to the condition's rule + * on success, LTTNG_CONDITION_STATUS_INVALID if an invalid + * parameter is passed. */ +extern enum lttng_condition_status lttng_condition_event_rule_get_rule( + const struct lttng_condition *condition, const struct lttng_event_rule **rule); + +/** + * lttng_evaluation_event_rule_hit are specialised lttng_evaluations which + * allow users to query a number of properties resulting from the evaluation + * of a condition which evaluated to true. + * + * The evaluation of a event rule hit yields two different results: + * TEMPORARY - The name of the triggers associated with the condition. + * TODO - The captured event payload if any + */ + +/* + * Get the trigger name property of a event rule hit evaluation. + * + * Returns LTTNG_EVALUATION_STATUS_OK on success and a trigger name + * or LTTNG_EVALUATION_STATUS_INVALID if + * an invalid parameter is passed. + */ +extern enum lttng_evaluation_status +lttng_evaluation_event_rule_get_trigger_name( + const struct lttng_evaluation *evaluation, + const char **name); + +/* + * Sets `*field_val` to the array event field value of the event rule + * condition evaluation `evaluation` which contains its captured values. + * + * Returns: + * + * `LTTNG_EVALUATION_STATUS_OK`: + * Success. + * + * `*field_val` is an array event field value with a length of at + * least one. + * + * `LTTNG_EVALUATION_STATUS_INVALID`: + * * `evaluation` is `NULL`. + * * The type of the condition of `evaluation` is not + * `LTTNG_CONDITION_TYPE_EVENT_RULE_HIT`. + * * The condition of `evaluation` has no capture descriptors. + * * `field_val` is `NULL`. + */ +extern enum lttng_evaluation_status +lttng_evaluation_get_captured_values( + const struct lttng_evaluation *evaluation, + const struct lttng_event_field_value **field_val); + +/* + * Appends (transfering the ownership) the capture descriptor `expr` to + * the event rule condition `condition`. + * + * Returns: + * + * `LTTNG_CONDITION_STATUS_OK`: + * Success. + * + * `LTTNG_CONDITION_STATUS_ERROR`: + * Memory error. + * + * `LTTNG_CONDITION_STATUS_INVALID`: + * * `condition` is `NULL`. + * * The type of `condition` is not + * `LTTNG_CONDITION_TYPE_EVENT_RULE_HIT`. + * * `expr` is `NULL`. + * * `expr` is not a locator expression, that is, its type is not + * one of: + * + * * `LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD` + * * `LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD` + * * `LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD` + * * `LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT` + * `LTTNG_CONDITION_STATUS_UNSUPPORTED`: + * * The associated event-rule does not support runtime capture. + */ +extern enum lttng_condition_status +lttng_condition_event_rule_append_capture_descriptor( + struct lttng_condition *condition, + struct lttng_event_expr *expr); + +/* + * Sets `*count` to the number of capture descriptors in the event rule + * condition `condition`. + * + * Returns: + * + * `LTTNG_CONDITION_STATUS_OK`: + * Success. + * + * `LTTNG_CONDITION_STATUS_INVALID`: + * * `condition` is `NULL`. + * * The type of `condition` is not + * `LTTNG_CONDITION_TYPE_EVENT_RULE_HIT`. + * * `count` is `NULL`. + */ +extern enum lttng_condition_status +lttng_condition_event_rule_get_capture_descriptor_count( + const struct lttng_condition *condition, unsigned int *count); + +/* + * Returns the capture descriptor (borrowed) of the event rule condition + * `condition` at the index `index`, or `NULL` if: + * + * * `condition` is `NULL`. + * * The type of `condition` is not + * `LTTNG_CONDITION_TYPE_EVENT_RULE_HIT`. + * * `index` is greater than or equal to the number of capture + * descriptors in `condition` (as returned by + * lttng_condition_event_rule_get_capture_descriptor_count()). + */ +extern const struct lttng_event_expr * +lttng_condition_event_rule_get_capture_descriptor_at_index( + const struct lttng_condition *condition, unsigned int index); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_CONDITION_EVENT_RULE_H */ diff --git a/include/lttng/domain-internal.h b/include/lttng/domain-internal.h new file mode 100644 index 000000000..f34a33bcf --- /dev/null +++ b/include/lttng/domain-internal.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 - EfficiOS, inc. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LTTNG_DOMAIN_INTERNAL_H +#define LTTNG_DOMAIN_INTERNAL_H + +#include "lttng/domain.h" +#include "common/macros.h" + +#ifdef __cplusplus +extern "C" { +#endif + +LTTNG_HIDDEN +const char *lttng_domain_type_str(enum lttng_domain_type domain_type); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_DOMAIN_INTERNAL_H */ diff --git a/include/lttng/event-expr-internal.h b/include/lttng/event-expr-internal.h new file mode 100644 index 000000000..8c877818d --- /dev/null +++ b/include/lttng/event-expr-internal.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 Philippe Proulx + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_EXPR_INTERNAL_H +#define LTTNG_EVENT_EXPR_INTERNAL_H + +#include +#include +#include +#include + +struct lttng_event_expr { + struct urcu_ref ref; + enum lttng_event_expr_type type; +}; + +/* + * `LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD` and + * `LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD`. + */ +struct lttng_event_expr_field { + struct lttng_event_expr parent; + char *name; +}; + +/* `LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD` */ +struct lttng_event_expr_app_specific_context_field { + struct lttng_event_expr parent; + char *provider_name; + char *type_name; +}; + +/* `LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT` */ +struct lttng_event_expr_array_field_element { + struct lttng_event_expr parent; + + /* Owned by this */ + struct lttng_event_expr *array_field_expr; + + unsigned int index; +}; + +/* + * Returns whether or not `expr` is an l-value (locator value). + */ +static inline +bool lttng_event_expr_is_lvalue(const struct lttng_event_expr *expr) +{ + assert(expr); + return expr->type == LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD || + expr->type == LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD || + expr->type == LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD || + expr->type == LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT; +} + +LTTNG_HIDDEN +void lttng_event_expr_get(struct lttng_event_expr *expr); + +LTTNG_HIDDEN +void lttng_event_expr_put(struct lttng_event_expr *expr); + +#endif /* LTTNG_EVENT_EXPR_INTERNAL_H */ diff --git a/include/lttng/event-expr.h b/include/lttng/event-expr.h new file mode 100644 index 000000000..911648779 --- /dev/null +++ b/include/lttng/event-expr.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2020 Philippe Proulx + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_EXPR_H +#define LTTNG_EVENT_EXPR_H + +#include + +struct lttng_event_expr; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Types of an event expression. + */ +enum lttng_event_expr_type { + /* + * Returned by lttng_event_expr_get_type() with an invalid + * parameter. + */ + LTTNG_EVENT_EXPR_TYPE_INVALID = -1, + + /* + * The named payload field of an event. + * + * Command-line expression example: + * + * next_prio + */ + LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD = 0, + + /* + * The named per-channel context field of an event. + * + * Command-line expression example: + * + * $ctx.vpid + */ + LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD = 1, + + /* + * The named application-specific context field of an event. + * + * Command-line expression example: + * + * $app.iga:active-clients + */ + LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD = 2, + + /* + * The element of an array field. + * + * Command-line expression example: + * + * my_field[4] + * $ctx.some_context[5][1] + */ + LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT = 3, +}; + +/* + * Event expression API status codes. + */ +enum lttng_event_expr_status { + /* + * Invalid parameter. + */ + LTTNG_EVENT_EXPR_STATUS_INVALID = -1, + + /* + * Success. + */ + LTTNG_EVENT_EXPR_STATUS_OK = 0, +}; + +/* + * Returns the type of the event expression `expr`, or + * `LTTNG_EVENT_EXPR_TYPE_INVALID` if `expr` is `NULL`. + */ +extern enum lttng_event_expr_type lttng_event_expr_get_type( + const struct lttng_event_expr *expr); + +/* + * Creates an event payload field expression for the payload field named + * `field_name`. + * + * Returns `NULL` if: + * + * * There's a memory error. + * * `field_name` is `NULL`. + */ +extern struct lttng_event_expr *lttng_event_expr_event_payload_field_create( + const char *field_name); + +/* + * Returns the field name of the event payload field expression `expr`, + * or `NULL` if: + * + * * `expr` is `NULL`. + * * The type of `expr` is not + * `LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD`. + */ +extern const char *lttng_event_expr_event_payload_field_get_name( + const struct lttng_event_expr *expr); + +/* + * Creates a per-channel context field expression for the per-channel + * context field named `field_name`. + * + * Returns `NULL` if: + * + * * There's a memory error. + * * `field_name` is `NULL`. + */ +extern struct lttng_event_expr * +lttng_event_expr_channel_context_field_create(const char *field_name); + +/* + * Returns the field name of the per-channel context field + * expression `expr`, or `NULL` if: + * + * `expr` is `NULL`. + * * The type of `expr` is not + * `LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD`. + */ +extern const char *lttng_event_expr_channel_context_field_get_name( + const struct lttng_event_expr *expr); + +/* + * Creates an application-specific context field expression for the + * application-specific context field provided by the provider named + * `provider_name` and having the type named `type_name`. + * + * Returns `NULL` if: + * + * * There's a memory error. + * * `provider_name` is `NULL`. + * * `type_name` is `NULL`. + */ +extern struct lttng_event_expr * +lttng_event_expr_app_specific_context_field_create( + const char *provider_name, const char *type_name); + +/* + * Returns the provider name of the application-specific context field + * expression `expr`, or `NULL` if: + * + * * `expr` is `NULL`. + * * The type of `expr` is not + * `LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD`. + */ +extern const char * +lttng_event_expr_app_specific_context_field_get_provider_name( + const struct lttng_event_expr *expr); + +/* + * Returns the type name of the application-specific context field + * expression `expr`, or `NULL` if: + * + * * `expr` is `NULL`. + * * The type of `expr` is not + * `LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD`. + */ +extern const char * +lttng_event_expr_app_specific_context_field_get_type_name( + const struct lttng_event_expr *expr); + +/* + * Creates an array field element expression for the parent array field + * `array_field_expr` (transfering the ownership) and the index `index`. + * + * Returns `NULL` if: + * + * * There's a memory error. + * * `array_field_expr` is `NULL`. + * * `array_field_expr` is not a locator expression, that is, its type + * is not one of: + * + * * `LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD` + * * `LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD` + * * `LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD` + * * `LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT` + */ +extern struct lttng_event_expr *lttng_event_expr_array_field_element_create( + struct lttng_event_expr *array_field_expr, + unsigned int index); + +/* + * Returns the parent array field expression of the array field element + * expression `expr`, or `NULL` if: + * + * * `expr` is `NULL`. + * * The type of `expr` is not + * `LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT`. + */ +extern const struct lttng_event_expr * +lttng_event_expr_array_field_element_get_parent_expr( + const struct lttng_event_expr *expr); + +/* + * Sets `*index` to the index of the array field element expression + * `expr`. + * + * Returns: + * + * `LTTNG_EVENT_EXPR_STATUS_OK`: + * Success. + * + * `LTTNG_EVENT_EXPR_STATUS_INVALID`: + * * `expr` is `NULL`. + * * The type of `expr` is not + * `LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT`. + * * `index` is `NULL`. + */ +extern enum lttng_event_expr_status +lttng_event_expr_array_field_element_get_index( + const struct lttng_event_expr *expr, unsigned int *index); + +/* + * Returns whether or not the event expressions `expr_a` and `expr_b` + * are equal. + * + * `expr_a` and `expr_b` can be `NULL`. + */ +extern bool lttng_event_expr_is_equal(const struct lttng_event_expr *expr_a, + const struct lttng_event_expr *expr_b); + +/* + * Destroys the event expression `expr` if not `NULL`. + */ +extern void lttng_event_expr_destroy(struct lttng_event_expr *expr); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_EVENT_EXPR_H */ diff --git a/include/lttng/event-field-value-internal.h b/include/lttng/event-field-value-internal.h new file mode 100644 index 000000000..3ef6d2ccf --- /dev/null +++ b/include/lttng/event-field-value-internal.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2020 Philippe Proulx + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_FIELD_VALUE_INTERNAL_H +#define LTTNG_EVENT_FIELD_VALUE_INTERNAL_H + +#include +#include +#include +#include + +struct lttng_event_field_value { + enum lttng_event_field_value_type type; +}; + +/* + * `LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT`. + */ +struct lttng_event_field_value_uint { + struct lttng_event_field_value parent; + uint64_t val; +}; + +/* + * `LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_INT`. + */ +struct lttng_event_field_value_int { + struct lttng_event_field_value parent; + int64_t val; +}; + +/* + * `LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM` and + * `LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM` (base). + */ +struct lttng_event_field_value_enum { + struct lttng_event_field_value parent; + + /* + * Array of `char *` (owned by this). + */ + struct lttng_dynamic_pointer_array labels; +}; + +/* + * `LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM`. + */ +struct lttng_event_field_value_enum_uint { + struct lttng_event_field_value_enum parent; + uint64_t val; +}; + +/* + * `LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM`. + */ +struct lttng_event_field_value_enum_int { + struct lttng_event_field_value_enum parent; + int64_t val; +}; + +/* `LTTNG_EVENT_FIELD_VALUE_TYPE_REAL` */ +struct lttng_event_field_value_real { + struct lttng_event_field_value parent; + double val; +}; + +/* `LTTNG_EVENT_FIELD_VALUE_TYPE_STRING` */ +struct lttng_event_field_value_string { + struct lttng_event_field_value parent; + + /* Owned by this */ + char *val; +}; + +/* `LTTNG_EVENT_FIELD_VALUE_TYPE_STRING` */ +struct lttng_event_field_value_array { + struct lttng_event_field_value parent; + + /* + * Array of `struct lttng_event_field_value *` (owned by this). + * + * A `NULL` element means it's unavailable + * (`LTTNG_EVENT_FIELD_VALUE_STATUS_UNAVAILABLE` status). + */ + struct lttng_dynamic_pointer_array elems; +}; + +/* + * NOTE JORAJ: This was previously public. The only slight problem with that is + * that as of today (2020-05-26) there is no plan/sessiond for the tracer to + * actually provide this information. This was already known in [1]. For now + * this code have no value since it only confuse the end user. At upstreaming + * time we will need to decide if we want to remove all code pertaining to enum + * label, at least on the lttng-tools API. + * + * [1] https://support.efficios.com/issues/792 + * + * Sets `*count` to the number of labels of the enumeration event field + * value `field_val`. + * + * Returns: + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_OK`: + * Success. + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID`: + * * `field_val` is `NULL`. + * * The type of `field_val` is not + * `LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM` or + * `LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM`. + * * `count` is `NULL`. + */ +LTTNG_HIDDEN +enum lttng_event_field_value_status +lttng_event_field_value_enum_get_label_count( + const struct lttng_event_field_value *field_val, + unsigned int *count); + +/* + * NOTE JORAJ: see NOTE JORAJ off lttng_event_field_value_enum_get_label_count + * + * Returns the label at index `index` of the enumeration event field + * value `field_val`, or `NULL` if: + * + * * `field_val` is `NULL`. + * * The type of `field_val` is not + * `LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM` or + * `LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM`. + * * `index` is greater than or equal to the label count of `field_val`, + * as returned by lttng_event_field_value_enum_get_label_count(). + */ +LTTNG_HIDDEN +const char *lttng_event_field_value_enum_get_label_at_index( + const struct lttng_event_field_value *field_val, + unsigned int index); + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_uint_create( + uint64_t val); + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_int_create( + int64_t val); + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_enum_uint_create( + uint64_t val); + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_enum_int_create( + int64_t val); + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_real_create(double val); + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_string_create( + const char *val); + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_string_create_with_size( + const char *val, size_t size); + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_array_create(void); + +LTTNG_HIDDEN +int lttng_event_field_value_enum_append_label( + struct lttng_event_field_value *field_val, const char *label); + +LTTNG_HIDDEN +int lttng_event_field_value_enum_append_label_with_size( + struct lttng_event_field_value *field_val, const char *label, + size_t size); + +LTTNG_HIDDEN +int lttng_event_field_value_array_append( + struct lttng_event_field_value *array_field_val, + struct lttng_event_field_value *field_val); + +LTTNG_HIDDEN +int lttng_event_field_value_array_append_unavailable( + struct lttng_event_field_value *array_field_val); + +LTTNG_HIDDEN +void lttng_event_field_value_destroy(struct lttng_event_field_value *field_val); + +#endif /* LTTNG_EVENT_FIELD_VALUE_INTERNAL_H */ diff --git a/include/lttng/event-field-value.h b/include/lttng/event-field-value.h new file mode 100644 index 000000000..db4a4fb3b --- /dev/null +++ b/include/lttng/event-field-value.h @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2020 Philippe Proulx + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_FIELD_VALUE_H +#define LTTNG_EVENT_FIELD_VALUE_H + +#include + +struct lttng_event_field_value; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Types of a event field value expression. + */ +enum lttng_event_field_value_type { + /* + * Unknown. + */ + LTTNG_EVENT_FIELD_VALUE_TYPE_UNKNOWN = -2, + + /* + * Returned by lttng_event_field_value_get_type() with an + * invalid parameter. + */ + LTTNG_EVENT_FIELD_VALUE_TYPE_INVALID = -1, + + /* + * Unsigned integer event field value. + */ + LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT = 0, + + /* + * Signed integer event field value. + */ + LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_INT = 1, + + /* + * Unsigned enumeration event field value. + * + * This type conceptually inherits + * `LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT`. + */ + LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM = 2, + + /* + * Signed enumeration event field value. + * + * This type conceptually inherits + * `LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_INT`. + */ + LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM = 3, + + /* + * Real event field value. + */ + LTTNG_EVENT_FIELD_VALUE_TYPE_REAL = 4, + + /* + * String event field value. + */ + LTTNG_EVENT_FIELD_VALUE_TYPE_STRING = 5, + + /* + * Array event field value. + */ + LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY = 6, +}; + +/* + * Event field value API status codes. + */ +enum lttng_event_field_value_status { + /* + * Event field value is not available. + */ + LTTNG_EVENT_FIELD_VALUE_STATUS_UNAVAILABLE = -2, + + /* + * Invalid parameter. + */ + LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID = -1, + + /* + * Success. + */ + LTTNG_EVENT_FIELD_VALUE_STATUS_OK = 0, +}; + +/* + * Returns the type of the event field value `field_val`, or: + * + * `LTTNG_EVENT_FIELD_VALUE_TYPE_UNKNOWN`: + * The type of `field_val` is unknown as of this version of the + * LTTng control library. + * + * `LTTNG_EVENT_FIELD_VALUE_TYPE_INVALID`: + * `field_val` is `NULL`. + */ +extern enum lttng_event_field_value_type lttng_event_field_value_get_type( + const struct lttng_event_field_value *field_val); + +/* + * Sets `*val` to the raw value of the unsigned integer/enumeration + * event field value `field_val`. + * + * Returns: + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_OK`: + * Success. + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID`: + * * `field_val` is `NULL`. + * * The type of `field_val` is not + * `LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT` or + * `LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM`. + * * `val` is `NULL`. + */ +extern enum lttng_event_field_value_status +lttng_event_field_value_unsigned_int_get_value( + const struct lttng_event_field_value *field_val, uint64_t *val); + +/* + * Sets `*val` to the raw value of the signed integer/enumeration event + * field value `field_val`. + * + * Returns: + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_OK`: + * Success. + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID`: + * * `field_val` is `NULL`. + * * The type of `field_val` is not + * `LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_INT` or + * `LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM`. + * * `val` is `NULL`. + */ +extern enum lttng_event_field_value_status +lttng_event_field_value_signed_int_get_value( + const struct lttng_event_field_value *field_val, int64_t *val); + +/* + * Sets `*val` to the raw value of the real event field value + * `field_val`. + * + * Returns: + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_OK`: + * Success. + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID`: + * * `field_val` is `NULL`. + * * The type of `field_val` is not + * `LTTNG_EVENT_FIELD_VALUE_TYPE_REAL`. + * * `val` is `NULL`. + */ +extern enum lttng_event_field_value_status +lttng_event_field_value_real_get_value( + const struct lttng_event_field_value *field_val, double *val); + +/* + * Returns the raw value (an UTF-8 C string) of the string event field + * value `field_val`, or `NULL` if: + * + * * `field_val` is `NULL`. + * * The type of `field_val` is not + * `LTTNG_EVENT_FIELD_VALUE_TYPE_STRING`. + */ +extern const char *lttng_event_field_value_string_get_value( + const struct lttng_event_field_value *field_val); + +/* + * Sets `*length` to the length (the number of contained elements) of + * the array event field value `field_val`. + * + * Returns: + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_OK`: + * Success. + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID`: + * * `field_val` is `NULL`. + * * The type of `field_val` is not + * `LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY`. + * * `length` is `NULL`. + */ +extern enum lttng_event_field_value_status +lttng_event_field_value_array_get_length( + const struct lttng_event_field_value *field_val, + unsigned int *length); + +/* + * Sets `*elem_field_val` to the event field value at index `index` in + * the array event field value `field_val`. + * + * Returns: + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_OK`: + * Success. + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID`: + * * `field_val` is `NULL`. + * * The type of `field_val` is not + * `LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY`. + * * `index` is greater than or equal to the length of `field_val`, + * as returned by lttng_event_field_value_array_get_length(). + * + * `LTTNG_EVENT_FIELD_VALUE_STATUS_UNAVAILABLE`: + * * No event field value exists at index `index` within + * `field_val`. + */ +extern enum lttng_event_field_value_status +lttng_event_field_value_array_get_element_at_index( + const struct lttng_event_field_value *field_val, + unsigned int index, + const struct lttng_event_field_value **elem_field_val); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_EVENT_FIELD_VALUE_H */ diff --git a/include/lttng/event-internal.h b/include/lttng/event-internal.h index b3df4c9c2..01e9b8088 100644 --- a/include/lttng/event-internal.h +++ b/include/lttng/event-internal.h @@ -36,4 +36,19 @@ struct lttng_event_extended { LTTNG_HIDDEN struct lttng_event *lttng_event_copy(const struct lttng_event *event); +// FIXME: the implementation of these should be moved to some common file, +// they should not be in the enable_events.c file. + +LTTNG_HIDDEN +int loglevel_str_to_value(const char *inputstr); + +LTTNG_HIDDEN +int loglevel_log4j_str_to_value(const char *inputstr); + +LTTNG_HIDDEN +int loglevel_jul_str_to_value(const char *inputstr); + +LTTNG_HIDDEN +int loglevel_python_str_to_value(const char *inputstr); + #endif /* LTTNG_EVENT_INTERNAL_H */ diff --git a/include/lttng/event-rule/event-rule-internal.h b/include/lttng/event-rule/event-rule-internal.h new file mode 100644 index 000000000..8ccf4d8a8 --- /dev/null +++ b/include/lttng/event-rule/event-rule-internal.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_INTERNAL_H +#define LTTNG_EVENT_RULE_INTERNAL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef void (*event_rule_destroy_cb)(struct lttng_event_rule *event_rule); +typedef bool (*event_rule_validate_cb)( + const struct lttng_event_rule *event_rule); +typedef int (*event_rule_serialize_cb)( + const struct lttng_event_rule *event_rule, + struct lttng_dynamic_buffer *buf, + int *fd_to_send); +typedef bool (*event_rule_equal_cb)(const struct lttng_event_rule *a, + const struct lttng_event_rule *b); +typedef ssize_t (*event_rule_create_from_buffer_cb)( + const struct lttng_buffer_view *view, + struct lttng_event_rule **event_rule); +typedef enum lttng_error_code (*event_rule_populate_cb)( + struct lttng_event_rule *event_rule, uid_t uid, gid_t gid); +typedef const char *(*event_rule_get_filter_cb)( + const struct lttng_event_rule *event_rule); +typedef const struct lttng_bytecode *( + *event_rule_get_filter_bytecode_cb)( + const struct lttng_event_rule *event_rule); +typedef struct lttng_event_exclusion *(*event_rule_generate_exclusions_cb)( + struct lttng_event_rule *event_rule); +typedef struct lttng_event *(*event_rule_generate_lttng_event_cb)( + const struct lttng_event_rule *event_rule); + +struct lttng_event_rule { + struct urcu_ref ref; + enum lttng_event_rule_type type; + event_rule_validate_cb validate; + event_rule_serialize_cb serialize; + event_rule_equal_cb equal; + event_rule_destroy_cb destroy; + event_rule_populate_cb populate; + event_rule_get_filter_cb get_filter; + event_rule_get_filter_bytecode_cb get_filter_bytecode; + event_rule_generate_exclusions_cb generate_exclusions; + event_rule_generate_lttng_event_cb generate_lttng_event; +}; + +struct lttng_event_rule_comm { + /* enum lttng_event_rule_type */ + int8_t event_rule_type; + char payload[]; +}; + +LTTNG_HIDDEN +void lttng_event_rule_init(struct lttng_event_rule *event_rule, + enum lttng_event_rule_type type); + +LTTNG_HIDDEN +bool lttng_event_rule_validate(const struct lttng_event_rule *event_rule); + +LTTNG_HIDDEN +ssize_t lttng_event_rule_create_from_buffer( + const struct lttng_buffer_view *buffer, + struct lttng_event_rule **event_rule); + +LTTNG_HIDDEN +int lttng_event_rule_serialize(const struct lttng_event_rule *event_rule, + struct lttng_dynamic_buffer *buf, + int *fd_to_send); + +LTTNG_HIDDEN +bool lttng_event_rule_is_equal(const struct lttng_event_rule *a, + const struct lttng_event_rule *b); + +LTTNG_HIDDEN +bool lttng_event_rule_get(struct lttng_event_rule *rule); + +LTTNG_HIDDEN +void lttng_event_rule_put(struct lttng_event_rule *rule); + +LTTNG_HIDDEN +enum lttng_domain_type lttng_event_rule_get_domain_type( + const struct lttng_event_rule *rule); + +LTTNG_HIDDEN +enum lttng_error_code lttng_event_rule_populate( + struct lttng_event_rule *rule, uid_t uid, gid_t gid); + +/* + * If not present/implemented returns NULL + * Caller DO NOT own the returned object + */ +LTTNG_HIDDEN +const char *lttng_event_rule_get_filter(const struct lttng_event_rule *rule); + +/* + * If not present/implemented returns NULL + * Caller DO NOT own the returned object + */ +LTTNG_HIDDEN +const struct lttng_bytecode *lttng_event_rule_get_filter_bytecode( + const struct lttng_event_rule *rule); + +/* + * If not present return NULL + * Caller OWN the returned object + * TODO: should this be done another way? + */ +LTTNG_HIDDEN +struct lttng_event_exclusion *lttng_event_rule_generate_exclusions( + struct lttng_event_rule *rule); + +LTTNG_HIDDEN +const char *lttng_event_rule_type_str(enum lttng_event_rule_type type); + +/* + * This is compatibility helper, allowing us to generate a sessiond side (not + * communication) struct lttng_event object. This facilitate integration with + * current code. + * Caller OWN the returned object + */ +LTTNG_HIDDEN +struct lttng_event *lttng_event_rule_generate_lttng_event( + const struct lttng_event_rule *rule); + +/* Quick helper to test if the event rule apply to an agent domain */ +LTTNG_HIDDEN +bool lttng_event_rule_is_agent(const struct lttng_event_rule *rule); + +#endif /* LTTNG_EVENT_RULE_INTERNAL_H */ diff --git a/include/lttng/event-rule/event-rule.h b/include/lttng/event-rule/event-rule.h new file mode 100644 index 000000000..7514f9441 --- /dev/null +++ b/include/lttng/event-rule/event-rule.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_H +#define LTTNG_EVENT_RULE_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct lttng_event_rule; + +enum lttng_event_rule_type { + LTTNG_EVENT_RULE_TYPE_UNKNOWN = -1, + LTTNG_EVENT_RULE_TYPE_TRACEPOINT = 100, + LTTNG_EVENT_RULE_TYPE_SYSCALL = 101, + LTTNG_EVENT_RULE_TYPE_KPROBE = 102, + LTTNG_EVENT_RULE_TYPE_KRETPROBE = 103, + LTTNG_EVENT_RULE_TYPE_UPROBE = 104, +}; + +enum lttng_event_rule_status { + LTTNG_EVENT_RULE_STATUS_OK = 0, + LTTNG_EVENT_RULE_STATUS_ERROR = -1, + LTTNG_EVENT_RULE_STATUS_UNKNOWN = -2, + LTTNG_EVENT_RULE_STATUS_INVALID = -3, + LTTNG_EVENT_RULE_STATUS_UNSET = -4, + LTTNG_EVENT_RULE_STATUS_UNSUPPORTED = -5, +}; + +/** + * An event rule describes a set of criteria to be used as a discriminant in + * regards to a set of events. + */ + +/* + * Get the event rule type. + * + * Returns the type of an event rule on success, LTTNG_EVENT_RULE_UNKNOWN on + * error. + */ +extern enum lttng_event_rule_type lttng_event_rule_get_type( + const struct lttng_event_rule *event_rule); + +/* + * Destroy an event rule object. + */ +extern void lttng_event_rule_destroy(struct lttng_event_rule *rule); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_EVENT_RULE_H */ diff --git a/include/lttng/event-rule/kprobe-internal.h b/include/lttng/event-rule/kprobe-internal.h new file mode 100644 index 000000000..fd4596754 --- /dev/null +++ b/include/lttng/event-rule/kprobe-internal.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_KPROBE_INTERNAL_H +#define LTTNG_EVENT_RULE_KPROBE_INTERNAL_H + +#include +#include +#include +#include + +struct lttng_event_rule_kprobe { + struct lttng_event_rule parent; + char *name; + struct { + uint64_t address; + uint64_t offset; + char *symbol_name; + bool set; + } probe; +}; + +struct lttng_event_rule_kprobe_comm { + uint32_t name_len; + uint32_t probe_symbol_name_len; + uint64_t probe_address; + uint64_t probe_offset; + /* name, source symbol_name */ + char payload[]; +} LTTNG_PACKED; + +LTTNG_HIDDEN +ssize_t lttng_event_rule_kprobe_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **rule); + +LTTNG_HIDDEN +uint64_t lttng_event_rule_kprobe_get_address( + const struct lttng_event_rule *rule); + +LTTNG_HIDDEN +uint64_t lttng_event_rule_kprobe_get_offset( + const struct lttng_event_rule *rule); + +LTTNG_HIDDEN +const char *lttng_event_rule_kprobe_get_symbol_name( + const struct lttng_event_rule *rule); + +#endif /* LTTNG_EVENT_RULE_KPROBE_INTERNAL_H */ diff --git a/include/lttng/event-rule/kprobe.h b/include/lttng/event-rule/kprobe.h new file mode 100644 index 000000000..ffa9002e3 --- /dev/null +++ b/include/lttng/event-rule/kprobe.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_KPROBE_H +#define LTTNG_EVENT_RULE_KPROBE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a newly allocated kprobe event rule. + * + * Returns a new event rule on success, NULL on failure. The returned event rule + * must be destroyed using lttng_event_rule_destroy(). + */ +extern struct lttng_event_rule *lttng_event_rule_kprobe_create(void); + +/* + * Set the source of a kprobe event rule. + * + * Possible formats for the source argument: + * Address (0x prefix supported) + * Symbol name + * Symbol name and offset (SYMBOL+OFFSET format) + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_kprobe_set_source( + struct lttng_event_rule *rule, const char *source); + +/* + * Set the name of a kprobe event rule. + * + * The name is copied internally. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_kprobe_set_name( + struct lttng_event_rule *rule, const char *name); + +/* + * Get the name of a kprobe event rule. + * + * The caller does not assume the ownership of the returned name. + * The name shall only only be used for the duration of the event + * rule's lifetime, or before a different name is set. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and a pointer to the event rule's name on + * success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid parameter is passed, + * or LTTNG_EVENT_RULE_STATUS_UNSET if a name was not set prior to this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_kprobe_get_name( + const struct lttng_event_rule *rule, const char **name); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_EVENT_RULE_KPROBE_H */ diff --git a/include/lttng/event-rule/kretprobe-internal.h b/include/lttng/event-rule/kretprobe-internal.h new file mode 100644 index 000000000..0bfa79f10 --- /dev/null +++ b/include/lttng/event-rule/kretprobe-internal.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_KRETPROBE_INTERNAL_H +#define LTTNG_EVENT_RULE_KRETPROBE_INTERNAL_H + +#include +#include +#include +#include + +struct lttng_event_rule_kretprobe { + struct lttng_event_rule parent; + char *name; + struct { + uint64_t address; + uint64_t offset; + char *symbol_name; + } probe; +}; + +struct lttng_event_rule_kretprobe_comm { + uint32_t name_len; + uint32_t probe_symbol_len; + /*name, probe symbol_name */ + char payload[]; +} LTTNG_PACKED; + +LTTNG_HIDDEN +ssize_t lttng_event_rule_kretprobe_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **rule); + +LTTNG_HIDDEN +uint64_t lttng_event_rule_kretprobe_get_address( + const struct lttng_event_rule *rule); + +LTTNG_HIDDEN +uint64_t lttng_event_rule_kretprobe_get_offset( + const struct lttng_event_rule *rule); + +LTTNG_HIDDEN +const char *lttng_event_rule_kretprobe_get_symbol_name( + const struct lttng_event_rule *rule); + +#endif /* LTTNG_EVENT_RULE_KRETPROBE_INTERNAL_H */ diff --git a/include/lttng/event-rule/kretprobe.h b/include/lttng/event-rule/kretprobe.h new file mode 100644 index 000000000..d7d50696f --- /dev/null +++ b/include/lttng/event-rule/kretprobe.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_KRETPROBE_H +#define LTTNG_EVENT_RULE_KRETPROBE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * TODO: + */ +extern struct lttng_event_rule *lttng_event_rule_kretprobe_create(void); + +/* + * Set the source of a kretprobe event rule. + * + * TODO: list possible format + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_kretprobe_set_source( + struct lttng_event_rule *rule, const char *source); + +/* + * Set the name of a kretprobe event rule. + * + * The name is copied. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_kretprobe_set_name( + struct lttng_event_rule *rule, const char *name); + +/* + * Get the name of a kretprobe event rule. + * + * The caller does not assume the ownership of the returned name. + * The name shall only only be used for the duration of the event + * rule's lifetime, or before a different name is set. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and a pointer to the event rule's name on + * success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid parameter is passed, + * or LTTNG_EVENT_RULE_STATUS_UNSET if a name was not set prior to this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_kretprobe_get_name( + const struct lttng_event_rule *rule, const char **name); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_EVENT_RULE_KRETPROBE_H */ diff --git a/include/lttng/event-rule/syscall-internal.h b/include/lttng/event-rule/syscall-internal.h new file mode 100644 index 000000000..9c7922dd1 --- /dev/null +++ b/include/lttng/event-rule/syscall-internal.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_SYSCALL_INTERNAL_H +#define LTTNG_EVENT_RULE_SYSCALL_INTERNAL_H + +#include +#include +#include +#include + +struct lttng_event_rule_syscall { + struct lttng_event_rule parent; + char *pattern; + char *filter_expression; + + /* internal use only */ + struct { + char *filter; + struct lttng_bytecode *bytecode; + } internal_filter; +}; + +struct lttng_event_rule_syscall_comm { + uint32_t pattern_len; + uint32_t filter_expression_len; + /* pattern, filter expression */ + char payload[]; +} LTTNG_PACKED; + +LTTNG_HIDDEN +ssize_t lttng_event_rule_syscall_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **rule); + +#endif /* LTTNG_EVENT_RULE_SYSCALL_INTERNAL_H */ diff --git a/include/lttng/event-rule/syscall.h b/include/lttng/event-rule/syscall.h new file mode 100644 index 000000000..ec66918c2 --- /dev/null +++ b/include/lttng/event-rule/syscall.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_SYSCALL_H +#define LTTNG_EVENT_RULE_SYSCALL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a newly allocated syscall event rule. + * + * Returns a new event rule on success, NULL on failure. This event rule must be + * destroyed using lttng_event_rule_destroy(). + */ +extern struct lttng_event_rule *lttng_event_rule_syscall_create(void); + +/* + * Set the pattern of a syscall event rule. + * + * Pattern can contain wildcard '*'. See man lttng-enable-event. + * + * The pattern is copied internally. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_syscall_set_pattern( + struct lttng_event_rule *rule, const char *pattern); + +/* + * Get the pattern of a syscall event rule. + * + * The caller does not assume the ownership of the returned pattern. The + * pattern shall only only be used for the duration of the event rule's + * lifetime, or before a different pattern is set. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and a pointer to the event rule's pattern + * on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid + * parameter is passed, or LTTNG_EVENT_RULE_STATUS_UNSET if a pattern + * was not set prior to this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_syscall_get_pattern( + const struct lttng_event_rule *rule, const char **pattern); + +/* + * Set the filter expression of a syscall event rule. + * + * The expression is copied internally. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_syscall_set_filter( + struct lttng_event_rule *rule, const char *expression); + +/* + * Get the filter expression of a syscall event rule. + * + * The caller does not assume the ownership of the returned filter expression. + * The filter expression shall only only be used for the duration of the event + * rule's lifetime, or before a different filter expression is set. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and a pointer to the event rule's filter + * expression on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid + * parameter is passed, or LTTNG_EVENT_RULE_STATUS_UNSET if a filter expression + * was not set prior to this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_syscall_get_filter( + const struct lttng_event_rule *rule, const char **expression); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_EVENT_RULE_SYSCALL_H */ diff --git a/include/lttng/event-rule/tracepoint-internal.h b/include/lttng/event-rule/tracepoint-internal.h new file mode 100644 index 000000000..60e9f45a8 --- /dev/null +++ b/include/lttng/event-rule/tracepoint-internal.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_TRACEPOINT_INTERNAL_H +#define LTTNG_EVENT_RULE_TRACEPOINT_INTERNAL_H + +#include +#include +#include +#include +#include +#include + +struct lttng_event_rule_tracepoint { + struct lttng_event_rule parent; + + /* Domain */ + enum lttng_domain_type domain; + + /* Name pattern */ + char *pattern; + + /* Filter */ + char *filter_expression; + + /* Loglevel */ + struct { + enum lttng_loglevel_type type; + int value; + } loglevel; + + /* Exclusions */ + + struct { + char **values; + unsigned int count; + } exclusions; + + /* internal use only */ + struct { + char *filter; + struct lttng_bytecode *bytecode; + } internal_filter; +}; + +struct lttng_event_rule_tracepoint_comm { + /* enum lttng_domain_type */ + int8_t domain_type; + /* enum lttng_event_logleven_type */ + int8_t loglevel_type; + int32_t loglevel_value; + uint32_t pattern_len; + uint32_t filter_expression_len; + uint32_t exclusions_count; + uint32_t exclusions_len; + /* + * pattern, filter expression and exclusions each terminating with '\0' + */ + char payload[]; +} LTTNG_PACKED; + +LTTNG_HIDDEN +ssize_t lttng_event_rule_tracepoint_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **rule); + +#endif /* LTTNG_EVENT_RULE_TRACEPOINT_INTERNAL_H */ diff --git a/include/lttng/event-rule/tracepoint.h b/include/lttng/event-rule/tracepoint.h new file mode 100644 index 000000000..75ae4bce2 --- /dev/null +++ b/include/lttng/event-rule/tracepoint.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_TRACEPOINT_H +#define LTTNG_EVENT_RULE_TRACEPOINT_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a newly allocated tracepoint event rule. + * + * Returns a new event rule on success, NULL on failure. This event rule must be + * destroyed using lttng_event_rule_destroy(). + */ +extern struct lttng_event_rule *lttng_event_rule_tracepoint_create( + enum lttng_domain_type domain); + +/* + * Set the pattern of a tracepoint event rule. + * + * Pattern can contain wildcard '*'. See man lttng-enable-event. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_tracepoint_set_pattern( + struct lttng_event_rule *rule, const char *pattern); + +/* + * Get the pattern of a tracepoint event rule. + * + * The caller does not assume the ownership of the returned pattern. The + * pattern shall only only be used for the duration of the event rule's + * lifetime, or before a different pattern is set. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and a pointer to the event rule's pattern + * on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid + * parameter is passed, or LTTNG_EVENT_RULE_STATUS_UNSET if a pattern + * was not set prior to this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_tracepoint_get_pattern( + const struct lttng_event_rule *rule, const char **pattern); + +/* + * Get the domain type of a tracepoint event rule. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and sets the domain type output parameter + * on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid parameter is + * passed, or LTTNG_EVENT_RULE_STATUS_UNSET if a pattern was not set prior to + * this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_tracepoint_get_domain_type( + const struct lttng_event_rule *rule, + enum lttng_domain_type *type); + +/* + * Set the filter expression of a tracepoint event rule. + * + * The expression is copied internally. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_tracepoint_set_filter( + struct lttng_event_rule *rule, const char *expression); + +/* + * Get the filter expression of a tracepoint event rule. + * + * The caller does not assume the ownership of the returned filter expression. + * The filter expression shall only only be used for the duration of the event + * rule's lifetime, or before a different filter expression is set. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and a pointer to the event rule's filter + * expression on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid + * parameter is passed, or LTTNG_EVENT_RULE_STATUS_UNSET if a filter expression + * was not set prior to this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_tracepoint_get_filter( + const struct lttng_event_rule *rule, const char **expression); + +/* + * Set the single loglevel of a tracepoint event rule. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_tracepoint_set_loglevel( + struct lttng_event_rule *rule, int level); + +/* + * Set the loglevel range of a tracepoint event rule. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status +lttng_event_rule_tracepoint_set_loglevel_range( + struct lttng_event_rule *rule, int level); + +/* + * Set the loglevel to all of a tracepoint event rule. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status +lttng_event_rule_tracepoint_set_loglevel_all(struct lttng_event_rule *rule); + +/* + * Get the loglevel type of a tracepoint event rule. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and sets the loglevel type output + * parameter on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid parameter + * is passed, or LTTNG_EVENT_RULE_STATUS_UNSET if a loglevel was not set prior + * to this call. + */ +extern enum lttng_event_rule_status +lttng_event_rule_tracepoint_get_loglevel_type( + const struct lttng_event_rule *rule, + enum lttng_loglevel_type *type); + +/* + * Get the loglevel of a tracepoint event rule. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and sets the loglevel output parameter + * on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid parameter is + * passed, or LTTNG_EVENT_RULE_STATUS_UNSET if a loglevel was not set prior to + * this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_tracepoint_get_loglevel( + const struct lttng_event_rule *rule, int *level); + +/* + * Set the exclusions property of a event rule. + * + * The passed exclusions will be copied to the event_rule. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK on success, + * LTTNG_EVENT_RULE_STATUS_INVALID if invalid parameters are passed, or + * LTTNG_EVENT_RULE_STATUS_UNSUPPORTED if this property is not supported by the + * domain. + */ +extern enum lttng_event_rule_status lttng_event_rule_tracepoint_set_exclusions( + struct lttng_event_rule *rule, + unsigned int count, + const char **exclusions); + +/* + * Get the exclusions property count of a event rule. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and sets the count output parameter + * on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid parameter is + * passed. + */ +extern enum lttng_event_rule_status +lttng_event_rule_tracepoint_get_exclusions_count( + const struct lttng_event_rule *rule, unsigned int *count); + +/* + * Get the event rule exclusion at the given index. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and sets the exclusion output parameter + * on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid parameter is + * passed. + */ +extern enum lttng_event_rule_status +lttng_event_rule_tracepoint_get_exclusion_at_index( + const struct lttng_event_rule *rule, + unsigned int index, + const char **exclusion); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_EVENT_RULE_TRACEPOINT_H */ diff --git a/include/lttng/event-rule/uprobe-internal.h b/include/lttng/event-rule/uprobe-internal.h new file mode 100644 index 000000000..7cccb16ce --- /dev/null +++ b/include/lttng/event-rule/uprobe-internal.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_UPROBE_INTERNAL_H +#define LTTNG_EVENT_RULE_UPROBE_INTERNAL_H + +#include +#include +#include +#include + +struct lttng_event_rule_uprobe { + struct lttng_event_rule parent; + char *name; + struct lttng_userspace_probe_location *location; +}; + +struct lttng_event_rule_uprobe_comm { + uint32_t name_len; + uint32_t location_len; + /*name, location object */ + char payload[]; +} LTTNG_PACKED; + +LTTNG_HIDDEN +ssize_t lttng_event_rule_uprobe_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **rule); + +LTTNG_HIDDEN +struct lttng_userspace_probe_location * +lttng_event_rule_uprobe_get_location_no_const( + const struct lttng_event_rule *rule); + +#endif /* LTTNG_EVENT_RULE_UPROBE_INTERNAL_H */ diff --git a/include/lttng/event-rule/uprobe.h b/include/lttng/event-rule/uprobe.h new file mode 100644 index 000000000..089acc59d --- /dev/null +++ b/include/lttng/event-rule/uprobe.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_EVENT_RULE_UPROBE_H +#define LTTNG_EVENT_RULE_UPROBE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Create a newly allocated uprobe event rule. + * + * Returns a new event rule on success, NULL on failure. This event rule must be + * destroyed using lttng_event_rule_destroy(). + */ +extern struct lttng_event_rule *lttng_event_rule_uprobe_create(void); + +/* + * Set the location of a uprobe event rule. + * + * The location is copied internally. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_uprobe_set_location( + struct lttng_event_rule *rule, + const struct lttng_userspace_probe_location *location); + +/* + * Get the location of a uprobe event rule. + * + * The caller does not assume the ownership of the returned location. + * The location shall only be used for the duration of the event + * rule's lifetime, or before a different location is set. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and a pointer to the event rule's location + * on success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid parameter is + * passed, or LTTNG_EVENT_RULE_STATUS_UNSET if a location was not set prior to + * this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_uprobe_get_location( + const struct lttng_event_rule *rule, + const struct lttng_userspace_probe_location **location); + +/* + * Set the name of a uprobe event rule. + * + * The name is copied internally. + * + * Return LTTNG_EVENT_RULE_STATUS_OK on success, LTTNG_EVENT_RULE_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_event_rule_status lttng_event_rule_uprobe_set_name( + struct lttng_event_rule *rule, const char *name); + +/* + * Get the name of a uprobe event rule. + * + * The caller does not assume the ownership of the returned name. + * The name shall only only be used for the duration of the event + * rule's lifetime, or before a different name is set. + * + * Returns LTTNG_EVENT_RULE_STATUS_OK and a pointer to the event rule's name on + * success, LTTNG_EVENT_RULE_STATUS_INVALID if an invalid parameter is passed, + * or LTTNG_EVENT_RULE_STATUS_UNSET if a name was not set prior to this call. + */ +extern enum lttng_event_rule_status lttng_event_rule_uprobe_get_name( + const struct lttng_event_rule *rule, const char **name); + +#ifdef __cplusplus +} +#endif + +#endif /* LTTNG_EVENT_RULE_UPROBE_H */ diff --git a/include/lttng/lttng.h b/include/lttng/lttng.h index 5d84510b5..b3a895904 100644 --- a/include/lttng/lttng.h +++ b/include/lttng/lttng.h @@ -17,19 +17,33 @@ /* Include every LTTng ABI/API available. */ #include +#include #include +#include +#include +#include +#include #include #include #include #include #include #include +#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include #include #include #include diff --git a/include/lttng/snapshot-internal.h b/include/lttng/snapshot-internal.h index 72d492237..8012a1e4e 100644 --- a/include/lttng/snapshot-internal.h +++ b/include/lttng/snapshot-internal.h @@ -50,7 +50,7 @@ struct lttng_snapshot_output_list { size_t count; /* - * Containes snapshot output object. + * Contains snapshot output object. */ struct lttng_snapshot_output *array; }; diff --git a/include/lttng/snapshot.h b/include/lttng/snapshot.h index 9bdf775a9..33e9a53f9 100644 --- a/include/lttng/snapshot.h +++ b/include/lttng/snapshot.h @@ -39,15 +39,15 @@ void lttng_snapshot_output_destroy(struct lttng_snapshot_output *output); */ /* Return snapshot ID. */ -uint32_t lttng_snapshot_output_get_id(struct lttng_snapshot_output *output); +uint32_t lttng_snapshot_output_get_id(const struct lttng_snapshot_output *output); /* Return maximum size of a snapshot. */ -uint64_t lttng_snapshot_output_get_maxsize(struct lttng_snapshot_output *output); +uint64_t lttng_snapshot_output_get_maxsize(const struct lttng_snapshot_output *output); /* Return snapshot name. */ -const char *lttng_snapshot_output_get_name(struct lttng_snapshot_output *output); +const char *lttng_snapshot_output_get_name(const struct lttng_snapshot_output *output); /* Return snapshot control URL in a text format. */ -const char *lttng_snapshot_output_get_ctrl_url(struct lttng_snapshot_output *output); +const char *lttng_snapshot_output_get_ctrl_url(const struct lttng_snapshot_output *output); /* Return snapshot data URL in a text format. */ -const char *lttng_snapshot_output_get_data_url(struct lttng_snapshot_output *output); +const char *lttng_snapshot_output_get_data_url(const struct lttng_snapshot_output *output); /* * Snapshot output setter family functions. @@ -65,6 +65,36 @@ int lttng_snapshot_output_set_size(uint64_t size, /* Set the snapshot name. */ int lttng_snapshot_output_set_name(const char *name, struct lttng_snapshot_output *output); + +/* + * Set the output destination to be a path on the local filesystem. + * + * The path must be absolute. It can optionally begin with `file://`. + */ +int lttng_snapshot_output_set_local_path(const char *path, + struct lttng_snapshot_output *output); + +/* + * Set the output destination to be the network from a combined control/data + * URL. + * + * `url` must start with `net://` or `net6://`. + */ +int lttng_snapshot_output_set_network_url(const char *url, + struct lttng_snapshot_output *output); + +/* + * Set the output destination to be the network using separate URLs for control + * and data. + * + * `ctrl_url` and `data_url` must start with `tcp://` or `tcp6://`. + */ +int lttng_snapshot_output_set_network_urls( + const char *ctrl_url, const char *data_url, + struct lttng_snapshot_output *output); + +// Deprecated? + /* Set the control URL. Local and remote URL are supported. */ int lttng_snapshot_output_set_ctrl_url(const char *url, struct lttng_snapshot_output *output); diff --git a/include/lttng/trigger/trigger-internal.h b/include/lttng/trigger/trigger-internal.h index cff4504a7..cfe0bee53 100644 --- a/include/lttng/trigger/trigger-internal.h +++ b/include/lttng/trigger/trigger-internal.h @@ -12,39 +12,164 @@ #include #include #include +#include +#include #include #include #include +#include struct lttng_trigger { + struct urcu_ref ref; /* internal use only */ + bool owns_internal_objects; /* internal use only */ + struct lttng_condition *condition; struct lttng_action *action; + char *name; + struct { /* Internal use only */ + bool set; + uint64_t value; + } key; + struct { /* internal use only */ + struct lttng_credentials credentials; + bool set; + } creds; + struct { + enum lttng_trigger_firing_policy_type type; + uint64_t threshold; + uint64_t current_count; + } firing_policy; + + /* This ordered set is used to hold the capture bytecodoes and their + * expression. lttng_action_capture_bytecode_element. + * We could only have bytecodes here... the expression are a left over + * from the generation process of the set. They are used for comparison + * during the gathering process. They are refcounted (TODO) and are the same + * object that are present un the underlying action object/s + */ + struct lttng_dynamic_pointer_array capture_bytecode_set; +}; + +struct lttng_triggers { + struct lttng_dynamic_pointer_array array; }; struct lttng_trigger_comm { /* length excludes its own length. */ + uint32_t name_length /* Includes '\0' */; uint32_t length; - /* A condition and action object follow. */ + uint8_t policy_type; + uint64_t policy_threshold; + /* A name, condition and action object follow. */ char payload[]; } LTTNG_PACKED; +struct lttng_triggers_comm { + uint32_t count; + uint32_t length; + /* Count * lttng_trigger_comm structure */ + char payload[]; +}; + LTTNG_HIDDEN ssize_t lttng_trigger_create_from_buffer(const struct lttng_buffer_view *view, struct lttng_trigger **trigger); LTTNG_HIDDEN -int lttng_trigger_serialize(struct lttng_trigger *trigger, - struct lttng_dynamic_buffer *buf); +int lttng_trigger_serialize(const struct lttng_trigger *trigger, + struct lttng_dynamic_buffer *buf, + int *fd_to_send); + +LTTNG_HIDDEN +bool lttng_trigger_validate(const struct lttng_trigger *trigger); + +LTTNG_HIDDEN +int lttng_trigger_assign(struct lttng_trigger *dst, + const struct lttng_trigger *src); + +LTTNG_HIDDEN +void lttng_trigger_set_key(struct lttng_trigger *trigger, uint64_t key); + +LTTNG_HIDDEN +uint64_t lttng_trigger_get_key(const struct lttng_trigger *trigger); + +LTTNG_HIDDEN +int lttng_trigger_generate_name(struct lttng_trigger *trigger, uint64_t offset); + +LTTNG_HIDDEN +bool lttng_trigger_is_equal(const struct lttng_trigger *a, + const struct lttng_trigger *b); + +LTTNG_HIDDEN +void lttng_trigger_get(struct lttng_trigger *trigger); + +LTTNG_HIDDEN +void lttng_trigger_put(struct lttng_trigger *trigger); + +/* + * Allocate a new list of lttng_trigger. + * The returned object must be freed via lttng_triggers_destroy. + */ +LTTNG_HIDDEN +struct lttng_triggers *lttng_triggers_create(void); + +/* + * Return the non-const pointer of an element at index "index" of a + * lttng_triggers. + * + * The ownership of the lttng_triggers element is NOT transfered. + * The returned object can NOT be freed via lttng_trigger_destroy. + */ +LTTNG_HIDDEN +struct lttng_trigger *lttng_triggers_get_pointer_of_index( + const struct lttng_triggers *triggers, unsigned int index); + +/* + * TODO: + */ +LTTNG_HIDDEN +int lttng_triggers_add( + struct lttng_triggers *triggers, struct lttng_trigger *trigger); + +/* + * Serialize a trigger collection to a lttng_dynamic_buffer. + * Return LTTNG_OK on success, negative lttng error code on error. + */ +LTTNG_HIDDEN +int lttng_triggers_serialize(const struct lttng_triggers *triggers, + struct lttng_dynamic_buffer *buffer); + +LTTNG_HIDDEN +ssize_t lttng_triggers_create_from_buffer(const struct lttng_buffer_view *view, + struct lttng_triggers **triggers); + +LTTNG_HIDDEN +const struct lttng_credentials *lttng_trigger_get_credentials( + const struct lttng_trigger *trigger); LTTNG_HIDDEN -const struct lttng_condition *lttng_trigger_get_const_condition( +void lttng_trigger_set_credentials( + struct lttng_trigger *trigger, uid_t uid, gid_t git); + +LTTNG_HIDDEN +bool lttng_trigger_is_ready_to_fire( + struct lttng_trigger *trigger); + +/* + * Return the type of any uderlying domain requirement. If no particular + * requirement is needed return LTTNG_DOMAIN_NONE. + */ +LTTNG_HIDDEN +enum lttng_domain_type lttng_trigger_get_underlying_domain_type_restriction( const struct lttng_trigger *trigger); LTTNG_HIDDEN -const struct lttng_action *lttng_trigger_get_const_action( +unsigned int lttng_trigger_get_capture_bytecode_count( const struct lttng_trigger *trigger); LTTNG_HIDDEN -bool lttng_trigger_validate(struct lttng_trigger *trigger); +const struct lttng_bytecode * +lttng_trigger_get_capture_bytecode_at_index( + const struct lttng_trigger *trigger, unsigned int index); #endif /* LTTNG_TRIGGER_INTERNAL_H */ diff --git a/include/lttng/trigger/trigger.h b/include/lttng/trigger/trigger.h index feffc6a8f..5ab4fb26e 100644 --- a/include/lttng/trigger/trigger.h +++ b/include/lttng/trigger/trigger.h @@ -11,6 +11,8 @@ struct lttng_action; struct lttng_condition; struct lttng_trigger; +/* A collection of trigger */ +struct lttng_triggers; #ifdef __cplusplus extern "C" { @@ -21,6 +23,20 @@ enum lttng_register_trigger_status { LTTNG_REGISTER_TRIGGER_STATUS_INVALID = -1, }; +enum lttng_trigger_status { + LTTNG_TRIGGER_STATUS_OK = 0, + LTTNG_TRIGGER_STATUS_ERROR = -1, + LTTNG_TRIGGER_STATUS_UNKNOWN = -2, + LTTNG_TRIGGER_STATUS_INVALID = -3, + LTTNG_TRIGGER_STATUS_UNSET = -4, + LTTNG_TRIGGER_STATUS_UNSUPPORTED = -5, +}; + +enum lttng_trigger_firing_policy_type { + LTTNG_TRIGGER_FIRE_EVERY_N = 0, + LTTNG_TRIGGER_FIRE_ONCE_AFTER_N = 1, +}; + /* * Create a trigger object associating a condition and an action. * @@ -32,6 +48,9 @@ enum lttng_register_trigger_status { * The caller retains the ownership of both the condition and action * and both must be kept alive for the lifetime of the trigger object. * + * If the action is a notification action with capture descriptors, + * the condition must be an event rule condition. + * * A trigger must be registered in order to become activate and can * be destroyed after its registration. * @@ -52,6 +71,9 @@ extern struct lttng_trigger *lttng_trigger_create( extern struct lttng_condition *lttng_trigger_get_condition( struct lttng_trigger *trigger); +const struct lttng_condition *lttng_trigger_get_const_condition( + const struct lttng_trigger *trigger); + /* * Get the action of a trigger. * @@ -62,6 +84,58 @@ extern struct lttng_condition *lttng_trigger_get_condition( extern struct lttng_action *lttng_trigger_get_action( struct lttng_trigger *trigger); +const struct lttng_action *lttng_trigger_get_const_action( + const struct lttng_trigger *trigger); + +/* + * Get the name of a trigger. + * + * The caller does not assume the ownership of the returned name. + * The name shall only only be used for the duration of the trigger's + * lifetime, or before a different name is set. + * + * Returns LTTNG_TRIGGER_STATUS_OK and a pointer to the trigger's name on + * success, LTTNG_TRIGGER_STATUS_INVALID if an invalid parameter is passed, + * or LTTNG_TRIGGER_STATUS_UNSET if a name was not set prior to this call. + */ +extern enum lttng_trigger_status lttng_trigger_get_name( + const struct lttng_trigger *trigger, const char **name); + +/* + * Set the trigger name. + * + * A name is optional. + * A name will be assigned on trigger registration if no name is set. + * + * The name is copied. + * + * Return LTTNG_TRIGGER_STATUS_OK on success, LTTNG_TRIGGER_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_trigger_status lttng_trigger_set_name( + struct lttng_trigger *trigger, const char *name); + +/* + * Set the trigger firing policy. + * + * This is optional. By default a trigger is set to fire each time the + * associated condition occurs. + * + * Threshold is the number of time the condition must be hit before the policy is + * enacted. + * + * Return LTTNG_TRIGGER_STATUS_OK on success, LTTNG_TRIGGER_STATUS_INVALID + * if invalid parameters are passed. + */ +extern enum lttng_trigger_status lttng_trigger_set_firing_policy( + struct lttng_trigger *trigger, + enum lttng_trigger_firing_policy_type policy_type, + unsigned long long threshold); +extern enum lttng_trigger_status lttng_trigger_get_firing_policy( + const struct lttng_trigger *trigger, + enum lttng_trigger_firing_policy_type *policy_type, + unsigned long long *threshold); + /* * Destroy (frees) a trigger object. */ @@ -83,7 +157,44 @@ extern int lttng_register_trigger(struct lttng_trigger *trigger); * * Return 0 on success, a negative LTTng error code on error. */ -extern int lttng_unregister_trigger(struct lttng_trigger *trigger); +extern int lttng_unregister_trigger(const struct lttng_trigger *trigger); + +/* + * List current triggers. + * + * On success, triggers is allocated. + * The trigger collection must be free by the caller with lttng_destroy_triggers + * + * Returns 0 on success, else a negative LTTng error code. + */ +extern int lttng_list_triggers(struct lttng_triggers **triggers); + +/* + * Get a trigger from the collection at a given index. + * + * Note that the collection maintains the ownership of the returned trigger. + * It must not be destroyed by the user, nor should it be held beyond the + * lifetime of the trigger collection. + * + * Returns a trigger, or NULL on error. + */ +extern const struct lttng_trigger *lttng_triggers_get_at_index( + const struct lttng_triggers *triggers, unsigned int index); + +/* + * Get the number of trigger in a tracker id list. + * + * Return LTTNG_TRIGGER_STATUS_OK on success, + * LTTNG_TRIGGER_STATUS_INVALID when passed invalid parameters. + */ +extern enum lttng_trigger_status lttng_triggers_get_count( + const struct lttng_triggers *triggers, unsigned int *count); + +/* + * Destroy a trigger collection. + */ +extern void lttng_triggers_destroy(struct lttng_triggers *ids); + #ifdef __cplusplus } diff --git a/include/lttng/userspace-probe-internal.h b/include/lttng/userspace-probe-internal.h index 828795493..6623fd19f 100644 --- a/include/lttng/userspace-probe-internal.h +++ b/include/lttng/userspace-probe-internal.h @@ -14,6 +14,10 @@ #include #include +typedef bool (*userspace_probe_location_equal_cb)( + const struct lttng_userspace_probe_location *a, + const struct lttng_userspace_probe_location *b); + /* * No elf-specific comm structure is defined since no elf-specific payload is * currently needed. @@ -79,6 +83,7 @@ struct lttng_userspace_probe_location_tracepoint_comm { struct lttng_userspace_probe_location { enum lttng_userspace_probe_location_type type; struct lttng_userspace_probe_location_lookup_method *lookup_method; + userspace_probe_location_equal_cb equal; }; struct lttng_userspace_probe_location_function { @@ -142,4 +147,18 @@ LTTNG_HIDDEN struct lttng_userspace_probe_location *lttng_userspace_probe_location_copy( const struct lttng_userspace_probe_location *location); +LTTNG_HIDDEN +bool lttng_userspace_probe_location_lookup_method_is_equal( + const struct lttng_userspace_probe_location_lookup_method *a, + const struct lttng_userspace_probe_location_lookup_method *b); + +LTTNG_HIDDEN +bool lttng_userspace_probe_location_is_equal( + const struct lttng_userspace_probe_location *a, + const struct lttng_userspace_probe_location *b); + +LTTNG_HIDDEN +int lttng_userspace_probe_location_set_binary_fd( + struct lttng_userspace_probe_location *location, int fd); + #endif /* LTTNG_USERSPACE_PROBE_INTERNAL_H */ diff --git a/src/bin/lttng-sessiond/Makefile.am b/src/bin/lttng-sessiond/Makefile.am index ee53655be..c4540a521 100644 --- a/src/bin/lttng-sessiond/Makefile.am +++ b/src/bin/lttng-sessiond/Makefile.am @@ -54,7 +54,8 @@ lttng_sessiond_SOURCES = utils.c utils.h \ manage-kernel.c manage-kernel.h \ manage-consumer.c manage-consumer.h \ clear.c clear.h \ - tracker.c tracker.h + tracker.c tracker.h \ + action-executor.c action-executor.h if HAVE_LIBLTTNG_UST_CTL lttng_sessiond_SOURCES += trace-ust.c ust-registry.c ust-app.c \ @@ -67,7 +68,7 @@ endif lttng_sessiond_SOURCES += lttng-sessiond.h main.c # link on liblttngctl for check if sessiond is already alive. -lttng_sessiond_LDADD = -lurcu-common -lurcu $(KMOD_LIBS) \ +lttng_sessiond_LDADD = -lurcu-common -lmsgpackc -lurcu $(KMOD_LIBS) \ $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la \ $(top_builddir)/src/common/sessiond-comm/libsessiond-comm.la \ $(top_builddir)/src/common/kernel-ctl/libkernel-ctl.la \ diff --git a/src/bin/lttng-sessiond/action-executor.c b/src/bin/lttng-sessiond/action-executor.c new file mode 100644 index 000000000..66ebd7c43 --- /dev/null +++ b/src/bin/lttng-sessiond/action-executor.c @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "action-executor.h" +#include "cmd.h" +#include "health-sessiond.h" +#include "lttng-sessiond.h" +#include "notification-thread-internal.h" +#include "session.h" +#include "thread.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THREAD_NAME "Action Executor" +#define MAX_QUEUED_WORK_COUNT 8192 + +struct action_work_item { + uint64_t id; + struct lttng_trigger *trigger; + struct notification_client_list *client_list; + struct cds_list_head list_node; + struct lttng_trigger_notification *trigger_notification; +}; + +struct action_executor { + struct lttng_thread *thread; + struct notification_thread_handle *notification_thread_handle; + struct { + uint64_t pending_count; + struct cds_list_head list; + pthread_cond_t cond; + pthread_mutex_t lock; + } work; + bool should_quit; + uint64_t next_work_item_id; +}; + +typedef int (*action_executor_handler)(struct action_executor *executor, + const struct action_work_item *, + const struct lttng_action *action); + +static int action_executor_notify_handler(struct action_executor *executor, + const struct action_work_item *, + const struct lttng_action *); +static int action_executor_start_session_handler(struct action_executor *executor, + const struct action_work_item *, + const struct lttng_action *); +static int action_executor_stop_session_handler(struct action_executor *executor, + const struct action_work_item *, + const struct lttng_action *); +static int action_executor_rotate_session_handler(struct action_executor *executor, + const struct action_work_item *, + const struct lttng_action *); +static int action_executor_snapshot_session_handler(struct action_executor *executor, + const struct action_work_item *, + const struct lttng_action *); +static int action_executor_group_handler(struct action_executor *executor, + const struct action_work_item *, + const struct lttng_action *); +static int action_executor_generic_handler(struct action_executor *executor, + const struct action_work_item *, + const struct lttng_action *); + +static const action_executor_handler action_executors[] = { + [LTTNG_ACTION_TYPE_NOTIFY] = action_executor_notify_handler, + [LTTNG_ACTION_TYPE_START_SESSION] = action_executor_start_session_handler, + [LTTNG_ACTION_TYPE_STOP_SESSION] = action_executor_stop_session_handler, + [LTTNG_ACTION_TYPE_ROTATE_SESSION] = action_executor_rotate_session_handler, + [LTTNG_ACTION_TYPE_SNAPSHOT_SESSION] = action_executor_snapshot_session_handler, + [LTTNG_ACTION_TYPE_GROUP] = action_executor_group_handler, +}; + +static const char *get_action_name(const struct lttng_action *action) +{ + const char *action_type_names[] = { + [LTTNG_ACTION_TYPE_NOTIFY] = "Notify", + [LTTNG_ACTION_TYPE_START_SESSION] = "Start session", + [LTTNG_ACTION_TYPE_STOP_SESSION] = "Stop session", + [LTTNG_ACTION_TYPE_ROTATE_SESSION] = "Rotate session", + [LTTNG_ACTION_TYPE_SNAPSHOT_SESSION] = "Snapshot session", + [LTTNG_ACTION_TYPE_GROUP] = "Group", + }; + + return action_type_names[lttng_action_get_type(action)]; +} + +static const char *get_trigger_name(const struct lttng_trigger *trigger) +{ + const char *trigger_name; + enum lttng_trigger_status trigger_status; + + trigger_status = lttng_trigger_get_name(trigger, &trigger_name); + assert(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + return trigger_name; +} + +static int client_handle_transmission_status( + struct notification_client *client, + enum client_transmission_status status, + void *user_data) +{ + int ret = 0; + struct action_executor *executor = user_data; + bool update_communication = true; + + ASSERT_LOCKED(client->lock); + + switch (status) { + case CLIENT_TRANSMISSION_STATUS_COMPLETE: + DBG("Successfully sent full notification to client, client_id = %" PRIu64, + client->id); + update_communication = false; + break; + case CLIENT_TRANSMISSION_STATUS_QUEUED: + DBG("Queued notification in client outgoing buffer, client_id = %" PRIu64, + client->id); + break; + case CLIENT_TRANSMISSION_STATUS_FAIL: + DBG("Communication error occurred while sending notification to client, client_id = %" PRIu64, + client->id); + client->communication.active = false; + break; + default: + ERR("Fatal error encoutered while sending notification to client, client_id = %" PRIu64, + client->id); + client->communication.active = false; + ret = -1; + goto end; + } + + if (!update_communication) { + goto end; + } + + ret = notification_thread_client_communication_update( + executor->notification_thread_handle, client->id, + status); +end: + return ret; +} + +static int action_executor_notify_handler(struct action_executor *executor, + const struct action_work_item *work_item, + const struct lttng_action *action) +{ + int ret = 0; + struct lttng_evaluation *evaluation = NULL; + struct lttng_trigger_notification *notification = work_item->trigger_notification; + unsigned int capture_count = 0; + + assert(work_item->client_list); + + if (LTTNG_CONDITION_STATUS_OK != + lttng_condition_event_rule_get_capture_descriptor_count( + lttng_trigger_get_const_condition(work_item->trigger), + &capture_count)) { + ERR("Get capture count"); + ret = -1; + goto end; + } + + if (!notification->capture_buffer && capture_count != 0) { + ERR("Expected capture but capture buffer is null"); + ret = -1; + goto end; + } + + evaluation = lttng_evaluation_event_rule_create( + container_of(lttng_trigger_get_const_condition(work_item->trigger), + struct lttng_condition_event_rule, + parent), + get_trigger_name(work_item->trigger), + notification->capture_buffer, + notification->capture_buf_size, false); + if (!evaluation) { + ERR("Failed to create event rule hit evaluation"); + ret = -1; + goto end; + } + + ret = notification_client_list_send_evaluation(work_item->client_list, + lttng_trigger_get_const_condition(work_item->trigger), + evaluation, + lttng_trigger_get_credentials(work_item->trigger), NULL, + client_handle_transmission_status, executor); +end: + lttng_evaluation_destroy(evaluation); + return ret; +} + +static int action_executor_start_session_handler(struct action_executor *executor, + const struct action_work_item *work_item, + const struct lttng_action *action) +{ + int ret = 0; + const char *session_name; + enum lttng_action_status action_status; + struct ltt_session *session; + + action_status = lttng_action_start_session_get_session_name( + action, &session_name); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to get session name from \"%s\" action", + get_action_name(action)); + ret = -1; + goto end; + } + + session_lock_list(); + session = session_find_by_name(session_name); + if (session) { + enum lttng_error_code cmd_ret; + + session_lock(session); + cmd_ret = cmd_start_trace(session); + session_unlock(session); + + switch (cmd_ret) { + case LTTNG_OK: + DBG("Successfully started session \"%s\" on behalf of trigger \"%s\"", + session_name, + get_trigger_name(work_item->trigger)); + break; + case LTTNG_ERR_TRACE_ALREADY_STARTED: + DBG("Attempted to start session \"%s\" on behalf of trigger \"%s\" but it was already started", + session_name, + get_trigger_name(work_item->trigger)); + break; + default: + WARN("Failed to start session \"%s\" on behalf of trigger \"%s\": %s", + session_name, + get_trigger_name(work_item->trigger), + lttng_strerror(-cmd_ret)); + break; + } + session_put(session); + } else { + DBG("Failed to find session \"%s\" by name while executing \"%s\" action of trigger \"%s\"", + session_name, get_action_name(action), + get_trigger_name(work_item->trigger)); + } + session_unlock_list(); +end: + return ret; +} + +static int action_executor_stop_session_handler(struct action_executor *executor, + const struct action_work_item *work_item, + const struct lttng_action *action) +{ + int ret = 0; + const char *session_name; + enum lttng_action_status action_status; + struct ltt_session *session; + + action_status = lttng_action_stop_session_get_session_name( + action, &session_name); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to get session name from \"%s\" action", + get_action_name(action)); + ret = -1; + goto end; + } + + session_lock_list(); + session = session_find_by_name(session_name); + if (session) { + enum lttng_error_code cmd_ret; + + session_lock(session); + cmd_ret = cmd_stop_trace(session); + session_unlock(session); + + switch (cmd_ret) { + case LTTNG_OK: + DBG("Successfully stopped session \"%s\" on behalf of trigger \"%s\"", + session_name, + get_trigger_name(work_item->trigger)); + break; + case LTTNG_ERR_TRACE_ALREADY_STOPPED: + DBG("Attempted to stop session \"%s\" on behalf of trigger \"%s\" but it was already stopped", + session_name, + get_trigger_name(work_item->trigger)); + break; + default: + WARN("Failed to stop session \"%s\" on behalf of trigger \"%s\": %s", + session_name, + get_trigger_name(work_item->trigger), + lttng_strerror(-cmd_ret)); + break; + } + session_put(session); + } else { + DBG("Failed to find session \"%s\" by name while executing \"%s\" action of trigger \"%s\"", + session_name, get_action_name(action), + get_trigger_name(work_item->trigger)); + } + session_unlock_list(); +end: + return ret; +} + +static int action_executor_rotate_session_handler(struct action_executor *executor, + const struct action_work_item *work_item, + const struct lttng_action *action) +{ + int ret = 0; + const char *session_name; + enum lttng_action_status action_status; + struct ltt_session *session; + + action_status = lttng_action_rotate_session_get_session_name( + action, &session_name); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to get session name from \"%s\" action", + get_action_name(action)); + ret = -1; + goto end; + } + + session_lock_list(); + session = session_find_by_name(session_name); + if (session) { + enum lttng_error_code cmd_ret; + + session_lock(session); + cmd_ret = cmd_rotate_session(session, NULL, false, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); + session_unlock(session); + + switch (cmd_ret) { + case LTTNG_OK: + DBG("Successfully started rotation of session \"%s\" on behalf of trigger \"%s\"", + session_name, + get_trigger_name(work_item->trigger)); + break; + case LTTNG_ERR_ROTATION_PENDING: + DBG("Attempted to start a rotation of session \"%s\" on behalf of trigger \"%s\" but a rotation is already ongoing", + session_name, + get_trigger_name(work_item->trigger)); + break; + case LTTNG_ERR_ROTATION_MULTIPLE_AFTER_STOP: + case LTTNG_ERR_ROTATION_AFTER_STOP_CLEAR: + DBG("Attempted to start a rotation of session \"%s\" on behalf of trigger \"%s\" but a rotation has already been completed since the last stop or clear", + session_name, + get_trigger_name(work_item->trigger)); + break; + default: + WARN("Failed to start a rotation of session \"%s\" on behalf of trigger \"%s\": %s", + session_name, + get_trigger_name(work_item->trigger), + lttng_strerror(-cmd_ret)); + break; + } + session_put(session); + } else { + DBG("Failed to find session \"%s\" by name while executing \"%s\" action of trigger \"%s\"", + session_name, get_action_name(action), + get_trigger_name(work_item->trigger)); + } + session_unlock_list(); +end: + return ret; +} + +static int action_executor_snapshot_session_handler(struct action_executor *executor, + const struct action_work_item *work_item, + const struct lttng_action *action) +{ + int ret = 0; + const char *session_name; + enum lttng_action_status action_status; + struct ltt_session *session; + const struct lttng_snapshot_output default_snapshot_output = { + .max_size = UINT64_MAX, + }; + const struct lttng_snapshot_output *snapshot_output = + &default_snapshot_output; + + action_status = lttng_action_snapshot_session_get_session_name( + action, &session_name); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to get session name from \"%s\" action", + get_action_name(action)); + ret = -1; + goto end; + } + + action_status = lttng_action_snapshot_session_get_output_const( + action, &snapshot_output); + if (action_status != LTTNG_ACTION_STATUS_OK && + action_status != LTTNG_ACTION_STATUS_UNSET) { + ERR("Failed to get output from \"%s\" action", + get_action_name(action)); + ret = -1; + goto end; + } + + session_lock_list(); + session = session_find_by_name(session_name); + if (session) { + enum lttng_error_code cmd_ret; + + session_lock(session); + cmd_ret = cmd_snapshot_record(session, snapshot_output, 0); + session_unlock(session); + + switch (cmd_ret) { + case LTTNG_OK: + DBG("Successfully recorded snapshot of session \"%s\" on behalf of trigger \"%s\"", + session_name, + get_trigger_name(work_item->trigger)); + break; + default: + WARN("Failed to record snapshot of session \"%s\" on behalf of trigger \"%s\": %s", + session_name, + get_trigger_name(work_item->trigger), + lttng_strerror(-cmd_ret)); + break; + } + session_put(session); + } else { + DBG("Failed to find session \"%s\" by name while executing \"%s\" action of trigger \"%s\"", + session_name, get_action_name(action), + get_trigger_name(work_item->trigger)); + } + session_unlock_list(); +end: + return ret; +} + +static int action_executor_group_handler(struct action_executor *executor, + const struct action_work_item *work_item, + const struct lttng_action *action_group) +{ + int ret = 0; + unsigned int i, count; + enum lttng_action_status action_status; + + action_status = lttng_action_group_get_count(action_group, &count); + if (action_status != LTTNG_ACTION_STATUS_OK) { + /* Fatal error. */ + ERR("Failed to get count of action in action group"); + ret = -1; + goto end; + } + + DBG("Action group has %u action%s", count, count != 1 ? "s" : ""); + for (i = 0; i < count; i++) { + const struct lttng_action *action = + lttng_action_group_get_at_index_const( + action_group, i); + + ret = action_executor_generic_handler( + executor, work_item, action); + if (ret) { + ERR("Stopping the execution of the action group of trigger \"%s\" following a fatal error", + get_trigger_name(work_item->trigger)); + goto end; + } + } +end: + return ret; +} + +static int action_executor_generic_handler(struct action_executor *executor, + const struct action_work_item *work_item, + const struct lttng_action *action) +{ + DBG("Executing action \"%s\" of trigger \"%s\" action work item %" PRIu64, + get_action_name(action), + get_trigger_name(work_item->trigger), + work_item->id); + + return action_executors[lttng_action_get_type(action)]( + executor, work_item, action); +} + +static int action_work_item_execute(struct action_executor *executor, + struct action_work_item *work_item) +{ + int ret; + const struct lttng_action *action = + lttng_trigger_get_const_action(work_item->trigger); + + DBG("Starting execution of action work item %" PRIu64 " of trigger \"%s\"", + work_item->id, get_trigger_name(work_item->trigger)); + ret = action_executor_generic_handler(executor, work_item, action); + DBG("Completed execution of action work item %" PRIu64 " of trigger \"%s\"", + work_item->id, get_trigger_name(work_item->trigger)); + return ret; +} + +static void action_work_item_destroy(struct action_work_item *work_item) +{ + lttng_trigger_put(work_item->trigger); + notification_client_list_put(work_item->client_list); + lttng_trigger_notification_destroy(work_item->trigger_notification); + free(work_item); +} + +static void *action_executor_thread(void *_data) +{ + struct action_executor *executor = _data; + + assert(executor); + + health_register(health_sessiond, HEALTH_SESSIOND_TYPE_ACTION_EXECUTOR); + + rcu_register_thread(); + rcu_thread_online(); + + DBG("Entering work execution loop"); + pthread_mutex_lock(&executor->work.lock); + while (!executor->should_quit) { + int ret; + struct action_work_item *work_item; + + health_code_update(); + if (executor->work.pending_count == 0) { + health_poll_entry(); + DBG("No work items enqueued, entering wait"); + pthread_cond_wait(&executor->work.cond, + &executor->work.lock); + DBG("Woke-up from wait"); + health_poll_exit(); + continue; + } + + /* Pop item from front of the list with work lock held. */ + work_item = cds_list_first_entry(&executor->work.list, + struct action_work_item, list_node); + cds_list_del(&work_item->list_node); + executor->work.pending_count--; + + /* + * Work can be performed without holding the work lock, + * allowing new items to be queued. + */ + pthread_mutex_unlock(&executor->work.lock); + ret = action_work_item_execute(executor, work_item); + action_work_item_destroy(work_item); + if (ret) { + /* Fatal error. */ + break; + } + health_code_update(); + pthread_mutex_lock(&executor->work.lock); + } + pthread_mutex_unlock(&executor->work.lock); + DBG("Left work execution loop"); + + health_code_update(); + + rcu_thread_offline(); + rcu_unregister_thread(); + health_unregister(health_sessiond); + + return NULL; +} + +static bool shutdown_action_executor_thread(void *_data) +{ + struct action_executor *executor = _data; + + /* TODO. */ + executor->should_quit = true; + pthread_cond_signal(&executor->work.cond); + return true; +} + +static void clean_up_action_executor_thread(void *_data) +{ + struct action_executor *executor = _data; + + assert(cds_list_empty(&executor->work.list)); + + pthread_mutex_destroy(&executor->work.lock); + pthread_cond_destroy(&executor->work.cond); + free(executor); +} + +struct action_executor *action_executor_create( + struct notification_thread_handle *handle) +{ + struct action_executor *executor = zmalloc(sizeof(*executor)); + + if (!executor) { + goto end; + } + + CDS_INIT_LIST_HEAD(&executor->work.list); + pthread_cond_init(&executor->work.cond, NULL); + pthread_mutex_init(&executor->work.lock, NULL); + executor->notification_thread_handle = handle; + + executor->thread = lttng_thread_create(THREAD_NAME, + action_executor_thread, shutdown_action_executor_thread, + clean_up_action_executor_thread, executor); +end: + return executor; +} + +void action_executor_destroy(struct action_executor *executor) +{ + struct action_work_item *work_item, *tmp; + + /* TODO Wait for work list to drain? */ + lttng_thread_shutdown(executor->thread); + pthread_mutex_lock(&executor->work.lock); + if (executor->work.pending_count != 0) { + WARN("%" PRIu64 + " trigger action%s still queued for execution and will be discarded", + executor->work.pending_count, + executor->work.pending_count == 1 ? " is" : + "s are"); + } + + cds_list_for_each_entry_safe ( + work_item, tmp, &executor->work.list, list_node) { + WARN("Discarding action work item %" PRIu64 + " associated to trigger \"%s\"", + work_item->id, get_trigger_name(work_item->trigger)); + cds_list_del(&work_item->list_node); + action_work_item_destroy(work_item); + } + pthread_mutex_unlock(&executor->work.lock); + lttng_thread_put(executor->thread); +} + +/* RCU read-lock must be held by the caller. */ +enum action_executor_status action_executor_enqueue( + struct action_executor *executor, + struct lttng_trigger *trigger, + struct notification_client_list *client_list, + struct lttng_trigger_notification *trigger_notification) +{ + enum action_executor_status executor_status = ACTION_EXECUTOR_STATUS_OK; + const uint64_t work_item_id = executor->next_work_item_id++; + struct action_work_item *work_item; + bool signal = false; + + pthread_mutex_lock(&executor->work.lock); + /* Check for queue overflow. */ + if (executor->work.pending_count >= MAX_QUEUED_WORK_COUNT) { + /* Most likely spammy, remove if it is the case. */ + DBG("Refusing to enqueue action for trigger \"%s\" as work item %" PRIu64 + " (overflow)", + get_trigger_name(trigger), work_item_id); + executor_status = ACTION_EXECUTOR_STATUS_OVERFLOW; + goto error_unlock; + } + + work_item = zmalloc(sizeof(*work_item)); + if (!work_item) { + PERROR("Failed to allocate action executor work item on behalf of trigger \"%s\"", + get_trigger_name(trigger)); + executor_status = ACTION_EXECUTOR_STATUS_ERROR; + goto error_unlock; + } + + lttng_trigger_get(trigger); + if (client_list) { + const bool reference_acquired = + notification_client_list_get(client_list); + + assert(reference_acquired); + } + + *work_item = (typeof(*work_item)){ + .id = work_item_id, + .trigger = trigger, + .client_list = client_list, + .list_node = CDS_LIST_HEAD_INIT(work_item->list_node), + .trigger_notification = trigger_notification, + }; + cds_list_add_tail(&work_item->list_node, &executor->work.list); + executor->work.pending_count++; + DBG("Enqueued action for trigger \"%s\" as work item %" PRIu64, + get_trigger_name(trigger), work_item_id); + signal = true; + + /* + * Note: + * Ownership of the lttng_trigger_notification object passed to the work + * item object incidentally to the executor list. + * Caller is responsible for freeing in case of error. + */ + +error_unlock: + pthread_mutex_unlock(&executor->work.lock); + if (signal) { + pthread_cond_signal(&executor->work.cond); + } + return executor_status; +} diff --git a/src/bin/lttng-sessiond/action-executor.h b/src/bin/lttng-sessiond/action-executor.h new file mode 100644 index 000000000..b841e3b58 --- /dev/null +++ b/src/bin/lttng-sessiond/action-executor.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#ifndef ACTION_EXECUTOR_H +#define ACTION_EXECUTOR_H + +struct action_executor; +struct notification_thread_handle; +struct lttng_trigger; +struct notification_client_list; +struct lttng_trigger_notification; + +enum action_executor_status { + ACTION_EXECUTOR_STATUS_OK, + ACTION_EXECUTOR_STATUS_OVERFLOW, + ACTION_EXECUTOR_STATUS_ERROR, + ACTION_EXECUTOR_STATUS_INVALID, +}; + +struct action_executor *action_executor_create( + struct notification_thread_handle *handle); + +void action_executor_destroy(struct action_executor *executor); + +enum action_executor_status action_executor_enqueue( + struct action_executor *executor, + struct lttng_trigger *trigger, + struct notification_client_list *list, + struct lttng_trigger_notification *priv_data); + +#endif /* ACTION_EXECUTOR_H */ diff --git a/src/bin/lttng-sessiond/agent-thread.c b/src/bin/lttng-sessiond/agent-thread.c index 06ef377a3..44f41059b 100644 --- a/src/bin/lttng-sessiond/agent-thread.c +++ b/src/bin/lttng-sessiond/agent-thread.c @@ -59,6 +59,8 @@ static void update_agent_app(const struct agent_app *app) { struct ltt_session *session, *stmp; struct ltt_session_list *list; + struct agent *trigger_agent; + struct lttng_ht_iter iter; list = session_get_list(); assert(list); @@ -82,6 +84,14 @@ static void update_agent_app(const struct agent_app *app) session_unlock(session); session_put(session); } + + /* Do we need more locking here? maybe against trigger add? */ + rcu_read_lock(); + cds_lfht_for_each_entry (trigger_agents_ht_by_domain->ht, &iter.iter, + trigger_agent, node.node) { + agent_update(trigger_agent, app); + } + rcu_read_unlock(); } /* diff --git a/src/bin/lttng-sessiond/agent.c b/src/bin/lttng-sessiond/agent.c index e5978be5c..2c79a1bb3 100644 --- a/src/bin/lttng-sessiond/agent.c +++ b/src/bin/lttng-sessiond/agent.c @@ -11,6 +11,12 @@ #include #include +#include +#include +#include +#include +#include + #include #include @@ -101,7 +107,8 @@ no_match: } /* - * Match function for the events hash table lookup by name and loglevel. + * Match function for the events hash table lookup by name, loglevel and + * filter_expression. */ static int ht_match_event(struct cds_lfht_node *node, const void *_key) @@ -682,6 +689,7 @@ int agent_enable_event(struct agent_event *event, } event->enabled = 1; + event->user_refcount++; ret = LTTNG_OK; error: @@ -791,6 +799,17 @@ int agent_disable_event(struct agent_event *event, goto end; } + if (event->user_refcount - 1 != 0) { + /* + * Disable the agent event only when all users (trigger etc.) + * have disabled it. + */ + + event->user_refcount--; + ret = LTTNG_OK; + goto end; + } + rcu_read_lock(); cds_lfht_for_each_entry(agent_apps_ht_by_sock->ht, &iter.iter, app, @@ -806,6 +825,7 @@ int agent_disable_event(struct agent_event *event, } } + event->user_refcount = 0; event->enabled = 0; error: @@ -1109,7 +1129,7 @@ error: */ struct agent_event *agent_create_event(const char *name, enum lttng_loglevel_type loglevel_type, int loglevel_value, - struct lttng_filter_bytecode *filter, char *filter_expression) + struct lttng_bytecode *filter, char *filter_expression) { struct agent_event *event = NULL; @@ -1206,6 +1226,66 @@ void agent_find_events_by_name(const char *name, struct agent *agt, ht_match_event_by_name, &key, &iter->iter); } +/* + * Find the agent event matching the trigger. + * + * RCU read side lock MUST be acquired. It must be kept for as long as + * the returned agent_event is used. + * + * Return object if found else NULL. + */ +struct agent_event *agent_find_event_by_trigger( + const struct lttng_trigger *trigger, struct agent *agt) +{ + enum lttng_condition_status c_status; + enum lttng_event_rule_status er_status; + enum lttng_domain_type d_type; + const struct lttng_condition *condition; + const struct lttng_event_rule *rule; + const char *name; + const char *filter_expression; + /* TODO validate if this is the unset value or no */ + int loglevel_value = 0; + enum lttng_loglevel_type loglevel_type; + + assert(agt); + assert(agt->events); + + condition = lttng_trigger_get_const_condition(trigger); + + assert(lttng_condition_get_type(condition) == + LTTNG_CONDITION_TYPE_EVENT_RULE_HIT); + + c_status = lttng_condition_event_rule_get_rule(condition, &rule); + assert(c_status == LTTNG_CONDITION_STATUS_OK); + + assert(lttng_event_rule_get_type(rule) == + LTTNG_EVENT_RULE_TYPE_TRACEPOINT); + + d_type = lttng_event_rule_get_domain_type(rule); + assert(d_type == LTTNG_DOMAIN_JUL || d_type == LTTNG_DOMAIN_LOG4J || + d_type == LTTNG_DOMAIN_PYTHON); + + /* Get the name (aka pattern) */ + er_status = lttng_event_rule_tracepoint_get_pattern(rule, &name); + assert(er_status == LTTNG_EVENT_RULE_STATUS_OK); + + /* Get the internal filter_expression */ + filter_expression = lttng_event_rule_get_filter(rule); + + er_status = lttng_event_rule_tracepoint_get_loglevel_type( + rule, &loglevel_type); + assert(er_status == LTTNG_EVENT_RULE_STATUS_OK); + if (loglevel_type != LTTNG_EVENT_LOGLEVEL_ALL) { + er_status = lttng_event_rule_tracepoint_get_loglevel( + rule, &loglevel_value); + assert(er_status == LTTNG_EVENT_RULE_STATUS_OK); + } + + return agent_find_event(name, loglevel_type, loglevel_value, + filter_expression, agt); +} + /* * Get the next agent event duplicate by name. This should be called * after a call to agent_find_events_by_name() to iterate on events. @@ -1233,8 +1313,10 @@ void agent_event_next_duplicate(const char *name, * Return object if found else NULL. */ struct agent_event *agent_find_event(const char *name, - enum lttng_loglevel_type loglevel_type, int loglevel_value, - char *filter_expression, struct agent *agt) + enum lttng_loglevel_type loglevel_type, + int loglevel_value, + const char *filter_expression, + struct agent *agt) { struct lttng_ht_node_str *node; struct lttng_ht_iter iter; @@ -1347,6 +1429,21 @@ int agent_app_ht_alloc(void) return ret; } +/* + * Allocate agent_apps_ht_by_sock. + */ +int trigger_agent_ht_alloc(void) +{ + int ret = 0; + + trigger_agents_ht_by_domain = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + if (!trigger_agents_ht_by_domain) { + ret = -1; + } + + return ret; +} + /* * Destroy a agent application by socket. */ @@ -1396,6 +1493,32 @@ void agent_app_ht_clean(void) lttng_ht_destroy(agent_apps_ht_by_sock); } +/* + * Clean-up the trigger agent hash table and destroy it. + */ +void trigger_agent_ht_clean(void) +{ + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + + if (!trigger_agents_ht_by_domain) { + return; + } + rcu_read_lock(); + cds_lfht_for_each_entry (trigger_agents_ht_by_domain->ht, &iter.iter, + node, node) { + struct agent *agent; + + (void) lttng_ht_del(trigger_agents_ht_by_domain, &iter); + + agent = caa_container_of(node, struct agent, node); + agent_destroy(agent); + } + rcu_read_unlock(); + + lttng_ht_destroy(trigger_agents_ht_by_domain); +} + /* * Update a agent application (given socket) using the given agent. * @@ -1447,3 +1570,27 @@ void agent_update(const struct agent *agt, const struct agent_app *app) rcu_read_unlock(); } + +struct agent *trigger_find_agent(enum lttng_domain_type domain_type) +{ + struct agent *agt = NULL; + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + uint64_t key; + + assert(trigger_agents_ht_by_domain); + + DBG3("Trigger agent lookup for domain %d", domain_type); + + key = domain_type; + + lttng_ht_lookup(trigger_agents_ht_by_domain, &key, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (!node) { + goto end; + } + agt = caa_container_of(node, struct agent, node); + +end: + return agt; +} diff --git a/src/bin/lttng-sessiond/agent.h b/src/bin/lttng-sessiond/agent.h index f8e67efda..72aa48108 100644 --- a/src/bin/lttng-sessiond/agent.h +++ b/src/bin/lttng-sessiond/agent.h @@ -24,11 +24,15 @@ */ extern struct lttng_ht *agent_apps_ht_by_sock; +/* + * Hash table that contains the trigger agents by domain */ +extern struct lttng_ht *trigger_agents_ht_by_domain; + struct agent_ht_key { const char *name; int loglevel_value; enum lttng_loglevel_type loglevel_type; - char *filter_expression; + const char *filter_expression; }; /* @@ -84,9 +88,15 @@ struct agent_event { struct lttng_ht_node_str node; /* Filter associated with the event. NULL if none. */ - struct lttng_filter_bytecode *filter; + struct lttng_bytecode *filter; char *filter_expression; struct lttng_event_exclusion *exclusion; + + /* + * Multiple triggers and events can use this agent event. + * The event can only be disabled when the count is zero. + */ + unsigned int user_refcount; }; /* @@ -128,13 +138,15 @@ void agent_add(struct agent *agt, struct lttng_ht *ht); /* Agent event API. */ struct agent_event *agent_create_event(const char *name, enum lttng_loglevel_type loglevel_type, int loglevel_value, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, char *filter_expression); void agent_add_event(struct agent_event *event, struct agent *agt); struct agent_event *agent_find_event(const char *name, - enum lttng_loglevel_type loglevel_type, int loglevel_value, - char *filter_expression, struct agent *agt); + enum lttng_loglevel_type loglevel_type, + int loglevel_value, + const char *filter_expression, + struct agent *agt); void agent_find_events_by_name(const char *name, struct agent *agt, struct lttng_ht_iter* iter); void agent_event_next_duplicate(const char *name, @@ -167,4 +179,12 @@ void agent_update(const struct agent *agt, const struct agent_app *app); int agent_list_events(struct lttng_event **events, enum lttng_domain_type domain); +struct agent_event *agent_find_event_by_trigger( + const struct lttng_trigger *trigger, struct agent *agt); + +/* todo: find a better place for this */ +struct agent *trigger_find_agent(enum lttng_domain_type domain_type); +void trigger_agent_ht_clean(void); +int trigger_agent_ht_alloc(void); + #endif /* LTTNG_SESSIOND_AGENT_H */ diff --git a/src/bin/lttng-sessiond/client.c b/src/bin/lttng-sessiond/client.c index 9f9afa25a..e9553bf1d 100644 --- a/src/bin/lttng-sessiond/client.c +++ b/src/bin/lttng-sessiond/client.c @@ -35,6 +35,7 @@ #include "utils.h" #include "manage-consumer.h" #include "clear.h" +#include "agent-thread.h" static bool is_root; @@ -538,7 +539,6 @@ static int receive_userspace_probe(struct command_ctx *cmd_ctx, int sock, { int fd, ret; struct lttng_userspace_probe_location *probe_location; - const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; struct lttng_dynamic_buffer probe_location_buffer; struct lttng_buffer_view buffer_view; @@ -598,30 +598,7 @@ static int receive_userspace_probe(struct command_ctx *cmd_ctx, int sock, * Set the file descriptor received from the client through the unix * socket in the probe location. */ - lookup = lttng_userspace_probe_location_get_lookup_method(probe_location); - if (!lookup) { - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - - /* - * From the kernel tracer's perspective, all userspace probe event types - * are all the same: a file and an offset. - */ - switch (lttng_userspace_probe_location_lookup_method_get_type(lookup)) { - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF: - ret = lttng_userspace_probe_location_function_set_binary_fd( - probe_location, fd); - break; - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: - ret = lttng_userspace_probe_location_tracepoint_set_binary_fd( - probe_location, fd); - break; - default: - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - + ret = lttng_userspace_probe_location_set_binary_fd(probe_location, fd); if (ret) { ret = LTTNG_ERR_PROBE_LOCATION_INVAL; goto error; @@ -716,6 +693,7 @@ static int process_client_msg(struct command_ctx *cmd_ctx, int *sock, int ret = LTTNG_OK; int need_tracing_session = 1; int need_domain; + int need_consumerd = 1; DBG("Processing client command %d", cmd_ctx->lsm->cmd_type); @@ -739,19 +717,29 @@ static int process_client_msg(struct command_ctx *cmd_ctx, int *sock, case LTTNG_SET_SESSION_SHM_PATH: case LTTNG_REGENERATE_METADATA: case LTTNG_REGENERATE_STATEDUMP: - case LTTNG_REGISTER_TRIGGER: - case LTTNG_UNREGISTER_TRIGGER: case LTTNG_ROTATE_SESSION: case LTTNG_ROTATION_GET_INFO: case LTTNG_ROTATION_SET_SCHEDULE: case LTTNG_SESSION_LIST_ROTATION_SCHEDULES: case LTTNG_CLEAR_SESSION: + case LTTNG_LIST_TRIGGERS: need_domain = 0; break; default: need_domain = 1; } + /* Needs a functioning consumerd */ + switch (cmd_ctx->lsm->cmd_type) { + case LTTNG_REGISTER_TRIGGER: + case LTTNG_UNREGISTER_TRIGGER: + need_consumerd = 0; + break; + default: + need_consumerd = 1; + break; + } + if (config.no_kernel && need_domain && cmd_ctx->lsm->domain.type == LTTNG_DOMAIN_KERNEL) { if (!is_root) { @@ -792,6 +780,8 @@ static int process_client_msg(struct command_ctx *cmd_ctx, int *sock, case LTTNG_DATA_PENDING: case LTTNG_ROTATE_SESSION: case LTTNG_ROTATION_GET_INFO: + case LTTNG_REGISTER_TRIGGER: + case LTTNG_LIST_TRIGGERS: break; default: /* Setup lttng message with no payload */ @@ -812,6 +802,7 @@ static int process_client_msg(struct command_ctx *cmd_ctx, int *sock, case LTTNG_SAVE_SESSION: case LTTNG_REGISTER_TRIGGER: case LTTNG_UNREGISTER_TRIGGER: + case LTTNG_LIST_TRIGGERS: need_tracing_session = 0; break; default: @@ -890,7 +881,8 @@ static int process_client_msg(struct command_ctx *cmd_ctx, int *sock, } /* Consumer is in an ERROR state. Report back to client */ - if (uatomic_read(&kernel_consumerd_state) == CONSUMER_ERROR) { + if (need_consumerd && uatomic_read(&kernel_consumerd_state) == + CONSUMER_ERROR) { ret = LTTNG_ERR_NO_KERNCONSUMERD; goto error; } @@ -935,14 +927,21 @@ static int process_client_msg(struct command_ctx *cmd_ctx, int *sock, case LTTNG_DOMAIN_JUL: case LTTNG_DOMAIN_LOG4J: case LTTNG_DOMAIN_PYTHON: + if (!agent_tracing_is_enabled()) { + ret = LTTNG_ERR_AGENT_TRACING_DISABLED; + goto error; + } + /* Fallthrough */ case LTTNG_DOMAIN_UST: { if (!ust_app_supported()) { ret = LTTNG_ERR_NO_UST; goto error; } + /* Consumer is in an ERROR state. Report back to client */ - if (uatomic_read(&ust_consumerd_state) == CONSUMER_ERROR) { + if (need_consumerd && uatomic_read(&ust_consumerd_state) == + CONSUMER_ERROR) { ret = LTTNG_ERR_NO_USTCONSUMERD; goto error; } @@ -1392,7 +1391,7 @@ error_add_context: { struct lttng_event *ev = NULL; struct lttng_event_exclusion *exclusion = NULL; - struct lttng_filter_bytecode *bytecode = NULL; + struct lttng_bytecode *bytecode = NULL; char *filter_expression = NULL; /* Handle exclusion events and receive it from the client. */ @@ -1969,8 +1968,33 @@ error_add_context: } case LTTNG_REGISTER_TRIGGER: { + struct lttng_dynamic_buffer payload; + struct lttng_trigger *return_trigger; + + lttng_dynamic_buffer_init(&payload); ret = cmd_register_trigger(cmd_ctx, *sock, - notification_thread_handle); + notification_thread_handle, &return_trigger); + if (ret != LTTNG_OK) { + goto error; + } + + ret = lttng_trigger_serialize(return_trigger, &payload, NULL); + if (ret) { + ERR("Failed to serialize trigger in reply to \"register trigger\" command"); + ret = LTTNG_ERR_NOMEM; + lttng_trigger_destroy(return_trigger); + goto error; + } + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, payload.data, + payload.size); + if (ret) { + lttng_trigger_destroy(return_trigger); + ret = LTTNG_ERR_NOMEM; + goto error; + } + lttng_trigger_destroy(return_trigger); + lttng_dynamic_buffer_reset(&payload); + ret = LTTNG_OK; break; } case LTTNG_UNREGISTER_TRIGGER: @@ -2083,6 +2107,37 @@ error_add_context: ret = cmd_clear_session(cmd_ctx->session, sock); break; } + case LTTNG_LIST_TRIGGERS: + { + struct lttng_dynamic_buffer payload; + struct lttng_triggers *return_triggers; + + lttng_dynamic_buffer_init(&payload); + ret = cmd_list_triggers(cmd_ctx, *sock, + notification_thread_handle, &return_triggers); + if (ret != LTTNG_OK) { + goto error; + } + + ret = lttng_triggers_serialize(return_triggers, &payload); + if (ret) { + ERR("Failed to serialize triggers in reply to \"list triggers\" command"); + ret = LTTNG_ERR_NOMEM; + lttng_triggers_destroy(return_triggers); + goto error; + } + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, payload.data, + payload.size); + if (ret) { + ret = LTTNG_ERR_NOMEM; + lttng_triggers_destroy(return_triggers); + goto error; + } + lttng_dynamic_buffer_reset(&payload); + lttng_triggers_destroy(return_triggers); + ret = LTTNG_OK; + break; + } default: ret = LTTNG_ERR_UND; break; diff --git a/src/bin/lttng-sessiond/cmd.c b/src/bin/lttng-sessiond/cmd.c index e45d4423a..91bdaf62b 100644 --- a/src/bin/lttng-sessiond/cmd.c +++ b/src/bin/lttng-sessiond/cmd.c @@ -30,6 +30,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -108,7 +115,7 @@ static int cmd_enable_event_internal(struct ltt_session *session, const struct lttng_domain *domain, char *channel_name, struct lttng_event *event, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, int wpipe); @@ -2165,7 +2172,7 @@ static int _cmd_enable_event(struct ltt_session *session, const struct lttng_domain *domain, char *channel_name, struct lttng_event *event, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, int wpipe, bool internal_event) { @@ -2248,7 +2255,7 @@ static int _cmd_enable_event(struct ltt_session *session, case LTTNG_EVENT_ALL: { char *filter_expression_a = NULL; - struct lttng_filter_bytecode *filter_a = NULL; + struct lttng_bytecode *filter_a = NULL; /* * We need to duplicate filter_expression and filter, @@ -2483,11 +2490,11 @@ static int _cmd_enable_event(struct ltt_session *session, { char *filter_expression_copy = NULL; - struct lttng_filter_bytecode *filter_copy = NULL; + struct lttng_bytecode *filter_copy = NULL; if (filter) { const size_t filter_size = sizeof( - struct lttng_filter_bytecode) + struct lttng_bytecode) + filter->len; filter_copy = zmalloc(filter_size); @@ -2563,7 +2570,7 @@ int cmd_enable_event(struct ltt_session *session, const struct lttng_domain *domain, char *channel_name, struct lttng_event *event, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, int wpipe) { @@ -2580,7 +2587,7 @@ static int cmd_enable_event_internal(struct ltt_session *session, const struct lttng_domain *domain, char *channel_name, struct lttng_event *event, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, int wpipe) { @@ -4343,8 +4350,100 @@ end: return ret; } +/* TODO: is this the best place to perform this? (code wise) */ +/* + * Set sock to -1 if reception of more information is not necessary e.g on + * unregister. TODO find a better way. + * + * On success LTTNG_OK. On error, returns lttng_error code. + * */ +static enum lttng_error_code prepare_trigger_object(struct lttng_trigger *trigger, int sock) +{ + enum lttng_error_code ret; + /* Internal object of the trigger might have to "generate" and + * "populate" internal field e.g filter bytecode + */ + struct lttng_condition *condition = NULL; + + condition = lttng_trigger_get_condition(trigger); + if (!condition) { + ret = LTTNG_ERR_INVALID_TRIGGER; + goto end; + } + + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + { + struct lttng_event_rule *event_rule; + const struct lttng_credentials *credential = lttng_trigger_get_credentials(trigger); + lttng_condition_event_rule_get_rule_no_const( + condition, &event_rule); + ret = lttng_event_rule_populate(event_rule, credential->uid, credential->gid); + if (ret != LTTNG_OK) { + goto end; + } + + switch (lttng_event_rule_get_type(event_rule)) { + case LTTNG_EVENT_RULE_TYPE_UPROBE: + { + int fd; + struct lttng_userspace_probe_location *location = lttng_event_rule_uprobe_get_location_no_const(event_rule); + + if (sock < 0) { + /* Nothing to receive */ + break; + } + /* + * Receive the file descriptor to the target binary from + * the client. + */ + DBG("Receiving userspace probe target FD from client ..."); + ret = lttcomm_recv_fds_unix_sock(sock, &fd, 1); + if (ret <= 0) { + DBG("Nothing recv() from client userspace probe fd... continuing"); + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto end; + } + + /* + * Set the file descriptor received from the client + * through the unix socket in the probe location. + */ + ret = lttng_userspace_probe_location_set_binary_fd( + location, fd); + if (ret) { + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto end; + } + + break; + } + default: + /* Nothing to do */ + break; + } + + /* Generate the capture bytecode set */ + ret = lttng_condition_event_rule_generate_capture_descriptor_bytecode_set( + condition, &trigger->capture_bytecode_set); + break; + } + default: + { + ret = LTTNG_OK; + break; + } + } + + +end: + return ret; +} + +/* Caller must call lttng_destroy_trigger on the returned trigger object */ int cmd_register_trigger(struct command_ctx *cmd_ctx, int sock, - struct notification_thread_handle *notification_thread) + struct notification_thread_handle *notification_thread, + struct lttng_trigger **return_trigger) { int ret; size_t trigger_len; @@ -4378,9 +4477,95 @@ int cmd_register_trigger(struct command_ctx *cmd_ctx, int sock, goto end; } + /* + * Since we return the trigger object, take a reference to it + * Caller is responsible for calling lttng_destroy_trigger on it. + * This thread does not OWN the trigger. + */ + lttng_trigger_get(trigger); + + /* Set the trigger credential */ + lttng_trigger_set_credentials(trigger, cmd_ctx->creds.uid, cmd_ctx->creds.gid); + + /* Prepare internal trigger object if needed on reception. + * Handles also special treatment for certain internal object of the + * trigger (e.g uprobe event rule binary fd. + */ + ret = prepare_trigger_object(trigger, sock); + if (ret != LTTNG_OK) { + goto end; + } + + /* Inform the notification thread */ ret = notification_thread_command_register_trigger(notification_thread, trigger); - /* Ownership of trigger was transferred. */ + if (ret != LTTNG_OK) { + goto end; + } + + /* Synchronize tracers, only if needed */ + /* TODO: maybe extract somewhere else */ + { + struct lttng_condition *condition = NULL; + condition = lttng_trigger_get_condition(trigger); + if (!condition) { + ret = LTTNG_ERR_INVALID_TRIGGER; + goto end; + } + + if (lttng_condition_get_type(condition) == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT) { + const struct lttng_event_rule *rule = NULL; + (void) lttng_condition_event_rule_get_rule(condition, &rule); + if (!rule) { + ret = LTTNG_ERR_INVALID_TRIGGER; + goto end; + } + if (lttng_event_rule_get_domain_type(rule) == LTTNG_DOMAIN_KERNEL) { + /* TODO: get the token value from the + * notification thread and only perform an + * enable and a disable.... This is NOT + * OPTIMIZED AT ALL + */ + kernel_update_tokens(); + } else { + /* TODO: get the token value from the + * notification thread and only perform an + * enable and a disable.... This is NOT + * OPTIMIZED AT ALL + */ + ust_app_global_update_all_tokens(); + /* Agent handling */ + if (lttng_event_rule_is_agent(rule)) { + struct agent *agt; + const char *pattern; + enum lttng_domain_type domain_type; + domain_type = lttng_event_rule_get_domain_type( + rule); + (void) lttng_event_rule_tracepoint_get_pattern( + rule, &pattern); + agt = trigger_find_agent(domain_type); + if (!agt) { + agt = agent_create(domain_type); + if (!agt) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + agent_add(agt, trigger_agents_ht_by_domain); + } + + ret = trigger_agent_enable( + trigger, agt); + if (ret != LTTNG_OK) { + goto end; + } + } + } + } + } + + /* Return an image of the updated object to the client */ + *return_trigger = trigger; + /* Ownership of trigger was transferred to caller. */ trigger = NULL; end: lttng_trigger_destroy(trigger); @@ -4423,14 +4608,101 @@ int cmd_unregister_trigger(struct command_ctx *cmd_ctx, int sock, goto end; } + lttng_trigger_set_credentials(trigger, cmd_ctx->creds.uid, cmd_ctx->creds.gid); + + ret = prepare_trigger_object(trigger, -1); + if (ret != LTTNG_OK) { + goto end; + } + ret = notification_thread_command_unregister_trigger(notification_thread, trigger); + + /* Synchronize tracers, only if needed */ + /* TODO: maybe extract somewhere else */ + { + struct lttng_condition *condition = NULL; + condition = lttng_trigger_get_condition(trigger); + if (!condition) { + ret = LTTNG_ERR_INVALID_TRIGGER; + goto end; + } + + if (lttng_condition_get_type(condition) == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT) { + const struct lttng_event_rule *rule = NULL; + (void) lttng_condition_event_rule_get_rule(condition, &rule); + if (!rule) { + ret = LTTNG_ERR_INVALID_TRIGGER; + goto end; + } + if (lttng_event_rule_get_domain_type(rule) == LTTNG_DOMAIN_KERNEL) { + /* TODO: get the token value from the + * notification thread and only perform an + * enable and a disable.... This is NOT + * OPTIMIZED AT ALL + */ + kernel_update_tokens(); + } else { + /* TODO: get the token value from the + * notification thread and only perform an + * enable and a disable.... This is NOT + * OPTIMIZED AT ALL + */ + ust_app_global_update_all_tokens(); + if (lttng_event_rule_is_agent(rule)) { + struct agent *agt; + const char *pattern; + enum lttng_domain_type domain_type; + + domain_type = lttng_event_rule_get_domain_type( + rule); + (void) lttng_event_rule_tracepoint_get_pattern( + rule, &pattern); + + agt = trigger_find_agent(domain_type); + if (!agt) { + ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; + goto end; + } + ret = trigger_agent_disable( + trigger, agt); + if (ret != LTTNG_OK) { + goto end; + } + } + } + } + } + end: lttng_trigger_destroy(trigger); lttng_dynamic_buffer_reset(&trigger_buffer); return ret; } +int cmd_list_triggers(struct command_ctx *cmd_ctx, int sock, + struct notification_thread_handle *notification_thread, + struct lttng_triggers **return_triggers) +{ + int ret = 0; + enum lttng_error_code ret_code; + struct lttng_triggers *triggers = NULL; + + /* Get list of token trigger from the notification thread here */ + ret_code = notification_thread_command_list_triggers(notification_thread_handle, cmd_ctx->creds.uid, cmd_ctx->creds.gid, &triggers); + if (ret_code != LTTNG_OK) { + ret = ret_code; + goto end; + } + + /* Return a "view" of the current triggers */ + *return_triggers = triggers; + triggers = NULL; + ret = LTTNG_OK; +end: + lttng_triggers_destroy(triggers); + return ret; +} /* * Send relayd sockets from snapshot output to consumer. Ignore request if the * snapshot output is *not* set with a remote destination. diff --git a/src/bin/lttng-sessiond/cmd.h b/src/bin/lttng-sessiond/cmd.h index 1be746031..089c8f906 100644 --- a/src/bin/lttng-sessiond/cmd.h +++ b/src/bin/lttng-sessiond/cmd.h @@ -86,11 +86,11 @@ int cmd_add_context(struct ltt_session *session, enum lttng_domain_type domain, char *channel_name, const struct lttng_event_context *ctx, int kwpipe); int cmd_set_filter(struct ltt_session *session, enum lttng_domain_type domain, char *channel_name, struct lttng_event *event, - struct lttng_filter_bytecode *bytecode); + struct lttng_bytecode *bytecode); int cmd_enable_event(struct ltt_session *session, const struct lttng_domain *domain, char *channel_name, struct lttng_event *event, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, int wpipe); @@ -142,10 +142,15 @@ int cmd_regenerate_metadata(struct ltt_session *session); int cmd_regenerate_statedump(struct ltt_session *session); int cmd_register_trigger(struct command_ctx *cmd_ctx, int sock, - struct notification_thread_handle *notification_thread_handle); + struct notification_thread_handle *notification_thread_handle, + struct lttng_trigger **return_trigger); int cmd_unregister_trigger(struct command_ctx *cmd_ctx, int sock, struct notification_thread_handle *notification_thread_handle); +int cmd_list_triggers(struct command_ctx *cmd_ctx, int sock, + struct notification_thread_handle *notification_thread_handle, + struct lttng_triggers **return_triggers); + int cmd_rotate_session(struct ltt_session *session, struct lttng_rotate_session_return *rotate_return, bool quiet_rotation, diff --git a/src/bin/lttng-sessiond/dispatch.c b/src/bin/lttng-sessiond/dispatch.c index 4fe3dfce7..461c56d64 100644 --- a/src/bin/lttng-sessiond/dispatch.c +++ b/src/bin/lttng-sessiond/dispatch.c @@ -36,6 +36,7 @@ static void update_ust_app(int app_sock) { struct ltt_session *sess, *stmp; const struct ltt_session_list *session_list = session_get_list(); + struct ust_app *app; /* Consumer is in an ERROR state. Stop any application update. */ if (uatomic_read(&ust_consumerd_state) == CONSUMER_ERROR) { @@ -43,10 +44,25 @@ static void update_ust_app(int app_sock) return; } + rcu_read_lock(); + assert(app_sock >= 0); + app = ust_app_find_by_sock(app_sock); + if (app == NULL) { + /* + * Application can be unregistered before so + * this is possible hence simply stopping the + * update. + */ + DBG3("UST app update failed to find app sock %d", + app_sock); + goto unlock_rcu; + } + + /* Update all tokens for the app */ + ust_app_global_update_tokens(app); + /* For all tracing session(s) */ cds_list_for_each_entry_safe(sess, stmp, &session_list->head, list) { - struct ust_app *app; - if (!session_get(sess)) { continue; } @@ -55,26 +71,15 @@ static void update_ust_app(int app_sock) goto unlock_session; } - rcu_read_lock(); - assert(app_sock >= 0); - app = ust_app_find_by_sock(app_sock); - if (app == NULL) { - /* - * Application can be unregistered before so - * this is possible hence simply stopping the - * update. - */ - DBG3("UST app update failed to find app sock %d", - app_sock); - goto unlock_rcu; - } ust_app_global_update(sess->ust_session, app); - unlock_rcu: - rcu_read_unlock(); unlock_session: session_unlock(sess); session_put(sess); } + +unlock_rcu: + rcu_read_unlock(); + } /* @@ -386,6 +391,8 @@ static void *thread_dispatch_ust_registration(void *data) /* Set app version. This call will print an error if needed. */ (void) ust_app_version(app); + (void) ust_app_setup_trigger_group(app); + /* Send notify socket through the notify pipe. */ ret = send_socket_to_thread( notifiers->apps_cmd_notify_pipe_write_fd, diff --git a/src/bin/lttng-sessiond/event.c b/src/bin/lttng-sessiond/event.c index 189236beb..598d448b5 100644 --- a/src/bin/lttng-sessiond/event.c +++ b/src/bin/lttng-sessiond/event.c @@ -12,6 +12,11 @@ #include #include +#include +#include +#include +#include +#include #include #include #include @@ -27,6 +32,7 @@ #include "trace-kernel.h" #include "trace-ust.h" #include "agent.h" +#include "utils.h" /* * Add unique UST event based on the event name, filter bytecode and loglevel. @@ -42,7 +48,7 @@ static void add_unique_ust_event(struct lttng_ht *ht, assert(event); key.name = event->attr.name; - key.filter = (struct lttng_filter_bytecode *) event->filter; + key.filter = (struct lttng_bytecode *) event->filter; key.loglevel_type = event->attr.loglevel_type; key.loglevel_value = event->attr.loglevel; key.exclusion = event->exclusion; @@ -99,7 +105,7 @@ int event_kernel_disable_event(struct ltt_kernel_channel *kchan, */ int event_kernel_enable_event(struct ltt_kernel_channel *kchan, struct lttng_event *event, char *filter_expression, - struct lttng_filter_bytecode *filter) + struct lttng_bytecode *filter) { int ret; struct ltt_kernel_event *kevent; @@ -149,7 +155,7 @@ end: int event_ust_enable_tracepoint(struct ltt_ust_session *usess, struct ltt_ust_channel *uchan, struct lttng_event *event, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, bool internal_event) { @@ -366,6 +372,20 @@ error: return ret; } +static void agent_enable_all(struct agent *agt) +{ + struct agent_event *aevent; + struct lttng_ht_iter iter; + + /* Flag every event that they are now enabled. */ + rcu_read_lock(); + cds_lfht_for_each_entry ( + agt->events->ht, &iter.iter, aevent, node.node) { + aevent->enabled = 1; + } + rcu_read_unlock(); +} + /* * Enable all agent event for a given UST session. * @@ -373,11 +393,9 @@ error: */ int event_agent_enable_all(struct ltt_ust_session *usess, struct agent *agt, struct lttng_event *event, - struct lttng_filter_bytecode *filter ,char *filter_expression) + struct lttng_bytecode *filter ,char *filter_expression) { int ret; - struct agent_event *aevent; - struct lttng_ht_iter iter; assert(usess); @@ -389,13 +407,7 @@ int event_agent_enable_all(struct ltt_ust_session *usess, goto error; } - /* Flag every event that they are now enabled. */ - rcu_read_lock(); - cds_lfht_for_each_entry(agt->events->ht, &iter.iter, aevent, - node.node) { - aevent->enabled = 1; - } - rcu_read_unlock(); + agent_enable_all(agt); ret = LTTNG_OK; @@ -410,7 +422,7 @@ error: * contexts yet. Not an issue for now, since they are not generated by * the lttng-ctl library. */ -static int add_filter_app_ctx(struct lttng_filter_bytecode *bytecode, +static int add_filter_app_ctx(struct lttng_bytecode *bytecode, const char *filter_expression, struct agent *agt) { int ret = LTTNG_OK; @@ -467,28 +479,17 @@ end: return ret; } -/* - * Enable a single agent event for a given UST session. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -int event_agent_enable(struct ltt_ust_session *usess, - struct agent *agt, struct lttng_event *event, - struct lttng_filter_bytecode *filter, +static int agent_enable(struct agent *agt, + struct lttng_event *event, + struct lttng_bytecode *filter, char *filter_expression) { int ret, created = 0; struct agent_event *aevent; - assert(usess); assert(event); assert(agt); - DBG("Event agent enabling %s for session %" PRIu64 " with loglevel type %d " - ", loglevel %d and filter \"%s\"", event->name, - usess->id, event->loglevel_type, event->loglevel, - filter_expression ? filter_expression : "NULL"); - aevent = agent_find_event(event->name, event->loglevel_type, event->loglevel, filter_expression, agt); if (!aevent) { @@ -542,6 +543,112 @@ end: return ret; } +/* + * Enable a single agent event for a given UST session. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int event_agent_enable(struct ltt_ust_session *usess, + struct agent *agt, + struct lttng_event *event, + struct lttng_bytecode *filter, + char *filter_expression) +{ + assert(usess); + assert(event); + assert(agt); + + DBG("Event agent enabling %s for session %" PRIu64 + " with loglevel type %d " + ", loglevel %d and filter \"%s\"", + event->name, usess->id, event->loglevel_type, + event->loglevel, + filter_expression ? filter_expression : "NULL"); + + return agent_enable(agt, event, filter, filter_expression); +} + +/* + * Enable a single agent event for a trigger. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int trigger_agent_enable(const struct lttng_trigger *trigger, struct agent *agt) +{ + int ret; + enum lttng_condition_status c_status; + enum lttng_domain_type d_type; + const struct lttng_condition *condition; + const struct lttng_event_rule *rule; + const char *filter_expression; + char *filter_expression_copy = NULL; + const struct lttng_bytecode *filter_bytecode; + struct lttng_bytecode *filter_bytecode_copy = NULL; + struct lttng_event *event = NULL; + + assert(trigger); + assert(agt); + + condition = lttng_trigger_get_const_condition(trigger); + + assert(lttng_condition_get_type(condition) == + LTTNG_CONDITION_TYPE_EVENT_RULE_HIT); + + c_status = lttng_condition_event_rule_get_rule(condition, &rule); + assert(c_status == LTTNG_CONDITION_STATUS_OK); + + assert(lttng_event_rule_get_type(rule) == + LTTNG_EVENT_RULE_TYPE_TRACEPOINT); + + d_type = lttng_event_rule_get_domain_type(rule); + assert(d_type == agt->domain); + + event = lttng_event_rule_generate_lttng_event(rule); + if (!event) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + /* Get the internal filter_expression and bytecode */ + filter_expression = lttng_event_rule_get_filter(rule); + if (filter_expression) { + filter_expression_copy = strdup(filter_expression); + if (!filter_expression_copy) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + /* Get the filter bytecode */ + filter_bytecode = lttng_event_rule_get_filter_bytecode(rule); + if (filter_bytecode) { + filter_bytecode_copy = bytecode_copy (filter_bytecode); + if (!filter_bytecode_copy) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + } + } + + DBG("Event agent enabling %s for trigger %" PRIu64 + " with loglevel type %d " + ", loglevel %d and filter \"%s\"", + event->name, lttng_trigger_get_key(trigger), + event->loglevel_type, event->loglevel, + filter_expression ? filter_expression : "NULL"); + + ret = agent_enable(agt, event, filter_bytecode_copy, + filter_expression_copy); + /* Ownership was passed even in case of error */ + filter_expression_copy = NULL; + filter_bytecode_copy = NULL; + +end: + free(filter_expression_copy); + free(filter_bytecode_copy); + free(event); + return ret; +} + /* * Return the default event name associated with the provided UST domain. Return * NULL on error. @@ -567,6 +674,43 @@ const char *event_get_default_agent_ust_name(enum lttng_domain_type domain) return default_event_name; } +static int trigger_agent_disable_one(const struct lttng_trigger *trigger, + struct agent *agt, + struct agent_event *aevent) + +{ + int ret; + + assert(agt); + assert(trigger); + assert(aevent); + + /* + * Actual ust event un-registration happens on the trigger + * un-registration at that point. + */ + + DBG("Event agent disabling %s (loglevel type %d, loglevel value %d) for trigger %" PRIu64, + aevent->name, aevent->loglevel_type, + aevent->loglevel_value, lttng_trigger_get_key(trigger)); + + /* Already disabled? */ + if (!aevent->enabled) { + goto end; + } + + ret = agent_disable_event(aevent, agt->domain); + if (ret != LTTNG_OK) { + goto error; + } + +end: + return LTTNG_OK; + +error: + return ret; +} + /* * Disable a given agent event for a given UST session. * @@ -659,6 +803,44 @@ error: return ret; } +/* + * Disable agent event matching a given trigger. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int trigger_agent_disable( + const struct lttng_trigger *trigger, struct agent *agt) +{ + int ret = LTTNG_OK; + struct agent_event *aevent; + + assert(trigger); + assert(agt); + + DBG("Event agent disabling for trigger %" PRIu64, + lttng_trigger_get_key(trigger)); + + rcu_read_lock(); + aevent = agent_find_event_by_trigger(trigger, agt); + + if (aevent == NULL) { + DBG2("Event agent NOT found by trigger %" PRIu64, + lttng_trigger_get_key(trigger)); + ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; + goto end; + } + + ret = trigger_agent_disable_one(trigger, agt, aevent); + + if (ret != LTTNG_OK) { + goto end; + } + +end: + rcu_read_unlock(); + return ret; +} + /* * Disable all agent events matching a given name for a given UST session. * diff --git a/src/bin/lttng-sessiond/event.h b/src/bin/lttng-sessiond/event.h index 1c646db37..8849ee7cc 100644 --- a/src/bin/lttng-sessiond/event.h +++ b/src/bin/lttng-sessiond/event.h @@ -17,12 +17,12 @@ int event_kernel_disable_event(struct ltt_kernel_channel *kchan, int event_kernel_enable_event(struct ltt_kernel_channel *kchan, struct lttng_event *event, char *filter_expression, - struct lttng_filter_bytecode *filter); + struct lttng_bytecode *filter); int event_ust_enable_tracepoint(struct ltt_ust_session *usess, struct ltt_ust_channel *uchan, struct lttng_event *event, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, bool internal_event); int event_ust_disable_tracepoint(struct ltt_ust_session *usess, @@ -32,16 +32,21 @@ int event_ust_disable_all_tracepoints(struct ltt_ust_session *usess, struct ltt_ust_channel *uchan); int event_agent_enable(struct ltt_ust_session *usess, struct agent *agt, - struct lttng_event *event, struct lttng_filter_bytecode *filter, + struct lttng_event *event, struct lttng_bytecode *filter, char *filter_expression); int event_agent_enable_all(struct ltt_ust_session *usess, struct agent *agt, - struct lttng_event *event, struct lttng_filter_bytecode *filter, + struct lttng_event *event, struct lttng_bytecode *filter, char *filter_expression); int event_agent_disable(struct ltt_ust_session *usess, struct agent *agt, const char *event_name); int event_agent_disable_all(struct ltt_ust_session *usess, struct agent *agt); +int trigger_agent_enable( + const struct lttng_trigger *trigger, struct agent *agt); +int trigger_agent_disable( + const struct lttng_trigger *trigger, struct agent *agt); + const char *event_get_default_agent_ust_name(enum lttng_domain_type domain); #endif /* _LTT_EVENT_H */ diff --git a/src/bin/lttng-sessiond/globals.c b/src/bin/lttng-sessiond/globals.c index 20aa790a7..8681d5d90 100644 --- a/src/bin/lttng-sessiond/globals.c +++ b/src/bin/lttng-sessiond/globals.c @@ -20,8 +20,10 @@ long page_size; struct health_app *health_sessiond; struct notification_thread_handle *notification_thread_handle; +pthread_mutex_t notification_trigger_tokens_ht_lock = PTHREAD_MUTEX_INITIALIZER; struct lttng_ht *agent_apps_ht_by_sock = NULL; +struct lttng_ht *trigger_agents_ht_by_domain = NULL; struct lttng_kernel_tracer_version kernel_tracer_version; struct lttng_kernel_tracer_abi_version kernel_tracer_abi_version; diff --git a/src/bin/lttng-sessiond/health-sessiond.h b/src/bin/lttng-sessiond/health-sessiond.h index 7c9dbd0b3..b541822f8 100644 --- a/src/bin/lttng-sessiond/health-sessiond.h +++ b/src/bin/lttng-sessiond/health-sessiond.h @@ -23,6 +23,7 @@ enum health_type_sessiond { HEALTH_SESSIOND_TYPE_NOTIFICATION = 8, HEALTH_SESSIOND_TYPE_ROTATION = 9, HEALTH_SESSIOND_TYPE_TIMER = 10, + HEALTH_SESSIOND_TYPE_ACTION_EXECUTOR = 11, NR_HEALTH_SESSIOND_TYPES, }; diff --git a/src/bin/lttng-sessiond/kernel.c b/src/bin/lttng-sessiond/kernel.c index a117575b7..fa682b910 100644 --- a/src/bin/lttng-sessiond/kernel.c +++ b/src/bin/lttng-sessiond/kernel.c @@ -21,11 +21,20 @@ #include #include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include + #include "lttng-sessiond.h" #include "lttng-syscall.h" #include "consumer.h" @@ -35,6 +44,7 @@ #include "utils.h" #include "rotate.h" #include "modprobe.h" +#include "notification-thread-commands.h" /* * Key used to reference a channel between the sessiond and the consumer. This @@ -45,9 +55,10 @@ static uint64_t next_kernel_channel_key; static const char *module_proc_lttng = "/proc/lttng"; static int kernel_tracer_fd = -1; +static int kernel_tracer_trigger_group_fd = -1; +static int kernel_tracer_trigger_group_notification_fd = -1; +static struct ltt_kernel_token_event_rule_list kernel_tracer_token_list; -#include -#include /* * Add context on a kernel channel. * @@ -219,6 +230,44 @@ error: return -1; } +/* + * Create a kernel channel, register it to the kernel tracer and add it to the + * kernel session. + */ +static +int kernel_create_trigger_group(int *trigger_group_fd) +{ + int ret; + int local_fd = -1; + + assert(trigger_group_fd); + + /* Kernel tracer channel creation */ + ret = kernctl_create_trigger_group(kernel_tracer_fd); + if (ret < 0) { + PERROR("ioctl kernel create trigger group"); + ret = -1; + goto error; + } + + /* Store locally */ + local_fd = ret; + + /* Prevent fd duplication after execlp() */ + ret = fcntl(local_fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("fcntl session fd"); + } + + DBG("Kernel trigger group created (fd: %d)", + local_fd); + ret = 0; + +error: + *trigger_group_fd = local_fd; + return ret; +} + /* * Compute the offset of the instrumentation byte in the binary based on the * function probe location using the ELF lookup method. @@ -230,7 +279,7 @@ error: static int extract_userspace_probe_offset_function_elf( const struct lttng_userspace_probe_location *probe_location, - struct ltt_kernel_session *session, uint64_t *offset) + uid_t uid, gid_t gid, uint64_t *offset) { int fd; int ret = 0; @@ -267,8 +316,7 @@ int extract_userspace_probe_offset_function_elf( goto end; } - ret = run_as_extract_elf_symbol_offset(fd, symbol, session->uid, - session->gid, offset); + ret = run_as_extract_elf_symbol_offset(fd, symbol, uid, gid, offset); if (ret < 0) { DBG("userspace probe offset calculation failed for " "function %s", symbol); @@ -292,7 +340,7 @@ end: static int extract_userspace_probe_offset_tracepoint_sdt( const struct lttng_userspace_probe_location *probe_location, - struct ltt_kernel_session *session, uint64_t **offsets, + uid_t uid, gid_t gid, uint64_t **offsets, uint32_t *offsets_count) { enum lttng_userspace_probe_location_lookup_method_type lookup_method_type; @@ -338,7 +386,7 @@ int extract_userspace_probe_offset_tracepoint_sdt( } ret = run_as_extract_sdt_probe_offsets(fd, provider_name, probe_name, - session->uid, session->gid, offsets, offsets_count); + uid, gid, offsets, offsets_count); if (ret < 0) { DBG("userspace probe offset calculation failed for sdt " "probe %s:%s", provider_name, probe_name); @@ -359,29 +407,16 @@ end: return ret; } -/* - * Extract the offsets of the instrumentation point for the different lookup - * methods. - */ static -int userspace_probe_add_callsites(struct lttng_event *ev, - struct ltt_kernel_session *session, int fd) +int userspace_probe_add_callsite( + const struct lttng_userspace_probe_location *location, + uid_t uid, gid_t gid, int fd) { const struct lttng_userspace_probe_location_lookup_method *lookup_method = NULL; enum lttng_userspace_probe_location_lookup_method_type type; - const struct lttng_userspace_probe_location *location = NULL; int ret; - assert(ev); - assert(ev->type == LTTNG_EVENT_USERSPACE_PROBE); - - location = lttng_event_get_userspace_probe_location(ev); - if (!location) { - ret = -1; - goto end; - } - lookup_method = - lttng_userspace_probe_location_get_lookup_method(location); + lookup_method = lttng_userspace_probe_location_get_lookup_method(location); if (!lookup_method) { ret = -1; goto end; @@ -394,7 +429,8 @@ int userspace_probe_add_callsites(struct lttng_event *ev, struct lttng_kernel_event_callsite callsite; uint64_t offset; - ret = extract_userspace_probe_offset_function_elf(location, session, &offset); + ret = extract_userspace_probe_offset_function_elf(location, + uid, gid, &offset); if (ret) { ret = LTTNG_ERR_PROBE_LOCATION_INVAL; goto end; @@ -403,8 +439,7 @@ int userspace_probe_add_callsites(struct lttng_event *ev, callsite.u.uprobe.offset = offset; ret = kernctl_add_callsite(fd, &callsite); if (ret) { - WARN("Adding callsite to userspace probe " - "event %s failed.", ev->name); + WARN("Adding callsite to ELF userspace probe failed."); ret = LTTNG_ERR_KERN_ENABLE_FAIL; goto end; } @@ -421,8 +456,8 @@ int userspace_probe_add_callsites(struct lttng_event *ev, * This call allocates the offsets buffer. This buffer must be freed * by the caller */ - ret = extract_userspace_probe_offset_tracepoint_sdt(location, session, - &offsets, &offsets_count); + ret = extract_userspace_probe_offset_tracepoint_sdt(location, + uid, gid, &offsets, &offsets_count); if (ret) { ret = LTTNG_ERR_PROBE_LOCATION_INVAL; goto end; @@ -431,8 +466,8 @@ int userspace_probe_add_callsites(struct lttng_event *ev, callsite.u.uprobe.offset = offsets[i]; ret = kernctl_add_callsite(fd, &callsite); if (ret) { - WARN("Adding callsite to userspace probe " - "event %s failed.", ev->name); + WARN("Adding callsite to SDT userspace probe " + "failed."); ret = LTTNG_ERR_KERN_ENABLE_FAIL; free(offsets); goto end; @@ -449,6 +484,71 @@ end: return ret; } +/* + * Extract the offsets of the instrumentation point for the different lookup + * methods. + */ +static +int userspace_probe_event_add_callsites(struct lttng_event *ev, + struct ltt_kernel_session *session, int fd) +{ + const struct lttng_userspace_probe_location *location = NULL; + int ret; + + assert(ev); + assert(ev->type == LTTNG_EVENT_USERSPACE_PROBE); + + location = lttng_event_get_userspace_probe_location(ev); + if (!location) { + ret = -1; + goto end; + } + + ret = userspace_probe_add_callsite(location, session->uid, session->gid, + fd); + if (ret) { + WARN("Adding callsite to userspace probe event \"%s\" " + "failed.", ev->name); + } + +end: + return ret; +} + +/* + * Extract the offsets of the instrumentation point for the different lookup + * methods. + */ +static int userspace_probe_event_rule_add_callsites( + const struct lttng_event_rule *rule, + const struct lttng_credentials *creds, + int fd) +{ + const struct lttng_userspace_probe_location *location = NULL; + enum lttng_event_rule_status status; + int ret; + + assert(rule); + assert(creds); + assert(lttng_event_rule_get_type(rule) == LTTNG_EVENT_RULE_TYPE_UPROBE); + + status = lttng_event_rule_uprobe_get_location(rule, &location); + if (status != LTTNG_EVENT_RULE_STATUS_OK || !location) { + ret = -1; + goto end; + } + + ret = userspace_probe_add_callsite(location, creds->uid, creds->gid, + fd); + if (ret) { + WARN("Adding callsite to userspace probe object %d" + "failed.", fd); + } + +end: + return ret; +} + /* * Create a kernel event, enable it to the kernel tracer and add it to the * channel event list of the kernel session. @@ -457,7 +557,7 @@ end: int kernel_create_event(struct lttng_event *ev, struct ltt_kernel_channel *channel, char *filter_expression, - struct lttng_filter_bytecode *filter) + struct lttng_bytecode *filter) { int err, fd; enum lttng_error_code ret; @@ -518,7 +618,8 @@ int kernel_create_event(struct lttng_event *ev, } if (ev->type == LTTNG_EVENT_USERSPACE_PROBE) { - ret = userspace_probe_add_callsites(ev, channel->session, event->fd); + ret = userspace_probe_event_add_callsites(ev, channel->session, + event->fd); if (ret) { goto add_callsite_error; } @@ -675,6 +776,38 @@ error: return ret; } +/* + * Disable a kernel trigger. + */ +static +int kernel_disable_token_event_rule(struct ltt_kernel_token_event_rule *event) +{ + int ret; + + assert(event); + + ret = kernctl_disable(event->fd); + if (ret < 0) { + switch (-ret) { + case EEXIST: + ret = LTTNG_ERR_KERN_EVENT_EXIST; + break; + default: + PERROR("disable kernel event"); + break; + } + goto error; + } + + event->enabled = 0; + DBG("Kernel trigger token %" PRIu64" disabled (fd: %d)", event->token, event->fd); + + return 0; + +error: + return ret; +} + static struct process_attr_tracker *_kernel_get_process_attr_tracker( struct ltt_kernel_session *session, @@ -1827,20 +1960,37 @@ int init_kernel_tracer(void) if (ret < 0) { goto error_modules; } - if (ret < 1) { WARN("Kernel tracer does not support buffer monitoring. " "The monitoring timer of channels in the kernel domain " "will be set to 0 (disabled)."); } + ret = kernel_create_trigger_group(&kernel_tracer_trigger_group_fd); + if (ret < 0) { + /* TODO: error handling if it is not supported etc. */ + WARN("Failed trigger group creation"); + kernel_tracer_trigger_group_fd = -1; + /* This is not fatal */ + } else { + ret = kernel_create_trigger_group_notification_fd(&kernel_tracer_trigger_group_notification_fd); + if (ret < 0) { + goto error_modules; + } + } + + CDS_INIT_LIST_HEAD(&kernel_tracer_token_list.head); + DBG("Kernel tracer fd %d", kernel_tracer_fd); + DBG("Kernel tracer trigger group fd %d", kernel_tracer_trigger_group_fd); + DBG("Kernel tracer trigger group notificationi fd %d", kernel_tracer_trigger_group_notification_fd); ret = syscall_init_table(kernel_tracer_fd); if (ret < 0) { ERR("Unable to populate syscall table. Syscall tracing won't " "work for this session daemon."); } + return 0; error_version: @@ -1876,6 +2026,31 @@ void cleanup_kernel_tracer(void) { int ret; + struct ltt_kernel_token_event_rule *rule, *rtmp; + cds_list_for_each_entry_safe(rule, rtmp, &kernel_tracer_token_list.head, list) { + kernel_disable_token_event_rule(rule); + trace_kernel_destroy_token_event_rule(rule); + } + + DBG2("Closing kernel trigger group notification fd"); + if (kernel_tracer_trigger_group_notification_fd >= 0) { + ret = close(kernel_tracer_trigger_group_notification_fd); + if (ret) { + PERROR("close"); + } + kernel_tracer_trigger_group_notification_fd = -1; + } + + /* TODO: do we iterate over the list to remove all token? */ + DBG2("Closing kernel trigger group fd"); + if (kernel_tracer_trigger_group_fd >= 0) { + ret = close(kernel_tracer_trigger_group_fd); + if (ret) { + PERROR("close"); + } + kernel_tracer_trigger_group_fd = -1; + } + DBG2("Closing kernel fd"); if (kernel_tracer_fd >= 0) { ret = close(kernel_tracer_fd); @@ -1884,6 +2059,7 @@ void cleanup_kernel_tracer(void) } kernel_tracer_fd = -1; } + DBG("Unloading kernel modules"); modprobe_remove_lttng_all(); free(syscall_table); @@ -1974,3 +2150,298 @@ end: rcu_read_unlock(); return status; } + +enum lttng_error_code kernel_create_trigger_group_notification_fd( + int *trigger_group_notification_fd) +{ + enum lttng_error_code ret = LTTNG_OK; + int local_fd = -1; + + assert(trigger_group_notification_fd); + + ret = kernctl_create_trigger_group_notification_fd(kernel_tracer_trigger_group_fd); + if (ret < 0) { + PERROR("ioctl kernel create trigger group"); + ret = -1; + goto error; + } + + /* Store locally */ + local_fd = ret; + + /* Prevent fd duplication after execlp() */ + ret = fcntl(local_fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("fcntl session fd"); + } + + DBG("Kernel trigger group notification created (fd: %d)", + local_fd); + ret = 0; + +error: + *trigger_group_notification_fd = local_fd; + return ret; +} + +enum lttng_error_code kernel_destroy_trigger_group_notification_fd( + int trigger_group_notification_fd) +{ + enum lttng_error_code ret = LTTNG_OK; + DBG("Closing trigger group notification fd %d", trigger_group_notification_fd); + if (trigger_group_notification_fd >= 0) { + ret = close(trigger_group_notification_fd); + if (ret) { + PERROR("close"); + } + } + return ret; +} + +static int kernel_create_token_event_rule(struct lttng_trigger *trigger, + const struct lttng_credentials *creds, uint64_t token) +{ + int err, fd; + enum lttng_error_code ret; + struct ltt_kernel_token_event_rule *event; + struct lttng_kernel_trigger kernel_trigger; + unsigned int capture_bytecode_count = 0; + struct lttng_condition *condition = NULL; + struct lttng_event_rule *event_rule = NULL; + + assert(trigger); + + condition = lttng_trigger_get_condition(trigger); + assert(condition); + assert(lttng_condition_get_type(condition) == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT); + + lttng_condition_event_rule_get_rule_no_const(condition, &event_rule); + assert(event_rule); + assert(lttng_event_rule_get_type(event_rule) != LTTNG_EVENT_RULE_TYPE_UNKNOWN); + + /* TODO: Should we check that the event-rule is kernel oriented here? */ + + ret = trace_kernel_create_token_event_rule(event_rule, token, &event); + if (ret != LTTNG_OK) { + goto error; + } + + trace_kernel_init_trigger_from_event_rule(event->event_rule, &kernel_trigger); + kernel_trigger.id = event->token; + + fd = kernctl_create_trigger(kernel_tracer_trigger_group_fd, &kernel_trigger); + if (fd < 0) { + switch (-fd) { + case EEXIST: + ret = LTTNG_ERR_KERN_EVENT_EXIST; + break; + case ENOSYS: + WARN("Trigger type not implemented"); + ret = LTTNG_ERR_KERN_EVENT_ENOSYS; + break; + case ENOENT: + WARN("Event %s not found!", kernel_trigger.name); + ret = LTTNG_ERR_KERN_ENABLE_FAIL; + break; + default: + ret = LTTNG_ERR_KERN_ENABLE_FAIL; + PERROR("create trigger ioctl"); + } + goto free_event; + } + + event->fd = fd; + /* Prevent fd duplication after execlp() */ + err = fcntl(event->fd, F_SETFD, FD_CLOEXEC); + if (err < 0) { + PERROR("fcntl session fd"); + } + + if (event->filter) { + err = kernctl_filter(event->fd, event->filter); + if (err < 0) { + switch (-err) { + case ENOMEM: + ret = LTTNG_ERR_FILTER_NOMEM; + break; + default: + ret = LTTNG_ERR_FILTER_INVAL; + break; + } + goto filter_error; + } + } + + if (lttng_event_rule_get_type(event->event_rule) == + LTTNG_EVENT_RULE_TYPE_UPROBE) { + ret = userspace_probe_event_rule_add_callsites( + event->event_rule, creds, event->fd); + if (ret) { + goto add_callsite_error; + } + } + + /* Set the capture bytecode */ + capture_bytecode_count = lttng_trigger_get_capture_bytecode_count(trigger); + for (unsigned int i = 0; i < capture_bytecode_count; i++) { + const struct lttng_bytecode *capture_bytecode = lttng_trigger_get_capture_bytecode_at_index(trigger, i); + ret = kernctl_capture(event->fd, capture_bytecode); + if (ret < 0) { + goto error; + } + } + + err = kernctl_enable(event->fd); + if (err < 0) { + switch (-err) { + case EEXIST: + ret = LTTNG_ERR_KERN_EVENT_EXIST; + break; + default: + PERROR("enable kernel trigger"); + ret = LTTNG_ERR_KERN_ENABLE_FAIL; + break; + } + goto enable_error; + } + + /* Add event to event list */ + cds_list_add(&event->list, &kernel_tracer_token_list.head); + + DBG("Trigger %s created (fd: %d)", kernel_trigger.name, event->fd); + + return 0; + +add_callsite_error: +enable_error: +filter_error: + { + int closeret; + + closeret = close(event->fd); + if (closeret) { + PERROR("close event fd"); + } + } +free_event: + free(event); +error: + return ret; +} + +enum lttng_error_code kernel_update_tokens(void) +{ + enum lttng_error_code ret = LTTNG_OK; + enum lttng_trigger_status t_status; + struct ltt_kernel_token_event_rule *token_event_rule_element; + struct lttng_triggers *triggers; + unsigned int count; + + /* TODO error handling */ + + /* Get list of token trigger from the notification thread here */ + rcu_read_lock(); + pthread_mutex_lock(¬ification_trigger_tokens_ht_lock); + ret = notification_thread_command_get_tokens(notification_thread_handle, &triggers); + if (ret != LTTNG_OK) { + ret = -1; + goto end; + } + + assert(triggers); + + t_status = lttng_triggers_get_count(triggers, &count); + if (t_status != LTTNG_TRIGGER_STATUS_OK) { + ret = -1; + goto end; + } + + for (unsigned int i = 0; i < count; i++) { + struct lttng_condition *condition; + struct lttng_event_rule *event_rule; + struct lttng_trigger *trigger; + struct ltt_kernel_token_event_rule *k_token; + const struct lttng_credentials *creds; + uint64_t token; + + trigger = lttng_triggers_get_pointer_of_index(triggers, i); + assert(trigger); + + /* TODO: error checking and type checking */ + token = lttng_trigger_get_key(trigger); + condition = lttng_trigger_get_condition(trigger); + (void) lttng_condition_event_rule_get_rule_no_const(condition, &event_rule); + + if (lttng_event_rule_get_domain_type(event_rule) != LTTNG_DOMAIN_KERNEL) { + /* Skip ust related trigger */ + continue; + } + + creds = lttng_trigger_get_credentials(trigger); + /* Iterate over all known token trigger */ + k_token = trace_kernel_find_trigger_by_token(&kernel_tracer_token_list, token); + if (!k_token) { + ret = kernel_create_token_event_rule(trigger, creds, token); + if (ret < 0) { + goto end; + } + } + } + + /* Remove all unknown trigger from the app + * TODO find a way better way then this, do it on the unregister command + * and be specific on the token to remove instead of going over all + * trigger known to the app. This is sub optimal. + */ + cds_list_for_each_entry (token_event_rule_element, &kernel_tracer_token_list.head, + list) { + uint64_t token; + bool found = false; + + token = token_event_rule_element->token; + + /* + * Check if the app event trigger still exists on the + * notification side. + * TODO: might want to change the backing data struct of the + * lttng_triggers object to allow quick lookup? + * For kernel mostly all of this can be removed once we delete + * on a per trigger basis. + */ + + for (unsigned int i = 0; i < count; i++) { + struct lttng_trigger *trigger; + uint64_t inner_token; + + trigger = lttng_triggers_get_pointer_of_index( + triggers, i); + assert(trigger); + + inner_token = lttng_trigger_get_key(trigger); + + if (inner_token == token) { + found = true; + break; + } + } + + if (found) { + /* Still valid */ + continue; + } + + kernel_disable_token_event_rule(token_event_rule_element); + trace_kernel_destroy_token_event_rule(token_event_rule_element); + } +end: + lttng_triggers_destroy(triggers); + rcu_read_unlock(); + pthread_mutex_unlock(¬ification_trigger_tokens_ht_lock); + return ret; + +} + +int kernel_get_notification_fd(void) +{ + return kernel_tracer_trigger_group_notification_fd; +} diff --git a/src/bin/lttng-sessiond/kernel.h b/src/bin/lttng-sessiond/kernel.h index 53d480313..913d14e44 100644 --- a/src/bin/lttng-sessiond/kernel.h +++ b/src/bin/lttng-sessiond/kernel.h @@ -29,7 +29,7 @@ int kernel_create_session(struct ltt_session *session); int kernel_create_channel(struct ltt_kernel_session *session, struct lttng_channel *chan); int kernel_create_event(struct lttng_event *ev, struct ltt_kernel_channel *channel, - char *filter_expression, struct lttng_filter_bytecode *filter); + char *filter_expression, struct lttng_bytecode *filter); int kernel_disable_channel(struct ltt_kernel_channel *chan); int kernel_disable_event(struct ltt_kernel_event *event); int kernel_enable_event(struct ltt_kernel_event *event); @@ -81,4 +81,11 @@ bool kernel_tracer_is_initialized(void); enum lttng_error_code kernel_create_channel_subdirectories( const struct ltt_kernel_session *ksess); +enum lttng_error_code kernel_create_trigger_group_notification_fd( + int *trigger_group_notification_fd); +enum lttng_error_code kernel_destroy_trigger_group_notification_fd( + int trigger_group_notification_fd); +enum lttng_error_code kernel_update_tokens(void); +int kernel_get_notification_fd(void); + #endif /* _LTT_KERNEL_CTL_H */ diff --git a/src/bin/lttng-sessiond/lttng-sessiond.h b/src/bin/lttng-sessiond/lttng-sessiond.h index 277fc23e3..ee8ea2400 100644 --- a/src/bin/lttng-sessiond/lttng-sessiond.h +++ b/src/bin/lttng-sessiond/lttng-sessiond.h @@ -70,6 +70,7 @@ extern struct lttng_kernel_tracer_abi_version kernel_tracer_abi_version; /* Notification thread handle. */ extern struct notification_thread_handle *notification_thread_handle; +extern pthread_mutex_t notification_trigger_tokens_ht_lock; /* * This contains extra data needed for processing a command received by the diff --git a/src/bin/lttng-sessiond/main.c b/src/bin/lttng-sessiond/main.c index 55e0ad3d7..965625c9b 100644 --- a/src/bin/lttng-sessiond/main.c +++ b/src/bin/lttng-sessiond/main.c @@ -314,6 +314,9 @@ static void sessiond_cleanup(void) pthread_mutex_destroy(&session_list->lock); + DBG("Cleaning up all trigger agents"); + trigger_agent_ht_clean(); + DBG("Cleaning up all agent apps"); agent_app_ht_clean(); DBG("Closing all UST sockets"); @@ -1302,6 +1305,7 @@ int main(int argc, char **argv) struct lttng_thread *notification_thread = NULL; struct lttng_thread *register_apps_thread = NULL; + logger_set_thread_name("Main", false); init_kernel_workarounds(); rcu_register_thread(); @@ -1538,6 +1542,11 @@ int main(int argc, char **argv) goto stop_threads; } + if (trigger_agent_ht_alloc()) { + ERR("Failed to allocate trigger agent hash table"); + retval = -1; + goto stop_threads; + } /* * These actions must be executed as root. We do that *after* setting up * the sockets path because we MUST make the check for another daemon using @@ -1635,7 +1644,8 @@ int main(int argc, char **argv) notification_thread_handle = notification_thread_handle_create( ust32_channel_monitor_pipe, ust64_channel_monitor_pipe, - kernel_channel_monitor_pipe); + kernel_channel_monitor_pipe, + kernel_get_notification_fd()); if (!notification_thread_handle) { retval = -1; ERR("Failed to create notification thread shared data"); diff --git a/src/bin/lttng-sessiond/modprobe.c b/src/bin/lttng-sessiond/modprobe.c index cf9c12b3c..7bee25f88 100644 --- a/src/bin/lttng-sessiond/modprobe.c +++ b/src/bin/lttng-sessiond/modprobe.c @@ -30,6 +30,10 @@ #define LTTNG_MOD_OPTIONAL 0 /* LTTng kernel tracer mandatory core modules list */ +/* TODO: the new trigger client might not be present in previous lttng-modules + * should it be optional? + * Can we reuse this to also know of the trigger feature is supported? + */ struct kern_modules_param kern_modules_control_core[] = { { (char *) "lttng-ring-buffer-client-discard" }, { (char *) "lttng-ring-buffer-client-overwrite" }, @@ -37,6 +41,7 @@ struct kern_modules_param kern_modules_control_core[] = { { (char *) "lttng-ring-buffer-client-mmap-discard" }, { (char *) "lttng-ring-buffer-client-mmap-overwrite" }, { (char *) "lttng-ring-buffer-metadata-mmap-client" }, + { (char *) "lttng-ring-buffer-trigger-client" }, }; /* LTTng kernel tracer probe modules list */ diff --git a/src/bin/lttng-sessiond/notification-thread-commands.c b/src/bin/lttng-sessiond/notification-thread-commands.c index 4474d1978..4584500e5 100644 --- a/src/bin/lttng-sessiond/notification-thread-commands.c +++ b/src/bin/lttng-sessiond/notification-thread-commands.c @@ -10,6 +10,7 @@ #include "notification-thread.h" #include "notification-thread-commands.h" #include +#include #include #include #include @@ -17,7 +18,6 @@ static void init_notification_thread_command(struct notification_thread_command *cmd) { - memset(cmd, 0, sizeof(*cmd)); CDS_INIT_LIST_HEAD(&cmd->cmd_list_node); lttng_waiter_init(&cmd->reply_waiter); } @@ -54,13 +54,69 @@ error_unlock_queue: return -1; } +static +struct notification_thread_command *notification_thread_command_copy( + const struct notification_thread_command *original_cmd) +{ + struct notification_thread_command *new_cmd; + + new_cmd = zmalloc(sizeof(*new_cmd)); + if (!new_cmd) { + goto end; + } + + *new_cmd = *original_cmd; + init_notification_thread_command(new_cmd); +end: + return new_cmd; +} + +static +int run_command_no_wait(struct notification_thread_handle *handle, + const struct notification_thread_command *in_cmd) +{ + int ret; + uint64_t notification_counter = 1; + struct notification_thread_command *new_cmd = + notification_thread_command_copy(in_cmd); + + if (!new_cmd) { + goto error; + } + new_cmd->is_async = true; + + pthread_mutex_lock(&handle->cmd_queue.lock); + /* Add to queue. */ + cds_list_add_tail(&new_cmd->cmd_list_node, + &handle->cmd_queue.list); + /* Wake-up thread. */ + ret = lttng_write(lttng_pipe_get_writefd(handle->cmd_queue.event_pipe), + ¬ification_counter, sizeof(notification_counter)); + if (ret != sizeof(notification_counter)) { + PERROR("write to notification thread's queue event fd"); + /* + * Remove the command from the list so the notification + * thread does not process it. + */ + cds_list_del(&new_cmd->cmd_list_node); + goto error_unlock_queue; + } + pthread_mutex_unlock(&handle->cmd_queue.lock); + return 0; +error_unlock_queue: + free(new_cmd); + pthread_mutex_unlock(&handle->cmd_queue.lock); +error: + return -1; +} + enum lttng_error_code notification_thread_command_register_trigger( struct notification_thread_handle *handle, struct lttng_trigger *trigger) { int ret; enum lttng_error_code ret_code; - struct notification_thread_command cmd; + struct notification_thread_command cmd = {}; init_notification_thread_command(&cmd); @@ -83,7 +139,7 @@ enum lttng_error_code notification_thread_command_unregister_trigger( { int ret; enum lttng_error_code ret_code; - struct notification_thread_command cmd; + struct notification_thread_command cmd = {}; init_notification_thread_command(&cmd); @@ -108,7 +164,7 @@ enum lttng_error_code notification_thread_command_add_channel( { int ret; enum lttng_error_code ret_code; - struct notification_thread_command cmd; + struct notification_thread_command cmd = {}; init_notification_thread_command(&cmd); @@ -137,7 +193,7 @@ enum lttng_error_code notification_thread_command_remove_channel( { int ret; enum lttng_error_code ret_code; - struct notification_thread_command cmd; + struct notification_thread_command cmd = {}; init_notification_thread_command(&cmd); @@ -162,7 +218,7 @@ enum lttng_error_code notification_thread_command_session_rotation_ongoing( { int ret; enum lttng_error_code ret_code; - struct notification_thread_command cmd; + struct notification_thread_command cmd = {}; init_notification_thread_command(&cmd); @@ -191,7 +247,7 @@ enum lttng_error_code notification_thread_command_session_rotation_completed( { int ret; enum lttng_error_code ret_code; - struct notification_thread_command cmd; + struct notification_thread_command cmd = {}; init_notification_thread_command(&cmd); @@ -213,11 +269,115 @@ end: return ret_code; } +enum lttng_error_code notification_thread_command_add_application( + struct notification_thread_handle *handle, + struct lttng_pipe *pipe) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_ADD_APPLICATION; + cmd.parameters.application.read_side_trigger_event_application_pipe = lttng_pipe_get_readfd(pipe); + + ret = run_command_wait(handle, &cmd); + if (ret) { + ret_code = LTTNG_ERR_UNK; + goto end; + } + ret_code = cmd.reply_code; +end: + return ret_code; +} + +enum lttng_error_code notification_thread_command_remove_application( + struct notification_thread_handle *handle, + struct lttng_pipe *pipe) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_REMOVE_APPLICATION; + cmd.parameters.application.read_side_trigger_event_application_pipe = lttng_pipe_get_readfd(pipe); + + ret = run_command_wait(handle, &cmd); + if (ret) { + ret_code = LTTNG_ERR_UNK; + goto end; + } + ret_code = cmd.reply_code; +end: + return ret_code; +} + +enum lttng_error_code notification_thread_command_get_tokens( + struct notification_thread_handle *handle, + struct lttng_triggers **tokens_triggers) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + assert(handle); + assert(tokens_triggers); + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_GET_TOKENS; + + ret = run_command_wait(handle, &cmd); + if (ret) { + ret_code = LTTNG_ERR_UNK; + goto end; + } + ret_code = cmd.reply_code; + *tokens_triggers = cmd.reply.get_tokens.triggers; + +end: + return ret_code; +} + +enum lttng_error_code notification_thread_command_list_triggers( + struct notification_thread_handle *handle, + uid_t uid, + gid_t gid, + struct lttng_triggers **triggers) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + assert(handle); + assert(triggers); + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_LIST_TRIGGERS; + cmd.parameters.list_triggers.uid = uid; + cmd.parameters.list_triggers.gid = gid; + + ret = run_command_wait(handle, &cmd); + if (ret) { + ret_code = LTTNG_ERR_UNK; + goto end; + } + ret_code = cmd.reply_code; + *triggers = cmd.reply.list_triggers.triggers; + +end: + return ret_code; +} + void notification_thread_command_quit( struct notification_thread_handle *handle) { int ret; - struct notification_thread_command cmd; + struct notification_thread_command cmd = {}; init_notification_thread_command(&cmd); @@ -225,3 +385,67 @@ void notification_thread_command_quit( ret = run_command_wait(handle, &cmd); assert(!ret && cmd.reply_code == LTTNG_OK); } + +int notification_thread_client_communication_update( + struct notification_thread_handle *handle, + notification_client_id id, + enum client_transmission_status transmission_status) +{ + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_CLIENT_COMMUNICATION_UPDATE; + cmd.parameters.client_communication_update.id = id; + cmd.parameters.client_communication_update.status = transmission_status; + return run_command_no_wait(handle, &cmd); +} + +/* + * Takes ownership of the payload if present. + */ +LTTNG_HIDDEN +struct lttng_trigger_notification *lttng_trigger_notification_create( + uint64_t id, + enum lttng_domain_type domain, + char *payload, + size_t payload_size) +{ + struct lttng_trigger_notification *notification = NULL; + + assert(domain != LTTNG_DOMAIN_NONE); + + if (payload) { + assert(payload_size > 0); + } else { + assert(payload_size == 0); + } + + notification = zmalloc(sizeof(struct lttng_trigger_notification)); + if (notification == NULL) { + ERR("[notification-thread] Error allocating notification "); + goto end; + } + + notification->id = id; + notification->type = domain; + notification->capture_buffer = payload; + notification->capture_buf_size = payload_size; + +end: + return notification; +} + +LTTNG_HIDDEN +void lttng_trigger_notification_destroy( + struct lttng_trigger_notification *notification) +{ + if (!notification) { + return; + } + + if(notification->capture_buffer) { + free(notification->capture_buffer); + } + free(notification); +} diff --git a/src/bin/lttng-sessiond/notification-thread-commands.h b/src/bin/lttng-sessiond/notification-thread-commands.h index a90d1ac2b..6ce17f062 100644 --- a/src/bin/lttng-sessiond/notification-thread-commands.h +++ b/src/bin/lttng-sessiond/notification-thread-commands.h @@ -15,6 +15,7 @@ #include "notification-thread-internal.h" #include "notification-thread-events.h" #include +#include struct notification_thread_data; struct lttng_trigger; @@ -26,7 +27,12 @@ enum notification_thread_command_type { NOTIFICATION_COMMAND_TYPE_REMOVE_CHANNEL, NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING, NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED, + NOTIFICATION_COMMAND_TYPE_ADD_APPLICATION, + NOTIFICATION_COMMAND_TYPE_REMOVE_APPLICATION, + NOTIFICATION_COMMAND_TYPE_GET_TOKENS, + NOTIFICATION_COMMAND_TYPE_LIST_TRIGGERS, NOTIFICATION_COMMAND_TYPE_QUIT, + NOTIFICATION_COMMAND_TYPE_CLIENT_COMMUNICATION_UPDATE, }; struct notification_thread_command { @@ -62,11 +68,37 @@ struct notification_thread_command { uint64_t trace_archive_chunk_id; struct lttng_trace_archive_location *location; } session_rotation; + /* Add/Remove application */ + struct { + int read_side_trigger_event_application_pipe; + } application; + /* List triggers */ + struct { + /* Credential */ + uid_t uid; + gid_t gid; + } list_triggers; + /* Client communication update. */ + struct { + notification_client_id id; + enum client_transmission_status status; + } client_communication_update; + } parameters; + union { + struct { + struct lttng_triggers *triggers; + } get_tokens; + struct { + struct lttng_triggers *triggers; + } list_triggers; + } reply; + /* lttng_waiter on which to wait for command reply (optional). */ struct lttng_waiter reply_waiter; enum lttng_error_code reply_code; + bool is_async; }; enum lttng_error_code notification_thread_command_register_trigger( @@ -99,6 +131,28 @@ enum lttng_error_code notification_thread_command_session_rotation_completed( uint64_t trace_archive_chunk_id, struct lttng_trace_archive_location *location); +enum lttng_error_code notification_thread_command_add_application( + struct notification_thread_handle *handle, + struct lttng_pipe *trigger_event_application_pipe); + +enum lttng_error_code notification_thread_command_remove_application( + struct notification_thread_handle *handle, + struct lttng_pipe *trigger_event_application_pipe); + +/* Must hold the notification_trigger_tokens_ht_lock to protect against + * insertion removal of triggers TODO: is it the case even with refcounting? */ +/* todo find a better way....*/ +enum lttng_error_code notification_thread_command_get_tokens( + struct notification_thread_handle *handle, + struct lttng_triggers **triggers); + +/* TODO: for now we borrow with no refcount the trigger. THIS IS DANGEROUS */ +enum lttng_error_code notification_thread_command_list_triggers( + struct notification_thread_handle *handle, + uid_t uid, + gid_t gid, + struct lttng_triggers **triggers); + void notification_thread_command_quit( struct notification_thread_handle *handle); diff --git a/src/bin/lttng-sessiond/notification-thread-events.c b/src/bin/lttng-sessiond/notification-thread-events.c index 6c69a0232..12f9bbb20 100644 --- a/src/bin/lttng-sessiond/notification-thread-events.c +++ b/src/bin/lttng-sessiond/notification-thread-events.c @@ -19,12 +19,16 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include +#include #include #include @@ -50,7 +54,7 @@ enum lttng_object_type { struct lttng_trigger_list_element { /* No ownership of the trigger object is assumed. */ - const struct lttng_trigger *trigger; + struct lttng_trigger *trigger; struct cds_list_head node; }; @@ -108,6 +112,7 @@ struct lttng_session_trigger_list { struct lttng_trigger_ht_element { struct lttng_trigger *trigger; struct cds_lfht_node node; + struct cds_lfht_node node_by_name; /* call_rcu delayed reclaim. */ struct rcu_head rcu_node; }; @@ -117,87 +122,6 @@ struct lttng_condition_list_element { struct cds_list_head node; }; -struct notification_client_list_element { - struct notification_client *client; - struct cds_list_head node; -}; - -struct notification_client_list { - const struct lttng_trigger *trigger; - struct cds_list_head list; - struct cds_lfht_node notification_trigger_ht_node; - /* call_rcu delayed reclaim. */ - struct rcu_head rcu_node; -}; - -struct notification_client { - int socket; - /* Client protocol version. */ - uint8_t major, minor; - uid_t uid; - gid_t gid; - /* - * Indicates if the credentials and versions of the client have been - * checked. - */ - bool validated; - /* - * Conditions to which the client's notification channel is subscribed. - * List of struct lttng_condition_list_node. The condition member is - * owned by the client. - */ - struct cds_list_head condition_list; - struct cds_lfht_node client_socket_ht_node; - struct { - struct { - /* - * During the reception of a message, the reception - * buffers' "size" is set to contain the current - * message's complete payload. - */ - struct lttng_dynamic_buffer buffer; - /* Bytes left to receive for the current message. */ - size_t bytes_to_receive; - /* Type of the message being received. */ - enum lttng_notification_channel_message_type msg_type; - /* - * Indicates whether or not credentials are expected - * from the client. - */ - bool expect_creds; - /* - * Indicates whether or not credentials were received - * from the client. - */ - bool creds_received; - /* Only used during credentials reception. */ - lttng_sock_cred creds; - } inbound; - struct { - /* - * Indicates whether or not a notification addressed to - * this client was dropped because a command reply was - * already buffered. - * - * A notification is dropped whenever the buffer is not - * empty. - */ - bool dropped_notification; - /* - * Indicates whether or not a command reply is already - * buffered. In this case, it means that the client is - * not consuming command replies before emitting a new - * one. This could be caused by a protocol error or a - * misbehaving/malicious client. - */ - bool queued_command_reply; - struct lttng_dynamic_buffer buffer; - } outbound; - } communication; - /* call_rcu delayed reclaim. */ - struct rcu_head rcu_node; -}; - struct channel_state_sample { struct channel_key key; struct cds_lfht_node channel_state_ht_node; @@ -258,20 +182,34 @@ void lttng_session_trigger_list_destroy( struct lttng_session_trigger_list *list); static int lttng_session_trigger_list_add(struct lttng_session_trigger_list *list, - const struct lttng_trigger *trigger); + struct lttng_trigger *trigger); +static +int client_handle_transmission_status( + struct notification_client *client, + enum client_transmission_status transmission_status, + struct notification_thread_state *state); static -int match_client(struct cds_lfht_node *node, const void *key) +int match_client_socket(struct cds_lfht_node *node, const void *key) { /* This double-cast is intended to supress pointer-to-cast warning. */ - int socket = (int) (intptr_t) key; - struct notification_client *client; + const int socket = (int) (intptr_t) key; + const struct notification_client *client = caa_container_of(node, + struct notification_client, client_socket_ht_node); - client = caa_container_of(node, struct notification_client, - client_socket_ht_node); + return client->socket == socket; +} + +static +int match_client_id(struct cds_lfht_node *node, const void *key) +{ + /* This double-cast is intended to supress pointer-to-cast warning. */ + const notification_client_id id = *((notification_client_id *) key); + const struct notification_client *client = caa_container_of( + node, struct notification_client, client_id_ht_node); - return !!(client->socket == socket); + return client->id == id; } static @@ -326,18 +264,42 @@ int match_channel_info(struct cds_lfht_node *node, const void *key) } static -int match_condition(struct cds_lfht_node *node, const void *key) +int match_trigger(struct cds_lfht_node *node, const void *key) { - struct lttng_condition *condition_key = (struct lttng_condition *) key; - struct lttng_trigger_ht_element *trigger; - struct lttng_condition *condition; + bool match = false; + struct lttng_trigger *trigger_key = (struct lttng_trigger *) key; + struct lttng_trigger_ht_element *trigger_ht_element; + const struct lttng_credentials *creds_key; + const struct lttng_credentials *creds_node; - trigger = caa_container_of(node, struct lttng_trigger_ht_element, + trigger_ht_element = caa_container_of(node, struct lttng_trigger_ht_element, node); - condition = lttng_trigger_get_condition(trigger->trigger); - assert(condition); - return !!lttng_condition_is_equal(condition_key, condition); + match = lttng_trigger_is_equal(trigger_key, trigger_ht_element->trigger); + if (!match) { + goto end; + } + + /* Validate credential */ + /* TODO: this could be moved to lttng_trigger_equal depending on how we + * handle root behaviour on disable and listing. + */ + creds_key = lttng_trigger_get_credentials(trigger_key); + creds_node = lttng_trigger_get_credentials(trigger_ht_element->trigger); + match = lttng_credentials_is_equal(creds_key, creds_node); +end: + return !!match; +} + +static +int match_trigger_token(struct cds_lfht_node *node, const void *key) +{ + const uint64_t *_key = key; + struct notification_trigger_tokens_ht_element *element; + + element = caa_container_of(node, struct notification_trigger_tokens_ht_element, + node); + return *_key == element->token ; } static @@ -350,7 +312,7 @@ int match_client_list_condition(struct cds_lfht_node *node, const void *key) assert(condition_key); client_list = caa_container_of(node, struct notification_client_list, - notification_trigger_ht_node); + notification_trigger_clients_ht_node); condition = lttng_trigger_get_const_condition(client_list->trigger); return !!lttng_condition_is_equal(condition_key, condition); @@ -366,6 +328,23 @@ int match_session(struct cds_lfht_node *node, const void *key) return !strcmp(session_info->name, name); } +/* + * Match function for string node. + */ +static int match_str(struct cds_lfht_node *node, const void *key) +{ + struct lttng_trigger_ht_element *trigger_ht_element; + const char *name; + + trigger_ht_element = caa_container_of(node, struct lttng_trigger_ht_element, + node_by_name); + + /* TODO error checking */ + lttng_trigger_get_name(trigger_ht_element->trigger, &name); + + return hash_match_key_str(name, (void *) key); +} + static unsigned long lttng_condition_buffer_usage_hash( const struct lttng_condition *_condition) @@ -442,6 +421,22 @@ unsigned long lttng_condition_session_rotation_hash( return hash; } +static +unsigned long lttng_condition_event_rule_hash( + const struct lttng_condition *_condition) +{ + unsigned long hash, condition_type; + struct lttng_condition_event_rule *condition; + + condition = container_of(_condition, + struct lttng_condition_event_rule, parent); + condition_type = (unsigned long) condition->parent.type; + hash = hash_key_ulong((void *) condition_type, lttng_ht_seed); + + /* TODO: further hasg using the event rule? on pattern maybe?*/ + return hash; +} + /* * The lttng_condition hashing code is kept in this file (rather than * condition.c) since it makes use of GPLv2 code (hashtable utils), which we @@ -459,6 +454,8 @@ unsigned long lttng_condition_hash(const struct lttng_condition *condition) case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: return lttng_condition_session_rotation_hash(condition); + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + return lttng_condition_event_rule_hash(condition); default: ERR("[notification-thread] Unexpected condition type caught"); abort(); @@ -475,6 +472,18 @@ unsigned long hash_channel_key(struct channel_key *key) return key_hash ^ domain_hash; } +static +unsigned long hash_client_socket(int socket) +{ + return hash_key_ulong((void *) (unsigned long) socket, lttng_ht_seed); +} + +static +unsigned long hash_client_id(notification_client_id id) +{ + return hash_key_u64(&id, lttng_ht_seed); +} + /* * Get the type of object to which a given condition applies. Bindings let * the notification system evaluate a trigger's condition when a given @@ -495,6 +504,8 @@ enum lttng_object_type get_condition_binding_object( case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: return LTTNG_OBJECT_TYPE_SESSION; + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + return LTTNG_OBJECT_TYPE_NONE; default: return LTTNG_OBJECT_TYPE_UNKNOWN; } @@ -581,7 +592,7 @@ struct session_info *session_info_create(const char *name, uid_t uid, gid_t gid, assert(name); - session_info = zmalloc(sizeof(*session_info)); + session_info = malloc(sizeof(*session_info)); if (!session_info) { goto end; } @@ -666,7 +677,90 @@ error: return NULL; } -/* RCU read lock must be held by the caller. */ +LTTNG_HIDDEN +bool notification_client_list_get(struct notification_client_list *list) +{ + return urcu_ref_get_unless_zero(&list->ref); +} + +static +void free_notification_client_list_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct notification_client_list, + rcu_node)); +} + +static +void notification_client_list_release(struct urcu_ref *list_ref) +{ + struct notification_client_list *list = + container_of(list_ref, typeof(*list), ref); + struct notification_client_list_element *client_list_element, *tmp; + + if (list->notification_trigger_clients_ht) { + rcu_read_lock(); + cds_lfht_del(list->notification_trigger_clients_ht, + &list->notification_trigger_clients_ht_node); + rcu_read_unlock(); + list->notification_trigger_clients_ht = NULL; + } + cds_list_for_each_entry_safe(client_list_element, tmp, + &list->list, node) { + free(client_list_element); + } + pthread_mutex_destroy(&list->lock); + call_rcu(&list->rcu_node, free_notification_client_list_rcu); +} + +static +struct notification_client_list *notification_client_list_create( + const struct lttng_trigger *trigger) +{ + struct notification_client_list *client_list = + zmalloc(sizeof(*client_list)); + + if (!client_list) { + goto error; + } + pthread_mutex_init(&client_list->lock, NULL); + urcu_ref_init(&client_list->ref); + cds_lfht_node_init(&client_list->notification_trigger_clients_ht_node); + CDS_INIT_LIST_HEAD(&client_list->list); + client_list->trigger = trigger; +error: + return client_list; +} + +static +void publish_notification_client_list( + struct notification_thread_state *state, + struct notification_client_list *list) +{ + const struct lttng_condition *condition = + lttng_trigger_get_const_condition(list->trigger); + + assert(!list->notification_trigger_clients_ht); + + list->notification_trigger_clients_ht = + state->notification_trigger_clients_ht; + + rcu_read_lock(); + cds_lfht_add(state->notification_trigger_clients_ht, + lttng_condition_hash(condition), + &list->notification_trigger_clients_ht_node); + rcu_read_unlock(); +} + +LTTNG_HIDDEN +void notification_client_list_put(struct notification_client_list *list) +{ + if (!list) { + return; + } + return urcu_ref_put(&list->ref, notification_client_list_release); +} + +/* Provides a reference to the returned list. */ static struct notification_client_list *get_client_list_from_condition( struct notification_thread_state *state, @@ -674,20 +768,24 @@ struct notification_client_list *get_client_list_from_condition( { struct cds_lfht_node *node; struct cds_lfht_iter iter; + struct notification_client_list *list = NULL; + rcu_read_lock(); cds_lfht_lookup(state->notification_trigger_clients_ht, lttng_condition_hash(condition), match_client_list_condition, condition, &iter); node = cds_lfht_iter_get_node(&iter); - - return node ? caa_container_of(node, - struct notification_client_list, - notification_trigger_ht_node) : NULL; + if (node) { + list = container_of(node, struct notification_client_list, + notification_trigger_clients_ht_node); + list = notification_client_list_get(list) ? list : NULL; + } + rcu_read_unlock(); + return list; } -/* This function must be called with the RCU read lock held. */ static int evaluate_channel_condition_for_client( const struct lttng_condition *condition, @@ -703,6 +801,8 @@ int evaluate_channel_condition_for_client( struct channel_state_sample *last_sample = NULL; struct lttng_channel_trigger_list *channel_trigger_list = NULL; + rcu_read_lock(); + /* Find the channel associated with the condition. */ cds_lfht_for_each_entry(state->channel_triggers_ht, &iter, channel_trigger_list, channel_triggers_ht_node) { @@ -777,6 +877,7 @@ int evaluate_channel_condition_for_client( *session_uid = channel_info->session_info->uid; *session_gid = channel_info->session_info->gid; end: + rcu_read_unlock(); return ret; } @@ -812,7 +913,6 @@ end: return session_name; } -/* This function must be called with the RCU read lock held. */ static int evaluate_session_condition_for_client( const struct lttng_condition *condition, @@ -826,6 +926,7 @@ int evaluate_session_condition_for_client( const char *session_name; struct session_info *session_info = NULL; + rcu_read_lock(); session_name = get_condition_session_name(condition); /* Find the session associated with the trigger. */ @@ -879,10 +980,10 @@ int evaluate_session_condition_for_client( end_session_put: session_info_put(session_info); end: + rcu_read_unlock(); return ret; } -/* This function must be called with the RCU read lock held. */ static int evaluate_condition_for_client(const struct lttng_trigger *trigger, const struct lttng_condition *condition, @@ -891,7 +992,9 @@ int evaluate_condition_for_client(const struct lttng_trigger *trigger, { int ret; struct lttng_evaluation *evaluation = NULL; - struct notification_client_list client_list = { 0 }; + struct notification_client_list client_list = { + .lock = PTHREAD_MUTEX_INITIALIZER, + }; struct notification_client_list_element client_list_element = { 0 }; uid_t object_uid = 0; gid_t object_gid = 0; @@ -911,6 +1014,7 @@ int evaluate_condition_for_client(const struct lttng_trigger *trigger, &evaluation, &object_uid, &object_gid); break; case LTTNG_OBJECT_TYPE_NONE: + DBG("[notification-thread] Newly subscribed-to condition not binded to object, nothing to evaluate"); ret = 0; goto end; case LTTNG_OBJECT_TYPE_UNKNOWN: @@ -933,7 +1037,7 @@ int evaluate_condition_for_client(const struct lttng_trigger *trigger, * Create a temporary client list with the client currently * subscribing. */ - cds_lfht_node_init(&client_list.notification_trigger_ht_node); + cds_lfht_node_init(&client_list.notification_trigger_clients_ht_node); CDS_INIT_LIST_HEAD(&client_list.list); client_list.trigger = trigger; @@ -957,7 +1061,7 @@ int notification_thread_client_subscribe(struct notification_client *client, enum lttng_notification_channel_status *_status) { int ret = 0; - struct notification_client_list *client_list; + struct notification_client_list *client_list = NULL; struct lttng_condition_list_element *condition_list_element = NULL; struct notification_client_list_element *client_list_element = NULL; enum lttng_notification_channel_status status = @@ -986,8 +1090,6 @@ int notification_thread_client_subscribe(struct notification_client *client, goto error; } - rcu_read_lock(); - /* * Add the newly-subscribed condition to the client's subscription list. */ @@ -1003,20 +1105,24 @@ int notification_thread_client_subscribe(struct notification_client *client, * since this trigger is not registered yet. */ free(client_list_element); - goto end_unlock; + goto end; } /* * The condition to which the client just subscribed is evaluated * at this point so that conditions that are already TRUE result * in a notification being sent out. + * + * The client_list's trigger is used without locking the list itself. + * This is correct since the list doesn't own the trigger and the + * object is immutable. */ if (evaluate_condition_for_client(client_list->trigger, condition, client, state)) { WARN("[notification-thread] Evaluation of a condition on client subscription failed, aborting."); ret = -1; free(client_list_element); - goto end_unlock; + goto end; } /* @@ -1026,13 +1132,17 @@ int notification_thread_client_subscribe(struct notification_client *client, */ client_list_element->client = client; CDS_INIT_LIST_HEAD(&client_list_element->node); + + pthread_mutex_lock(&client_list->lock); cds_list_add(&client_list_element->node, &client_list->list); -end_unlock: - rcu_read_unlock(); + pthread_mutex_unlock(&client_list->lock); end: if (_status) { *_status = status; } + if (client_list) { + notification_client_list_put(client_list); + } return ret; error: free(condition_list_element); @@ -1088,23 +1198,24 @@ int notification_thread_client_unsubscribe( * Remove the client from the list of clients interested the trigger * matching the condition. */ - rcu_read_lock(); client_list = get_client_list_from_condition(state, condition); if (!client_list) { - goto end_unlock; + goto end; } + pthread_mutex_lock(&client_list->lock); cds_list_for_each_entry_safe(client_list_element, client_tmp, &client_list->list, node) { - if (client_list_element->client->socket != client->socket) { + if (client_list_element->client->id != client->id) { continue; } cds_list_del(&client_list_element->node); free(client_list_element); break; } -end_unlock: - rcu_read_unlock(); + pthread_mutex_unlock(&client_list->lock); + notification_client_list_put(client_list); + client_list = NULL; end: lttng_condition_destroy(condition); if (_status) { @@ -1123,24 +1234,22 @@ static void notification_client_destroy(struct notification_client *client, struct notification_thread_state *state) { - struct lttng_condition_list_element *condition_list_element, *tmp; - if (!client) { return; } - /* Release all conditions to which the client was subscribed. */ - cds_list_for_each_entry_safe(condition_list_element, tmp, - &client->condition_list, node) { - (void) notification_thread_client_unsubscribe(client, - condition_list_element->condition, state, NULL); - } - + /* + * The client object is not reachable by other threads, no need to lock + * the client here. + */ if (client->socket >= 0) { (void) lttcomm_close_unix_sock(client->socket); + client->socket = -1; } + client->communication.active = false; lttng_dynamic_buffer_reset(&client->communication.inbound.buffer); lttng_dynamic_buffer_reset(&client->communication.outbound.buffer); + pthread_mutex_destroy(&client->lock); call_rcu(&client->rcu_node, free_notification_client_rcu); } @@ -1157,8 +1266,8 @@ struct notification_client *get_client_from_socket(int socket, struct notification_client *client = NULL; cds_lfht_lookup(state->client_socket_ht, - hash_key_ulong((void *) (unsigned long) socket, lttng_ht_seed), - match_client, + hash_client_socket(socket), + match_client_socket, (void *) (unsigned long) socket, &iter); node = cds_lfht_iter_get_node(&iter); @@ -1172,6 +1281,34 @@ end: return client; } +/* + * Call with rcu_read_lock held (and hold for the lifetime of the returned + * client pointer). + */ +static +struct notification_client *get_client_from_id(notification_client_id id, + struct notification_thread_state *state) +{ + struct cds_lfht_iter iter; + struct cds_lfht_node *node; + struct notification_client *client = NULL; + + cds_lfht_lookup(state->client_id_ht, + hash_client_id(id), + match_client_id, + &id, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (!node) { + goto end; + } + + client = caa_container_of(node, struct notification_client, + client_id_ht_node); +end: + return client; +} + static bool buffer_usage_condition_applies_to_channel( const struct lttng_condition *condition, @@ -1375,7 +1512,7 @@ void lttng_session_trigger_list_destroy(struct lttng_session_trigger_list *list) static int lttng_session_trigger_list_add(struct lttng_session_trigger_list *list, - const struct lttng_trigger *trigger) + struct lttng_trigger *trigger) { int ret = 0; struct lttng_trigger_list_element *new_element = @@ -1547,9 +1684,10 @@ int handle_notification_thread_command_add_channel( struct cds_lfht_iter iter; struct session_info *session_info = NULL; - DBG("[notification-thread] Adding channel %s from session %s, channel key = %" PRIu64 " in %s domain", + DBG("[notification-thread] Adding channel %s from session %s, channel key = %" PRIu64 + " in %s domain", channel_name, session_name, channel_key_int, - channel_domain == LTTNG_DOMAIN_KERNEL ? "kernel" : "user space"); + lttng_domain_type_str(channel_domain)); CDS_INIT_LIST_HEAD(&trigger_list); @@ -1649,8 +1787,9 @@ int handle_notification_thread_command_remove_channel( struct channel_key key = { .key = channel_key, .domain = domain }; struct channel_info *channel_info; - DBG("[notification-thread] Removing channel key = %" PRIu64 " in %s domain", - channel_key, domain == LTTNG_DOMAIN_KERNEL ? "kernel" : "user space"); + DBG("[notification-thread] Removing channel key = %" PRIu64 + " in %s domain", + channel_key, lttng_domain_type_str(domain)); rcu_read_lock(); @@ -1762,6 +1901,7 @@ int handle_notification_thread_command_session_rotation( struct notification_client_list *client_list; struct lttng_evaluation *evaluation = NULL; enum lttng_condition_type condition_type; + bool client_list_is_empty; trigger = trigger_list_element->trigger; condition = lttng_trigger_get_const_condition(trigger); @@ -1785,7 +1925,10 @@ int handle_notification_thread_command_session_rotation( client_list = get_client_list_from_condition(state, condition); assert(client_list); - if (cds_list_empty(&client_list->list)) { + pthread_mutex_lock(&client_list->lock); + client_list_is_empty = cds_list_empty(&client_list->list); + pthread_mutex_unlock(&client_list->lock); + if (client_list_is_empty) { /* * No clients interested in the evaluation's result, * skip it. @@ -1805,7 +1948,7 @@ int handle_notification_thread_command_session_rotation( /* Internal error */ ret = -1; cmd_result = LTTNG_ERR_UNK; - goto end; + goto put_list; } /* Dispatch evaluation result to all clients. */ @@ -1814,8 +1957,10 @@ int handle_notification_thread_command_session_rotation( session_info->uid, session_info->gid); lttng_evaluation_destroy(evaluation); +put_list: + notification_client_list_put(client_list); if (caa_unlikely(ret)) { - goto end; + break; } } end: @@ -1826,57 +1971,293 @@ end: } static -int condition_is_supported(struct lttng_condition *condition) +int handle_notification_thread_command_add_application( + struct notification_thread_handle *handle, + struct notification_thread_state *state, + int read_side_trigger_event_application_pipe, + enum lttng_error_code *_cmd_result) { - int ret; + int ret = 0; + enum lttng_error_code cmd_result = LTTNG_OK; + struct notification_event_trigger_source_element *element = NULL; - switch (lttng_condition_get_type(condition)) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - { - enum lttng_domain_type domain; + element = zmalloc(sizeof(*element)); + if (!element) { + cmd_result = LTTNG_ERR_NOMEM; + ret = -1; + goto end; + } - ret = lttng_condition_buffer_usage_get_domain_type(condition, - &domain); - if (ret) { - ret = -1; - goto end; - } + CDS_INIT_LIST_HEAD(&element->node); + element->fd = read_side_trigger_event_application_pipe; - if (domain != LTTNG_DOMAIN_KERNEL) { - ret = 1; - goto end; - } + pthread_mutex_lock(&handle->event_trigger_sources.lock); + cds_list_add(&element->node, &handle->event_trigger_sources.list); + pthread_mutex_unlock(&handle->event_trigger_sources.lock); - /* - * Older kernel tracers don't expose the API to monitor their - * buffers. Therefore, we reject triggers that require that - * mechanism to be available to be evaluated. - */ - ret = kernel_supports_ring_buffer_snapshot_sample_positions(); - break; - } - default: - ret = 1; + /* TODO: remove on failure to add to list? */ + + /* Adding the read side pipe to the event poll */ + ret = lttng_poll_add(&state->events, + read_side_trigger_event_application_pipe, + LPOLLIN | LPOLLERR); + + DBG3("[notification-thread] Adding application event source from fd: %d", read_side_trigger_event_application_pipe); + if (ret < 0) { + /* TODO: what should be the value of cmd_result??? */ + ERR("[notification-thread] Failed to add event source pipe fd to pollset"); + goto end; } + end: + *_cmd_result = cmd_result; return ret; } -/* Must be called with RCU read lock held. */ static -int bind_trigger_to_matching_session(const struct lttng_trigger *trigger, - struct notification_thread_state *state) +int handle_notification_thread_command_remove_application( + struct notification_thread_handle *handle, + struct notification_thread_state *state, + int read_side_trigger_event_application_pipe, + enum lttng_error_code *_cmd_result) { int ret = 0; - const struct lttng_condition *condition; - const char *session_name; - struct lttng_session_trigger_list *trigger_list; + enum lttng_error_code cmd_result = LTTNG_OK; - condition = lttng_trigger_get_const_condition(trigger); - switch (lttng_condition_get_type(condition)) { - case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: - case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: + /* TODO: missing a lock propably to revisit */ + struct notification_event_trigger_source_element *source_element, *tmp; + cds_list_for_each_entry_safe(source_element, tmp, + &handle->event_trigger_sources.list, node) { + if (source_element->fd != read_side_trigger_event_application_pipe) { + continue; + } + + DBG("[notification-thread] Removed event source from event source list"); + cds_list_del(&source_element->node); + free(source_element); + break; + } + + DBG3("[notification-thread] Removing application event source from fd: %d", read_side_trigger_event_application_pipe); + /* Removing the read side pipe to the event poll */ + ret = lttng_poll_del(&state->events, + read_side_trigger_event_application_pipe); + if (ret < 0) { + /* TODO: what should be the value of cmd_result??? */ + ERR("[notification-thread] Failed to remove event source pipe fd from pollset"); + goto end; + } + +end: + *_cmd_result = cmd_result; + return ret; +} + +static int handle_notification_thread_command_get_tokens( + struct notification_thread_handle *handle, + struct notification_thread_state *state, + struct lttng_triggers **triggers, + enum lttng_error_code *_cmd_result) +{ + int ret = 0, i = 0; + enum lttng_error_code cmd_result = LTTNG_OK; + struct cds_lfht_iter iter; + struct notification_trigger_tokens_ht_element *element; + struct lttng_triggers *local_triggers = NULL; + + local_triggers = lttng_triggers_create(); + if (!local_triggers) { + cmd_result = LTTNG_ERR_NOMEM; + goto end; + } + + rcu_read_lock(); + cds_lfht_for_each_entry ( + state->trigger_tokens_ht, &iter, element, node) { + ret = lttng_triggers_add(local_triggers, element->trigger); + if (ret < 0) { + cmd_result = LTTNG_ERR_FATAL; + ret = -1; + goto end; + } + + /* Ownership is shared with the lttng_triggers object */ + lttng_trigger_get(element->trigger); + + i++; + } + + /* Passing ownership up */ + *triggers = local_triggers; + local_triggers = NULL; + +end: + rcu_read_unlock(); + lttng_triggers_destroy(local_triggers); + *_cmd_result = cmd_result; + return ret; +} + +static +int handle_notification_thread_command_list_triggers( + struct notification_thread_handle *handle, + struct notification_thread_state *state, + uid_t uid, + gid_t gid, + struct lttng_triggers **triggers, + enum lttng_error_code *_cmd_result) +{ + int ret = 0, i = 0; + enum lttng_error_code cmd_result = LTTNG_OK; + struct cds_lfht_iter iter; + struct lttng_trigger_ht_element *trigger_ht_element; + struct lttng_triggers *local_triggers = NULL; + const struct lttng_credentials *creds; + + long scb, sca; + unsigned long count; + + rcu_read_lock(); + cds_lfht_count_nodes(state->triggers_ht, &scb, &count, &sca); + + /* TODO check downcasting */ + local_triggers = lttng_triggers_create(); + if (!local_triggers) { + cmd_result = LTTNG_ERR_NOMEM; + goto end; + } + + cds_lfht_for_each_entry (state->triggers_ht, &iter, + trigger_ht_element, node) { + /* Only return the trigger for which the requestion client have + * access. For now the root user can only list its own + * triggers. + * TODO: root user behavior + */ + creds = lttng_trigger_get_credentials(trigger_ht_element->trigger); + if ((uid != creds->uid) || (gid != creds->gid)) { + continue; + } + + ret = lttng_triggers_add(local_triggers, trigger_ht_element->trigger); + if (ret < 0) { + ret = -1; + goto end; + } + /* Ownership is shared with the lttng_triggers object */ + lttng_trigger_get(trigger_ht_element->trigger); + + i++; + } + + /* Passing ownership up */ + *triggers = local_triggers; + local_triggers = NULL; + +end: + rcu_read_unlock(); + lttng_triggers_destroy(local_triggers); + *_cmd_result = cmd_result; + return ret; +} + +static +int condition_is_supported(struct lttng_condition *condition) +{ + int ret; + + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + { + enum lttng_domain_type domain; + + ret = lttng_condition_buffer_usage_get_domain_type(condition, + &domain); + if (ret) { + ret = -1; + goto end; + } + + if (domain != LTTNG_DOMAIN_KERNEL) { + ret = 1; + goto end; + } + + /* + * Older kernel tracers don't expose the API to monitor their + * buffers. Therefore, we reject triggers that require that + * mechanism to be available to be evaluated. + */ + ret = kernel_supports_ring_buffer_snapshot_sample_positions(); + break; + } + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + { + /* TODO: + * Check for kernel support. + * Check for ust support ?? + */ + ret = 1; + break; + } + default: + ret = 1; + } +end: + return ret; +} + +static +int action_is_supported(struct lttng_action *action) +{ + int ret; + + switch (lttng_action_get_type(action)) { + case LTTNG_ACTION_TYPE_NOTIFY: + case LTTNG_ACTION_TYPE_START_SESSION: + case LTTNG_ACTION_TYPE_STOP_SESSION: + case LTTNG_ACTION_TYPE_ROTATE_SESSION: + case LTTNG_ACTION_TYPE_SNAPSHOT_SESSION: + { + /* TODO validate that this is true for kernel in regards to + * rotation and snapshot. Start stop is not a problem notify + * either. + */ + /* For now all type of actions are supported */ + ret = 1; + break; + } + case LTTNG_ACTION_TYPE_GROUP: + { + /* TODO: Iterate over all internal actions and validate that + * they are supported + */ + ret = 1; + break; + + } + default: + ret = 1; + } + + return ret; +} + +/* Must be called with RCU read lock held. */ +static +int bind_trigger_to_matching_session(struct lttng_trigger *trigger, + struct notification_thread_state *state) +{ + int ret = 0; + const struct lttng_condition *condition; + const char *session_name; + struct lttng_session_trigger_list *trigger_list; + + condition = lttng_trigger_get_const_condition(trigger); + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: { enum lttng_condition_status status; @@ -1911,7 +2292,7 @@ end: /* Must be called with RCU read lock held. */ static -int bind_trigger_to_matching_channels(const struct lttng_trigger *trigger, +int bind_trigger_to_matching_channels(struct lttng_trigger *trigger, struct notification_thread_state *state) { int ret = 0; @@ -1955,95 +2336,65 @@ end: return ret; } -/* - * FIXME A client's credentials are not checked when registering a trigger, nor - * are they stored alongside with the trigger. - * - * The effects of this are benign since: - * - The client will succeed in registering the trigger, as it is valid, - * - The trigger will, internally, be bound to the channel/session, - * - The notifications will not be sent since the client's credentials - * are checked against the channel at that moment. - * - * If this function returns a non-zero value, it means something is - * fundamentally broken and the whole subsystem/thread will be torn down. - * - * If a non-fatal error occurs, just set the cmd_result to the appropriate - * error code. - */ static -int handle_notification_thread_command_register_trigger( +bool trigger_name_taken(struct notification_thread_state *state, const char *name) +{ + struct cds_lfht_node *triggers_by_name_ht_node; + struct cds_lfht_iter iter; + /* TODO change hashing for trigger */ + cds_lfht_lookup(state->triggers_by_name_ht, + hash_key_str(name, lttng_ht_seed), + match_str, + name, + &iter); + triggers_by_name_ht_node = cds_lfht_iter_get_node(&iter); + if (triggers_by_name_ht_node) { + return true; + } else { + return false; + } + +} + +static +void generate_trigger_name(struct notification_thread_state *state, struct lttng_trigger *trigger, const char **name) +{ + /* Here the offset criteria guarantee an end. This will be a nice + * bikeshedding conversation. I would simply generate uuid and use them + * as trigger name. + */ + bool taken = false; + do { + lttng_trigger_generate_name(trigger, state->trigger_id.name_offset); + /* TODO error checking */ + lttng_trigger_get_name(trigger, name); + taken = trigger_name_taken(state, *name); + if (taken) { + state->trigger_id.name_offset++; + } + } while (taken || state->trigger_id.name_offset == UINT32_MAX); +} + +static int action_notify_register_trigger( struct notification_thread_state *state, - struct lttng_trigger *trigger, - enum lttng_error_code *cmd_result) + struct lttng_trigger *trigger) { + int ret = 0; struct lttng_condition *condition; struct notification_client *client; struct notification_client_list *client_list = NULL; - struct lttng_trigger_ht_element *trigger_ht_element = NULL; - struct notification_client_list_element *client_list_element, *tmp; - struct cds_lfht_node *node; struct cds_lfht_iter iter; - bool free_trigger = true; - - rcu_read_lock(); + struct notification_client_list_element *client_list_element, *tmp; condition = lttng_trigger_get_condition(trigger); assert(condition); - ret = condition_is_supported(condition); - if (ret < 0) { - goto error; - } else if (ret == 0) { - *cmd_result = LTTNG_ERR_NOT_SUPPORTED; - goto error; - } else { - /* Feature is supported, continue. */ - ret = 0; - } - - trigger_ht_element = zmalloc(sizeof(*trigger_ht_element)); - if (!trigger_ht_element) { - ret = -1; - goto error; - } - - /* Add trigger to the trigger_ht. */ - cds_lfht_node_init(&trigger_ht_element->node); - trigger_ht_element->trigger = trigger; - - node = cds_lfht_add_unique(state->triggers_ht, - lttng_condition_hash(condition), - match_condition, - condition, - &trigger_ht_element->node); - if (node != &trigger_ht_element->node) { - /* Not a fatal error, simply report it to the client. */ - *cmd_result = LTTNG_ERR_TRIGGER_EXISTS; - goto error_free_ht_element; - } - - /* - * Ownership of the trigger and of its wrapper was transfered to - * the triggers_ht. - */ - trigger_ht_element = NULL; - free_trigger = false; - - /* - * The rest only applies to triggers that have a "notify" action. - * It is not skipped as this is the only action type currently - * supported. - */ - client_list = zmalloc(sizeof(*client_list)); + client_list = notification_client_list_create(trigger); if (!client_list) { ret = -1; - goto error_free_ht_element; + goto end; } - cds_lfht_node_init(&client_list->notification_trigger_ht_node); - CDS_INIT_LIST_HEAD(&client_list->list); - client_list->trigger = trigger; /* Build a list of clients to which this new trigger applies. */ cds_lfht_for_each_entry(state->client_socket_ht, &iter, client, @@ -2055,23 +2406,19 @@ int handle_notification_thread_command_register_trigger( client_list_element = zmalloc(sizeof(*client_list_element)); if (!client_list_element) { ret = -1; - goto error_free_client_list; + goto error_put_client_list; } CDS_INIT_LIST_HEAD(&client_list_element->node); client_list_element->client = client; cds_list_add(&client_list_element->node, &client_list->list); } - cds_lfht_add(state->notification_trigger_clients_ht, - lttng_condition_hash(condition), - &client_list->notification_trigger_ht_node); - switch (get_condition_binding_object(condition)) { case LTTNG_OBJECT_TYPE_SESSION: /* Add the trigger to the list if it matches a known session. */ ret = bind_trigger_to_matching_session(trigger, state); if (ret) { - goto error_free_client_list; + goto error_put_client_list; } break; case LTTNG_OBJECT_TYPE_CHANNEL: @@ -2081,7 +2428,7 @@ int handle_notification_thread_command_register_trigger( */ ret = bind_trigger_to_matching_channels(trigger, state); if (ret) { - goto error_free_client_list; + goto error_put_client_list; } break; case LTTNG_OBJECT_TYPE_NONE: @@ -2089,7 +2436,7 @@ int handle_notification_thread_command_register_trigger( default: ERR("[notification-thread] Unknown object type on which to bind a newly registered trigger was encountered"); ret = -1; - goto error_free_client_list; + goto error_put_client_list; } /* @@ -2117,13 +2464,15 @@ int handle_notification_thread_command_register_trigger( * current state. Otherwise, the next evaluation cycle may only see * that the evaluations remain the same (true for samples n-1 and n) and * the client will never know that the condition has been met. + * + * No need to lock the list here as it has not been published yet. */ cds_list_for_each_entry_safe(client_list_element, tmp, &client_list->list, node) { ret = evaluate_condition_for_client(trigger, condition, client_list_element->client, state); if (ret) { - goto error_free_client_list; + goto error_put_client_list; } } @@ -2131,25 +2480,207 @@ int handle_notification_thread_command_register_trigger( * Client list ownership transferred to the * notification_trigger_clients_ht. */ + publish_notification_client_list(state, client_list); client_list = NULL; +error_put_client_list: + notification_client_list_put(client_list); +end: + return ret; +} - *cmd_result = LTTNG_OK; -error_free_client_list: - if (client_list) { - cds_list_for_each_entry_safe(client_list_element, tmp, - &client_list->list, node) { - free(client_list_element); +static bool action_is_notify(const struct lttng_action *action) +{ + /* TODO for action groups we need to iterate over all of them */ + enum lttng_action_type type = lttng_action_get_type_const(action); + bool ret = false; + enum lttng_action_status status; + const struct lttng_action *tmp; + unsigned int i, count; + + switch (type) { + case LTTNG_ACTION_TYPE_NOTIFY: + ret = true; + break; + case LTTNG_ACTION_TYPE_GROUP: + status = lttng_action_group_get_count(action, &count); + if (status != LTTNG_ACTION_STATUS_OK) { + assert(0); + } + for (i = 0; i < count; i++) { + tmp = lttng_action_group_get_at_index_const(action, i); + assert(tmp); + ret = action_is_notify(tmp); + if (ret) { + break; + } } - free(client_list); + break; + default: + ret = false; + break; } + + return ret; +} + +/* + * TODO: REVIEW THIS COMMENT. + * FIXME A client's credentials are not checked when registering a trigger, nor + * are they stored alongside with the trigger. + * + * The effects of this are benign since: + * - The client will succeed in registering the trigger, as it is valid, + * - The trigger will, internally, be bound to the channel/session, + * - The notifications will not be sent since the client's credentials + * are checked against the channel at that moment. + * + * If this function returns a non-zero value, it means something is + * fundamentally broken and the whole subsystem/thread will be torn down. + * + * If a non-fatal error occurs, just set the cmd_result to the appropriate + * error code. + */ +static +int handle_notification_thread_command_register_trigger( + struct notification_thread_state *state, + struct lttng_trigger *trigger, + enum lttng_error_code *cmd_result) +{ + int ret = 0; + int is_supported; + struct lttng_condition *condition; + struct lttng_action *action; + struct lttng_trigger_ht_element *trigger_ht_element = NULL; + struct notification_trigger_tokens_ht_element *trigger_tokens_ht_element = NULL; + struct cds_lfht_node *node; + const char* trigger_name; + bool free_trigger = true; + + assert(trigger->creds.set); + + rcu_read_lock(); + + /* Set the trigger's key */ + lttng_trigger_set_key(trigger, state->trigger_id.token_generator); + + if (lttng_trigger_get_name(trigger, &trigger_name) == + LTTNG_TRIGGER_STATUS_UNSET) { + generate_trigger_name(state, trigger, &trigger_name); + } else if (trigger_name_taken(state, trigger_name)) { + /* Not a fatal error */ + *cmd_result = LTTNG_ERR_TRIGGER_EXISTS; + ret = 0; + goto error; + } + + condition = lttng_trigger_get_condition(trigger); + assert(condition); + + action = lttng_trigger_get_action(trigger); + assert(action); + + is_supported = condition_is_supported(condition); + if (is_supported < 0) { + goto error; + } else if (is_supported == 0) { + ret = 0; + *cmd_result = LTTNG_ERR_NOT_SUPPORTED; + goto error; + } + + is_supported = action_is_supported(action); + if (is_supported < 0) { + goto error; + } else if (is_supported == 0) { + ret = 0; + *cmd_result = LTTNG_ERR_NOT_SUPPORTED; + goto error; + } + + trigger_ht_element = zmalloc(sizeof(*trigger_ht_element)); + if (!trigger_ht_element) { + ret = -1; + goto error; + } + + /* Add trigger to the trigger_ht. */ + cds_lfht_node_init(&trigger_ht_element->node); + cds_lfht_node_init(&trigger_ht_element->node_by_name); + trigger_ht_element->trigger = trigger; + + node = cds_lfht_add_unique(state->triggers_ht, + lttng_condition_hash(condition), + match_trigger, + trigger, + &trigger_ht_element->node); + if (node != &trigger_ht_element->node) { + /* Not a fatal error, simply report it to the client. */ + *cmd_result = LTTNG_ERR_TRIGGER_EXISTS; + goto error_free_ht_element; + } + + node = cds_lfht_add_unique(state->triggers_by_name_ht, + hash_key_str(trigger_name, lttng_ht_seed), match_str, + trigger_name, &trigger_ht_element->node_by_name); + if (node != &trigger_ht_element->node_by_name) { + /* This should never happen */ + /* Not a fatal error, simply report it to the client. */ + /* TODO remove from the trigger_ht */ + *cmd_result = LTTNG_ERR_TRIGGER_EXISTS; + goto error_free_ht_element; + } + + if (lttng_condition_get_type(condition) == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT) { + trigger_tokens_ht_element = zmalloc(sizeof(*trigger_tokens_ht_element)); + if (!trigger_tokens_ht_element) { + ret = -1; + goto error; + } + + /* Add trigger token to the trigger_tokens_ht. */ + cds_lfht_node_init(&trigger_tokens_ht_element->node); + trigger_tokens_ht_element->token = trigger->key.value; + trigger_tokens_ht_element->trigger = trigger; + + node = cds_lfht_add_unique(state->trigger_tokens_ht, + hash_key_u64(&trigger_tokens_ht_element->token, lttng_ht_seed), + match_trigger_token, + &trigger_tokens_ht_element->token, + &trigger_tokens_ht_element->node); + if (node != &trigger_tokens_ht_element->node) { + /* TODO: THIS IS A FATAL ERROR... should never happen */ + /* Not a fatal error, simply report it to the client. */ + *cmd_result = LTTNG_ERR_TRIGGER_EXISTS; + goto error_free_ht_element; + } + } + + /* + * Ownership of the trigger and of its wrapper was transfered to + * the triggers_ht. Same for token ht element if necessary. + */ + trigger_tokens_ht_element = NULL; + trigger_ht_element = NULL; + free_trigger = false; + + if (action_is_notify(action)) { + ret = action_notify_register_trigger(state, trigger); + if (ret < 0) { + /* TODO should cmd_result be set here? */ + ret = -1; + goto error_free_ht_element; + } + } + + /* Increment the trigger unique id generator */ + state->trigger_id.token_generator++; + *cmd_result = LTTNG_OK; + error_free_ht_element: free(trigger_ht_element); + free(trigger_tokens_ht_element); error: if (free_trigger) { - struct lttng_action *action = lttng_trigger_get_action(trigger); - - lttng_condition_destroy(condition); - lttng_action_destroy(action); lttng_trigger_destroy(trigger); } rcu_read_unlock(); @@ -2157,16 +2688,16 @@ error: } static -void free_notification_client_list_rcu(struct rcu_head *node) +void free_lttng_trigger_ht_element_rcu(struct rcu_head *node) { - free(caa_container_of(node, struct notification_client_list, + free(caa_container_of(node, struct lttng_trigger_ht_element, rcu_node)); } static -void free_lttng_trigger_ht_element_rcu(struct rcu_head *node) +void free_notification_trigger_tokens_ht_element_rcu(struct rcu_head *node) { - free(caa_container_of(node, struct lttng_trigger_ht_element, + free(caa_container_of(node, struct notification_trigger_tokens_ht_element, rcu_node)); } @@ -2180,19 +2711,22 @@ int handle_notification_thread_command_unregister_trigger( struct cds_lfht_node *triggers_ht_node; struct lttng_channel_trigger_list *trigger_list; struct notification_client_list *client_list; - struct notification_client_list_element *client_list_element, *tmp; struct lttng_trigger_ht_element *trigger_ht_element = NULL; struct lttng_condition *condition = lttng_trigger_get_condition( trigger); - struct lttng_action *action; + struct lttng_action *action = lttng_trigger_get_action(trigger); enum lttng_error_code cmd_reply; rcu_read_lock(); + /* TODO change hashing for trigger */ + /* TODO Disabling for the root user is not complete, for now the root + * user cannot disable the trigger from another user. + */ cds_lfht_lookup(state->triggers_ht, lttng_condition_hash(condition), - match_condition, - condition, + match_trigger, + trigger, &iter); triggers_ht_node = cds_lfht_iter_get_node(&iter); if (!triggers_ht_node) { @@ -2209,13 +2743,7 @@ int handle_notification_thread_command_unregister_trigger( cds_list_for_each_entry_safe(trigger_element, tmp, &trigger_list->list, node) { - const struct lttng_condition *current_condition = - lttng_trigger_get_const_condition( - trigger_element->trigger); - - assert(current_condition); - if (!lttng_condition_is_equal(condition, - current_condition)) { + if (!lttng_trigger_is_equal(trigger, trigger_element->trigger)) { continue; } @@ -2226,30 +2754,45 @@ int handle_notification_thread_command_unregister_trigger( } } - /* - * Remove and release the client list from - * notification_trigger_clients_ht. - */ - client_list = get_client_list_from_condition(state, condition); - assert(client_list); + if (lttng_condition_get_type(condition) == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT) { + struct notification_trigger_tokens_ht_element *trigger_tokens_ht_element; + cds_lfht_for_each_entry(state->trigger_tokens_ht, &iter, trigger_tokens_ht_element, + node) { + if (!lttng_trigger_is_equal(trigger, trigger_tokens_ht_element->trigger)) { + continue; + } + + /* TODO talk to all app and remove it */ + DBG("[notification-thread] Removed trigger from tokens_ht"); + cds_lfht_del(state->trigger_tokens_ht, + &trigger_tokens_ht_element->node); + call_rcu(&trigger_tokens_ht_element->rcu_node, free_notification_trigger_tokens_ht_element_rcu); + + break; + } + } - cds_list_for_each_entry_safe(client_list_element, tmp, - &client_list->list, node) { - free(client_list_element); + if (action_is_notify(action)) { + /* + * Remove and release the client list from + * notification_trigger_clients_ht. + */ + client_list = get_client_list_from_condition(state, condition); + assert(client_list); + + /* Put new reference and the hashtable's reference. */ + notification_client_list_put(client_list); + notification_client_list_put(client_list); + client_list = NULL; } - cds_lfht_del(state->notification_trigger_clients_ht, - &client_list->notification_trigger_ht_node); - call_rcu(&client_list->rcu_node, free_notification_client_list_rcu); /* Remove trigger from triggers_ht. */ trigger_ht_element = caa_container_of(triggers_ht_node, struct lttng_trigger_ht_element, node); + cds_lfht_del(state->triggers_by_name_ht, &trigger_ht_element->node_by_name); cds_lfht_del(state->triggers_ht, triggers_ht_node); - condition = lttng_trigger_get_condition(trigger_ht_element->trigger); - lttng_condition_destroy(condition); - action = lttng_trigger_get_action(trigger_ht_element->trigger); - lttng_action_destroy(action); + /* Release the ownership of the trigger */ lttng_trigger_destroy(trigger_ht_element->trigger); call_rcu(&trigger_ht_element->rcu_node, free_lttng_trigger_ht_element_rcu); end: @@ -2279,6 +2822,8 @@ int handle_notification_thread_command( pthread_mutex_lock(&handle->cmd_queue.lock); cmd = cds_list_first_entry(&handle->cmd_queue.list, struct notification_thread_command, cmd_list_node); + cds_list_del(&cmd->cmd_list_node); + pthread_mutex_unlock(&handle->cmd_queue.lock); switch (cmd->type) { case NOTIFICATION_COMMAND_TYPE_REGISTER_TRIGGER: DBG("[notification-thread] Received register trigger command"); @@ -2327,11 +2872,78 @@ int handle_notification_thread_command( cmd->parameters.session_rotation.location, &cmd->reply_code); break; + case NOTIFICATION_COMMAND_TYPE_ADD_APPLICATION: + ret = handle_notification_thread_command_add_application( + handle, + state, + cmd->parameters.application.read_side_trigger_event_application_pipe, + &cmd->reply_code); + break; + case NOTIFICATION_COMMAND_TYPE_REMOVE_APPLICATION: + ret = handle_notification_thread_command_remove_application( + handle, + state, + cmd->parameters.application.read_side_trigger_event_application_pipe, + &cmd->reply_code); + break; + case NOTIFICATION_COMMAND_TYPE_GET_TOKENS: + { + struct lttng_triggers *triggers = NULL; + ret = handle_notification_thread_command_get_tokens( + handle, state, &triggers, &cmd->reply_code); + cmd->reply.get_tokens.triggers = triggers; + ret = 0; + break; + } + case NOTIFICATION_COMMAND_TYPE_LIST_TRIGGERS: + { + struct lttng_triggers *triggers = NULL; + ret = handle_notification_thread_command_list_triggers( + handle, + state, + cmd->parameters.list_triggers.uid, + cmd->parameters.list_triggers.gid, + &triggers, + &cmd->reply_code); + cmd->reply.list_triggers.triggers = triggers; + ret = 0; + break; + } case NOTIFICATION_COMMAND_TYPE_QUIT: DBG("[notification-thread] Received quit command"); cmd->reply_code = LTTNG_OK; ret = 1; goto end; + case NOTIFICATION_COMMAND_TYPE_CLIENT_COMMUNICATION_UPDATE: + { + const enum client_transmission_status client_status = + cmd->parameters.client_communication_update + .status; + const notification_client_id client_id = + cmd->parameters.client_communication_update.id; + struct notification_client *client; + + rcu_read_lock(); + client = get_client_from_id(client_id, state); + + if (!client) { + /* + * Client error was probably already picked-up by the + * notification thread or it has disconnected + * gracefully while this command was queued. + */ + DBG("Failed to find notification client to update communication status, client id = %" PRIu64, + client_id); + ret = 0; + } else { + pthread_mutex_lock(&client->lock); + ret = client_handle_transmission_status( + client, client_status, state); + pthread_mutex_unlock(&client->lock); + } + rcu_read_unlock(); + break; + } default: ERR("[notification-thread] Unknown internal command received"); goto error_unlock; @@ -2341,26 +2953,22 @@ int handle_notification_thread_command( goto error_unlock; } end: - cds_list_del(&cmd->cmd_list_node); - lttng_waiter_wake_up(&cmd->reply_waiter); - pthread_mutex_unlock(&handle->cmd_queue.lock); + if (cmd->is_async) { + free(cmd); + cmd = NULL; + } else { + lttng_waiter_wake_up(&cmd->reply_waiter); + } return ret; error_unlock: /* Wake-up and return a fatal error to the calling thread. */ lttng_waiter_wake_up(&cmd->reply_waiter); - pthread_mutex_unlock(&handle->cmd_queue.lock); cmd->reply_code = LTTNG_ERR_FATAL; error: /* Indicate a fatal error to the caller. */ return -1; } -static -unsigned long hash_client_socket(int socket) -{ - return hash_key_ulong((void *) (unsigned long) socket, lttng_ht_seed); -} - static int socket_set_non_blocking(int socket) { @@ -2384,11 +2992,14 @@ end: return ret; } +/* Client lock must be acquired by caller. */ static int client_reset_inbound_state(struct notification_client *client) { int ret; + ASSERT_LOCKED(client->lock); + ret = lttng_dynamic_buffer_set_size( &client->communication.inbound.buffer, 0); assert(!ret); @@ -2419,11 +3030,16 @@ int handle_notification_thread_client_connect( ret = -1; goto error; } + pthread_mutex_init(&client->lock, NULL); + client->id = state->next_notification_client_id++; CDS_INIT_LIST_HEAD(&client->condition_list); lttng_dynamic_buffer_init(&client->communication.inbound.buffer); lttng_dynamic_buffer_init(&client->communication.outbound.buffer); client->communication.inbound.expect_creds = true; + + pthread_mutex_lock(&client->lock); ret = client_reset_inbound_state(client); + pthread_mutex_unlock(&client->lock); if (ret) { ERR("[notification-thread] Failed to reset client communication's inbound state"); ret = 0; @@ -2467,6 +3083,9 @@ int handle_notification_thread_client_connect( cds_lfht_add(state->client_socket_ht, hash_client_socket(client->socket), &client->client_socket_ht_node); + cds_lfht_add(state->client_id_ht, + hash_client_id(client->id), + &client->client_id_ht_node); rcu_read_unlock(); return ret; @@ -2475,9 +3094,44 @@ error: return ret; } -int handle_notification_thread_client_disconnect( - int client_socket, +/* RCU read-lock must be held by the caller. */ +/* Client lock must be held by the caller */ +static +int notification_thread_client_disconnect( + struct notification_client *client, struct notification_thread_state *state) +{ + int ret; + struct lttng_condition_list_element *condition_list_element, *tmp; + + /* Acquire the client lock to disable its communication atomically. */ + client->communication.active = false; + ret = lttng_poll_del(&state->events, client->socket); + if (ret) { + ERR("[notification-thread] Failed to remove client socket %d from poll set", + client->socket); + } + + cds_lfht_del(state->client_socket_ht, &client->client_socket_ht_node); + cds_lfht_del(state->client_id_ht, &client->client_id_ht_node); + + /* Release all conditions to which the client was subscribed. */ + cds_list_for_each_entry_safe(condition_list_element, tmp, + &client->condition_list, node) { + (void) notification_thread_client_unsubscribe(client, + condition_list_element->condition, state, NULL); + } + + /* + * Client no longer accessible to other threads (through the + * client lists). + */ + notification_client_destroy(client, state); + return ret; +} + +int handle_notification_thread_client_disconnect( + int client_socket, struct notification_thread_state *state) { int ret = 0; struct notification_client *client; @@ -2494,13 +3148,9 @@ int handle_notification_thread_client_disconnect( goto end; } - ret = lttng_poll_del(&state->events, client_socket); - if (ret) { - ERR("[notification-thread] Failed to remove client socket from poll set"); - } - cds_lfht_del(state->client_socket_ht, - &client->client_socket_ht_node); - notification_client_destroy(client, state); + pthread_mutex_lock(&client->lock); + ret = notification_thread_client_disconnect(client, state); + pthread_mutex_unlock(&client->lock); end: rcu_read_unlock(); return ret; @@ -2516,11 +3166,13 @@ int handle_notification_thread_client_disconnect_all( rcu_read_lock(); DBG("[notification-thread] Closing all client connections"); cds_lfht_for_each_entry(state->client_socket_ht, &iter, client, - client_socket_ht_node) { + client_socket_ht_node) { int ret; - ret = handle_notification_thread_client_disconnect( - client->socket, state); + pthread_mutex_lock(&client->lock); + ret = notification_thread_client_disconnect( + client, state); + pthread_mutex_unlock(&client->lock); if (ret) { error_encoutered = true; } @@ -2550,11 +3202,68 @@ int handle_notification_thread_trigger_unregister_all( } static -int client_flush_outgoing_queue(struct notification_client *client, +int client_handle_transmission_status( + struct notification_client *client, + enum client_transmission_status transmission_status, struct notification_thread_state *state) +{ + int ret = 0; + + ASSERT_LOCKED(client->lock); + + switch (transmission_status) { + case CLIENT_TRANSMISSION_STATUS_COMPLETE: + ret = lttng_poll_mod(&state->events, client->socket, + CLIENT_POLL_MASK_IN); + if (ret) { + goto end; + } + + client->communication.outbound.queued_command_reply = false; + client->communication.outbound.dropped_notification = false; + break; + case CLIENT_TRANSMISSION_STATUS_QUEUED: + /* + * We want to be notified whenever there is buffer space + * available to send the rest of the payload. + */ + ret = lttng_poll_mod(&state->events, client->socket, + CLIENT_POLL_MASK_IN_OUT); + if (ret) { + goto end; + } + break; + case CLIENT_TRANSMISSION_STATUS_FAIL: + ret = notification_thread_client_disconnect(client, state); + if (ret) { + goto end; + } + break; + case CLIENT_TRANSMISSION_STATUS_ERROR: + ret = -1; + goto end; + default: + abort(); + } +end: + return ret; +} + +/* Client lock must be acquired by caller. */ +static +enum client_transmission_status client_flush_outgoing_queue( + struct notification_client *client) { ssize_t ret; size_t to_send_count; + enum client_transmission_status status; + + ASSERT_LOCKED(client->lock); + + if (!client->communication.active) { + status = CLIENT_TRANSMISSION_STATUS_FAIL; + goto end; + } assert(client->communication.outbound.buffer.size != 0); to_send_count = client->communication.outbound.buffer.size; @@ -2580,25 +3289,12 @@ int client_flush_outgoing_queue(struct notification_client *client, if (ret) { goto error; } - - /* - * We want to be notified whenever there is buffer space - * available to send the rest of the payload. - */ - ret = lttng_poll_mod(&state->events, client->socket, - CLIENT_POLL_MASK_IN_OUT); - if (ret) { - goto error; - } + status = CLIENT_TRANSMISSION_STATUS_QUEUED; } else if (ret < 0) { /* Generic error, disconnect the client. */ - ERR("[notification-thread] Failed to send flush outgoing queue, disconnecting client (socket fd = %i)", + PERROR("[notification-thread] Failed to flush outgoing queue, disconnecting client (socket fd = %i)", client->socket); - ret = handle_notification_thread_client_disconnect( - client->socket, state); - if (ret) { - goto error; - } + status = CLIENT_TRANSMISSION_STATUS_FAIL; } else { /* No error and flushed the queue completely. */ ret = lttng_dynamic_buffer_set_size( @@ -2606,21 +3302,15 @@ int client_flush_outgoing_queue(struct notification_client *client, if (ret) { goto error; } - ret = lttng_poll_mod(&state->events, client->socket, - CLIENT_POLL_MASK_IN); - if (ret) { - goto error; - } - - client->communication.outbound.queued_command_reply = false; - client->communication.outbound.dropped_notification = false; + status = CLIENT_TRANSMISSION_STATUS_COMPLETE; } - - return 0; +end: + return status; error: - return -1; + return CLIENT_TRANSMISSION_STATUS_ERROR; } +/* Client lock must be acquired by caller. */ static int client_send_command_reply(struct notification_client *client, struct notification_thread_state *state, @@ -2635,37 +3325,233 @@ int client_send_command_reply(struct notification_client *client, .size = sizeof(reply), }; char buffer[sizeof(msg) + sizeof(reply)]; + enum client_transmission_status transmission_status; + + ASSERT_LOCKED(client->lock); + + if (client->communication.outbound.queued_command_reply) { + /* Protocol error. */ + goto error; + } + + memcpy(buffer, &msg, sizeof(msg)); + memcpy(buffer + sizeof(msg), &reply, sizeof(reply)); + DBG("[notification-thread] Send command reply (%i)", (int) status); + + /* Enqueue buffer to outgoing queue and flush it. */ + ret = lttng_dynamic_buffer_append( + &client->communication.outbound.buffer, + buffer, sizeof(buffer)); + if (ret) { + goto error; + } + + transmission_status = client_flush_outgoing_queue(client); + ret = client_handle_transmission_status( + client, transmission_status, state); + if (ret) { + goto error; + } + + if (client->communication.outbound.buffer.size != 0) { + /* Queue could not be emptied. */ + client->communication.outbound.queued_command_reply = true; + } + + return 0; +error: + return -1; +} + +static +int client_handle_message_unknown(struct notification_client *client, + struct notification_thread_state *state) +{ + int ret; + + pthread_mutex_lock(&client->lock); + + /* + * Receiving message header. The function will be called again + * once the rest of the message as been received and can be + * interpreted. + */ + const struct lttng_notification_channel_message *msg; + + assert(sizeof(*msg) == client->communication.inbound.buffer.size); + msg = (const struct lttng_notification_channel_message *) + client->communication.inbound.buffer.data; + + if (msg->size == 0 || + msg->size > DEFAULT_MAX_NOTIFICATION_CLIENT_MESSAGE_PAYLOAD_SIZE) { + ERR("[notification-thread] Invalid notification channel message: length = %u", + msg->size); + ret = -1; + goto end; + } + + switch (msg->type) { + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE: + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNSUBSCRIBE: + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE: + break; + default: + ret = -1; + ERR("[notification-thread] Invalid notification channel message: unexpected message type"); + goto end; + } + + client->communication.inbound.bytes_to_receive = msg->size; + client->communication.inbound.msg_type = + (enum lttng_notification_channel_message_type) msg->type; + ret = lttng_dynamic_buffer_set_size( + &client->communication.inbound.buffer, msg->size); +end: + pthread_mutex_unlock(&client->lock); + return ret; +} + +static +int client_handle_message_handshake(struct notification_client *client, + struct notification_thread_state *state) +{ + int ret; + struct lttng_notification_channel_command_handshake *handshake_client; + const struct lttng_notification_channel_command_handshake handshake_reply = { + .major = LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR, + .minor = LTTNG_NOTIFICATION_CHANNEL_VERSION_MINOR, + }; + const struct lttng_notification_channel_message msg_header = { + .type = LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE, + .size = sizeof(handshake_reply), + }; + enum lttng_notification_channel_status status = + LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; + char send_buffer[sizeof(msg_header) + sizeof(handshake_reply)]; + enum client_transmission_status transmission_status; + + pthread_mutex_lock(&client->lock); + + memcpy(send_buffer, &msg_header, sizeof(msg_header)); + memcpy(send_buffer + sizeof(msg_header), &handshake_reply, + sizeof(handshake_reply)); + + handshake_client = + (struct lttng_notification_channel_command_handshake *) + client->communication.inbound.buffer + .data; + client->major = handshake_client->major; + client->minor = handshake_client->minor; + if (!client->communication.inbound.creds_received) { + ERR("[notification-thread] No credentials received from client"); + ret = -1; + goto end; + } + + client->uid = LTTNG_SOCK_GET_UID_CRED( + &client->communication.inbound.creds); + client->gid = LTTNG_SOCK_GET_GID_CRED( + &client->communication.inbound.creds); + DBG("[notification-thread] Received handshake from client (uid = %u, gid = %u) with version %i.%i", + client->uid, client->gid, (int) client->major, + (int) client->minor); + + if (handshake_client->major != + LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR) { + status = LTTNG_NOTIFICATION_CHANNEL_STATUS_UNSUPPORTED_VERSION; + } + + ret = lttng_dynamic_buffer_append( + &client->communication.outbound.buffer, send_buffer, + sizeof(send_buffer)); + if (ret) { + ERR("[notification-thread] Failed to send protocol version to notification channel client"); + goto end; + } + + client->validated = true; + client->communication.active = true; + + transmission_status = client_flush_outgoing_queue(client); + ret = client_handle_transmission_status( + client, transmission_status, state); + if (ret) { + goto end; + } + + ret = client_send_command_reply(client, state, status); + if (ret) { + ERR("[notification-thread] Failed to send reply to notification channel client"); + goto end; + } + + /* Set reception state to receive the next message header. */ + ret = client_reset_inbound_state(client); + if (ret) { + ERR("[notification-thread] Failed to reset client communication's inbound state"); + goto end; + } + +end: + pthread_mutex_unlock(&client->lock); + return ret; +} + +static +int client_handle_message_subscription( + struct notification_client *client, + enum lttng_notification_channel_message_type msg_type, + struct notification_thread_state *state) +{ + int ret; + struct lttng_condition *condition; + enum lttng_notification_channel_status status = + LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; + const struct lttng_buffer_view condition_view = + lttng_buffer_view_from_dynamic_buffer( + &client->communication.inbound.buffer, + 0, -1); + size_t expected_condition_size; - if (client->communication.outbound.queued_command_reply) { - /* Protocol error. */ - goto error; - } + pthread_mutex_lock(&client->lock); + expected_condition_size = client->communication.inbound.buffer.size; + pthread_mutex_unlock(&client->lock); - memcpy(buffer, &msg, sizeof(msg)); - memcpy(buffer + sizeof(msg), &reply, sizeof(reply)); - DBG("[notification-thread] Send command reply (%i)", (int) status); + ret = lttng_condition_create_from_buffer(&condition_view, &condition); + if (ret != expected_condition_size) { + ERR("[notification-thread] Malformed condition received from client"); + goto end; + } - /* Enqueue buffer to outgoing queue and flush it. */ - ret = lttng_dynamic_buffer_append( - &client->communication.outbound.buffer, - buffer, sizeof(buffer)); + if (msg_type == LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE) { + ret = notification_thread_client_subscribe( + client, condition, state, &status); + } else { + ret = notification_thread_client_unsubscribe( + client, condition, state, &status); + } if (ret) { - goto error; + goto end; } - ret = client_flush_outgoing_queue(client, state); + pthread_mutex_lock(&client->lock); + ret = client_send_command_reply(client, state, status); if (ret) { - goto error; + ERR("[notification-thread] Failed to send reply to notification channel client"); + goto end_unlock; } - if (client->communication.outbound.buffer.size != 0) { - /* Queue could not be emptied. */ - client->communication.outbound.queued_command_reply = true; + /* Set reception state to receive the next message header. */ + ret = client_reset_inbound_state(client); + if (ret) { + ERR("[notification-thread] Failed to reset client communication's inbound state"); + goto end_unlock; } - return 0; -error: - return -1; +end_unlock: + pthread_mutex_unlock(&client->lock); +end: + return ret; } static @@ -2687,158 +3573,19 @@ int client_dispatch_message(struct notification_client *client, switch (client->communication.inbound.msg_type) { case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNKNOWN: { - /* - * Receiving message header. The function will be called again - * once the rest of the message as been received and can be - * interpreted. - */ - const struct lttng_notification_channel_message *msg; - - assert(sizeof(*msg) == - client->communication.inbound.buffer.size); - msg = (const struct lttng_notification_channel_message *) - client->communication.inbound.buffer.data; - - if (msg->size == 0 || msg->size > DEFAULT_MAX_NOTIFICATION_CLIENT_MESSAGE_PAYLOAD_SIZE) { - ERR("[notification-thread] Invalid notification channel message: length = %u", msg->size); - ret = -1; - goto end; - } - - switch (msg->type) { - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE: - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNSUBSCRIBE: - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE: - break; - default: - ret = -1; - ERR("[notification-thread] Invalid notification channel message: unexpected message type"); - goto end; - } - - client->communication.inbound.bytes_to_receive = msg->size; - client->communication.inbound.msg_type = - (enum lttng_notification_channel_message_type) msg->type; - ret = lttng_dynamic_buffer_set_size( - &client->communication.inbound.buffer, msg->size); - if (ret) { - goto end; - } + ret = client_handle_message_unknown(client, state); break; } case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE: { - struct lttng_notification_channel_command_handshake *handshake_client; - struct lttng_notification_channel_command_handshake handshake_reply = { - .major = LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR, - .minor = LTTNG_NOTIFICATION_CHANNEL_VERSION_MINOR, - }; - struct lttng_notification_channel_message msg_header = { - .type = LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE, - .size = sizeof(handshake_reply), - }; - enum lttng_notification_channel_status status = - LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; - char send_buffer[sizeof(msg_header) + sizeof(handshake_reply)]; - - memcpy(send_buffer, &msg_header, sizeof(msg_header)); - memcpy(send_buffer + sizeof(msg_header), &handshake_reply, - sizeof(handshake_reply)); - - handshake_client = - (struct lttng_notification_channel_command_handshake *) - client->communication.inbound.buffer.data; - client->major = handshake_client->major; - client->minor = handshake_client->minor; - if (!client->communication.inbound.creds_received) { - ERR("[notification-thread] No credentials received from client"); - ret = -1; - goto end; - } - - client->uid = LTTNG_SOCK_GET_UID_CRED( - &client->communication.inbound.creds); - client->gid = LTTNG_SOCK_GET_GID_CRED( - &client->communication.inbound.creds); - DBG("[notification-thread] Received handshake from client (uid = %u, gid = %u) with version %i.%i", - client->uid, client->gid, (int) client->major, - (int) client->minor); - - if (handshake_client->major != LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR) { - status = LTTNG_NOTIFICATION_CHANNEL_STATUS_UNSUPPORTED_VERSION; - } - - ret = lttng_dynamic_buffer_append(&client->communication.outbound.buffer, - send_buffer, sizeof(send_buffer)); - if (ret) { - ERR("[notification-thread] Failed to send protocol version to notification channel client"); - goto end; - } - - ret = client_flush_outgoing_queue(client, state); - if (ret) { - goto end; - } - - ret = client_send_command_reply(client, state, status); - if (ret) { - ERR("[notification-thread] Failed to send reply to notification channel client"); - goto end; - } - - /* Set reception state to receive the next message header. */ - ret = client_reset_inbound_state(client); - if (ret) { - ERR("[notification-thread] Failed to reset client communication's inbound state"); - goto end; - } - client->validated = true; + ret = client_handle_message_handshake(client, state); break; } case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE: case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNSUBSCRIBE: { - struct lttng_condition *condition; - enum lttng_notification_channel_status status = - LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; - const struct lttng_buffer_view condition_view = - lttng_buffer_view_from_dynamic_buffer( - &client->communication.inbound.buffer, - 0, -1); - size_t expected_condition_size = - client->communication.inbound.buffer.size; - - ret = lttng_condition_create_from_buffer(&condition_view, - &condition); - if (ret != expected_condition_size) { - ERR("[notification-thread] Malformed condition received from client"); - goto end; - } - - if (client->communication.inbound.msg_type == - LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE) { - ret = notification_thread_client_subscribe(client, - condition, state, &status); - } else { - ret = notification_thread_client_unsubscribe(client, - condition, state, &status); - } - if (ret) { - goto end; - } - - ret = client_send_command_reply(client, state, status); - if (ret) { - ERR("[notification-thread] Failed to send reply to notification channel client"); - goto end; - } - - /* Set reception state to receive the next message header. */ - ret = client_reset_inbound_state(client); - if (ret) { - ERR("[notification-thread] Failed to reset client communication's inbound state"); - goto end; - } + ret = client_handle_message_subscription(client, + client->communication.inbound.msg_type, state); break; } default: @@ -2856,6 +3603,7 @@ int handle_notification_thread_client_in( struct notification_client *client; ssize_t recv_ret; size_t offset; + bool message_is_complete = false; client = get_client_from_socket(socket, state); if (!client) { @@ -2864,6 +3612,7 @@ int handle_notification_thread_client_in( goto end; } + pthread_mutex_lock(&client->lock); offset = client->communication.inbound.buffer.size - client->communication.inbound.bytes_to_receive; if (client->communication.inbound.expect_creds) { @@ -2880,12 +3629,17 @@ int handle_notification_thread_client_in( client->communication.inbound.buffer.data + offset, client->communication.inbound.bytes_to_receive); } + if (recv_ret >= 0) { + client->communication.inbound.bytes_to_receive -= recv_ret; + message_is_complete = client->communication.inbound + .bytes_to_receive == 0; + } + pthread_mutex_unlock(&client->lock); if (recv_ret < 0) { goto error_disconnect_client; } - client->communication.inbound.bytes_to_receive -= recv_ret; - if (client->communication.inbound.bytes_to_receive == 0) { + if (message_is_complete) { ret = client_dispatch_message(client, state); if (ret) { /* @@ -2894,13 +3648,13 @@ int handle_notification_thread_client_in( */ goto error_disconnect_client; } - } else { - goto end; } end: return ret; error_disconnect_client: - ret = handle_notification_thread_client_disconnect(socket, state); + pthread_mutex_lock(&client->lock); + ret = notification_thread_client_disconnect(client, state); + pthread_mutex_unlock(&client->lock); return ret; } @@ -2910,6 +3664,7 @@ int handle_notification_thread_client_out( { int ret; struct notification_client *client; + enum client_transmission_status transmission_status; client = get_client_from_socket(socket, state); if (!client) { @@ -2918,7 +3673,11 @@ int handle_notification_thread_client_out( goto end; } - ret = client_flush_outgoing_queue(client, state); + pthread_mutex_lock(&client->lock); + transmission_status = client_flush_outgoing_queue(client); + ret = client_handle_transmission_status( + client, transmission_status, state); + pthread_mutex_unlock(&client->lock); if (ret) { goto end; } @@ -3089,33 +3848,77 @@ end: } static -int client_enqueue_dropped_notification(struct notification_client *client, - struct notification_thread_state *state) +int client_notification_overflow(struct notification_client *client) { - int ret; - struct lttng_notification_channel_message msg = { + int ret = 0; + const struct lttng_notification_channel_message msg = { .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION_DROPPED, - .size = 0, }; + ASSERT_LOCKED(client->lock); + + DBG("Dropping notification addressed to client (socket fd = %i)", + client->socket); + if (client->communication.outbound.dropped_notification) { + /* + * The client already has a "notification dropped" message + * in its outgoing queue. Nothing to do since all + * of those messages are coalesced. + */ + goto end; + } + + client->communication.outbound.dropped_notification = true; ret = lttng_dynamic_buffer_append( &client->communication.outbound.buffer, &msg, sizeof(msg)); + if (ret) { + PERROR("Failed to enqueue \"dropped notification\" message in client's (socket fd = %i) outgoing queue", + client->socket); + } +end: return ret; } +static int client_handle_transmission_status_wrapper( + struct notification_client *client, + enum client_transmission_status status, + void *user_data) +{ + return client_handle_transmission_status(client, status, + (struct notification_thread_state *) user_data); +} + static int send_evaluation_to_clients(const struct lttng_trigger *trigger, const struct lttng_evaluation *evaluation, struct notification_client_list* client_list, struct notification_thread_state *state, - uid_t channel_uid, gid_t channel_gid) + uid_t object_uid, gid_t object_gid) +{ + return notification_client_list_send_evaluation(client_list, + lttng_trigger_get_const_condition(trigger), evaluation, + lttng_trigger_get_credentials(trigger), + &(struct lttng_credentials){ + .uid = object_uid, .gid = object_gid}, + client_handle_transmission_status_wrapper, state); +} + +LTTNG_HIDDEN +int notification_client_list_send_evaluation( + struct notification_client_list *client_list, + const struct lttng_condition *condition, + const struct lttng_evaluation *evaluation, + const struct lttng_credentials *trigger_creds, + const struct lttng_credentials *source_object_creds, + report_client_transmission_result_cb client_report, + void *user_data) { int ret = 0; struct lttng_dynamic_buffer msg_buffer; struct notification_client_list_element *client_list_element, *tmp; const struct lttng_notification notification = { - .condition = (struct lttng_condition *) lttng_trigger_get_const_condition(trigger), + .condition = (struct lttng_condition *) condition, .evaluation = (struct lttng_evaluation *) evaluation, }; struct lttng_notification_channel_message msg_header = { @@ -3141,16 +3944,40 @@ int send_evaluation_to_clients(const struct lttng_trigger *trigger, ((struct lttng_notification_channel_message * ) msg_buffer.data)->size = (uint32_t) (msg_buffer.size - sizeof(msg_header)); + pthread_mutex_lock(&client_list->lock); cds_list_for_each_entry_safe(client_list_element, tmp, &client_list->list, node) { + enum client_transmission_status transmission_status; struct notification_client *client = client_list_element->client; - if (client->uid != channel_uid && client->gid != channel_gid && - client->uid != 0) { - /* Client is not allowed to monitor this channel. */ - DBG("[notification-thread] Skipping client at it does not have the permission to receive notification for this channel"); - continue; + ret = 0; + pthread_mutex_lock(&client->lock); + if (source_object_creds) { + if (client->uid != source_object_creds->uid && + client->gid != source_object_creds->gid && + client->uid != 0) { + /* + * Client is not allowed to monitor this + * object. + */ + DBG("[notification-thread] Skipping client at it does not have the object permission to receive notification for this trigger"); + goto unlock_client; + } + } + + /* TODO: what is the behavior for root client on non root + * trigger? Since multiple triggers (different user) can have the same condition + * but with different action group that can have each a notify. + * Does the root client receive multiple notification for all + * those triggers with the same condition or only notification + * for triggers the root user configured? + * For now we do the later. All users including the root user + * can only receive notification from trigger it registered. + */ + if (client->uid != trigger_creds->uid && client->gid != trigger_creds->gid) { + DBG("[notification-thread] Skipping client at it does not have the permission to receive notification for this trigger"); + goto unlock_client; } DBG("[notification-thread] Sending notification to client (fd = %i, %zu bytes)", @@ -3163,37 +3990,271 @@ int send_evaluation_to_clients(const struct lttng_trigger *trigger, * notification since the socket spilled-over to the * queue. */ - DBG("[notification-thread] Dropping notification addressed to client (socket fd = %i)", - client->socket); - if (!client->communication.outbound.dropped_notification) { - client->communication.outbound.dropped_notification = true; - ret = client_enqueue_dropped_notification( - client, state); - if (ret) { - goto end; - } + ret = client_notification_overflow(client); + if (ret) { + goto unlock_client; } - continue; } ret = lttng_dynamic_buffer_append_buffer( &client->communication.outbound.buffer, &msg_buffer); if (ret) { - goto end; + goto unlock_client; } - ret = client_flush_outgoing_queue(client, state); + transmission_status = client_flush_outgoing_queue(client); + ret = client_report(client, transmission_status, user_data); if (ret) { - goto end; + goto unlock_client; + } +unlock_client: + pthread_mutex_unlock(&client->lock); + if (ret) { + goto end_unlock_list; } } ret = 0; + +end_unlock_list: + pthread_mutex_unlock(&client_list->lock); end: lttng_dynamic_buffer_reset(&msg_buffer); return ret; } +static struct lttng_trigger_notification *receive_notification(int pipe, + enum lttng_domain_type domain) +{ + int ret; + uint64_t id; + struct lttng_trigger_notification *notification = NULL; + char *capture_buffer = NULL; + size_t capture_buffer_size; + void *reception_buffer; + size_t reception_size; + + struct lttng_ust_trigger_notification ust_notification; + struct lttng_kernel_trigger_notification kernel_notification; + + /* Init lttng_trigger_notification */ + + switch(domain) { + case LTTNG_DOMAIN_UST: + reception_buffer = (void *) &ust_notification; + reception_size = sizeof(ust_notification); + break; + case LTTNG_DOMAIN_KERNEL: + reception_buffer = (void *) &kernel_notification; + reception_size = sizeof(kernel_notification); + break; + default: + assert(0); + } + + /* + * The monitoring pipe only holds messages smaller than PIPE_BUF, + * ensuring that read/write of sampling messages are atomic. + */ + /* TODO: should we read as much as we can ? EWOULDBLOCK? */ + + ret = lttng_read(pipe, reception_buffer, reception_size); + if (ret != reception_size) { + ERR("[notification-thread] Failed to read from event source pipe (fd = %i)", + pipe); + /* TODO: Should this error out completly. + * This can happen when an app is killed as of today + * ret = -1 cause the whole thread to die and fuck up + * everything. + */ + goto end; + } + + switch(domain) { + case LTTNG_DOMAIN_UST: + id = ust_notification.id; + capture_buffer_size = + ust_notification.capture_buf_size; + break; + case LTTNG_DOMAIN_KERNEL: + id = kernel_notification.id; + capture_buffer_size = + kernel_notification.capture_buf_size; + break; + default: + assert(0); + } + + if (capture_buffer_size == 0) { + capture_buffer = NULL; + goto skip_capture; + } + + capture_buffer = malloc(capture_buffer_size); + if (!capture_buffer) { + ERR("[notification-thread] Failed to allocate capture buffer"); + goto end; + } + + /* + * Fetch additional payload (capture). + */ + ret = lttng_read(pipe, capture_buffer, capture_buffer_size); + if (ret != capture_buffer_size) { + ERR("[notification-thread] Failed to read from event source pipe (fd = %i)", + pipe); + /* TODO: Should this error out completly. + * This can happen when an app is killed as of today + * ret = -1 cause the whole thread to die and fuck up + * everything. + */ + goto end; + } + +skip_capture: + notification = lttng_trigger_notification_create( + id, domain, capture_buffer, capture_buffer_size); + if (notification == NULL) { + goto end; + } + + /* Ownership transfered to the lttng_trigger_notification object */ + capture_buffer = NULL; + +end: + free(capture_buffer); + return notification; +} + +int handle_notification_thread_event(struct notification_thread_state *state, + int pipe, + enum lttng_domain_type domain) +{ + int ret; + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + struct notification_trigger_tokens_ht_element *element; + struct lttng_trigger_notification *notification = NULL; + enum action_executor_status executor_status; + struct notification_client_list *client_list = NULL; + + notification = receive_notification(pipe, domain); + if (notification == NULL) { + ERR("[notification-thread] Error receiving notification from tracer " + "(fd = %i, domain = %s)", + pipe, lttng_domain_type_str(domain)); + ret = -1; + goto end; + } + + /* Find triggers associated with this token. */ + rcu_read_lock(); + cds_lfht_lookup(state->trigger_tokens_ht, + hash_key_u64(¬ification->id, lttng_ht_seed), + match_trigger_token, ¬ification->id, &iter); + node = cds_lfht_iter_get_node(&iter); + if (caa_unlikely(!node)) { + /* TODO: is this an error? This might happen if the receive side + * is slow to process event from source and that the trigger was + * removed but the app still kicking. This yield another + * question on the trigger lifetime and when we can remove a + * trigger. How to guarantee that all event with the token idea + * have be processed? Do we want to provide this guarantee? + * + * Update: I have encountered this when using a trigger on + * sched_switch and then removing it. The frequency is quite + * high hence we en up exactly in the mentionned scenario. + * AFAIK this might be the best way to handle this. + */ + ret = 0; + goto end_unlock; + } + element = caa_container_of(node, + struct notification_trigger_tokens_ht_element, + node); + + if (!lttng_trigger_is_ready_to_fire(element->trigger)) { + ret = 0; + goto end_unlock; + } + + client_list = get_client_list_from_condition(state, + lttng_trigger_get_const_condition(element->trigger)); + executor_status = action_executor_enqueue(state->executor, + element->trigger, client_list, notification); + + /* Notification ownership passed to the executor. */ + notification = NULL; + + switch (executor_status) { + case ACTION_EXECUTOR_STATUS_OK: + ret = 0; + break; + case ACTION_EXECUTOR_STATUS_OVERFLOW: + { + struct notification_client_list_element *client_list_element, + *tmp; + + /* + * Not a fatal error; this is expected and simply means the + * executor has too much work queued already. + */ + ret = 0; + + if (!client_list) { + break; + } + + /* Warn clients that a notification (or more) was dropped. */ + pthread_mutex_lock(&client_list->lock); + cds_list_for_each_entry_safe(client_list_element, tmp, + &client_list->list, node) { + enum client_transmission_status transmission_status; + struct notification_client *client = + client_list_element->client; + + pthread_mutex_lock(&client->lock); + ret = client_notification_overflow(client); + if (ret) { + /* Fatal error. */ + goto next_client; + } + + transmission_status = + client_flush_outgoing_queue(client); + ret = client_handle_transmission_status( + client, transmission_status, state); + if (ret) { + /* Fatal error. */ + goto next_client; + } +next_client: + pthread_mutex_unlock(&client->lock); + if (ret) { + break; + } + } + pthread_mutex_lock(&client_list->lock); + break; + } + case ACTION_EXECUTOR_STATUS_ERROR: + /* Fatal error, shut down everything. */ + ERR("Fatal error encoutered while enqueuing action"); + ret = -1; + goto end_unlock; + default: + /* Unhandled error. */ + abort(); + } + +end_unlock: + lttng_trigger_notification_destroy(notification); + notification_client_list_put(client_list); + rcu_read_unlock(); +end: + return ret; +} + int handle_notification_thread_channel_sample( struct notification_thread_state *state, int pipe, enum lttng_domain_type domain) @@ -3244,10 +4305,10 @@ int handle_notification_thread_channel_sample( * channel's destruction before we get a chance to process that * sample. */ - DBG("[notification-thread] Received a sample for an unknown channel from consumerd, key = %" PRIu64 " in %s domain", + DBG("[notification-thread] Received a sample for an unknown channel from consumerd, key = %" PRIu64 + " in %s domain", latest_sample.key.key, - domain == LTTNG_DOMAIN_KERNEL ? "kernel" : - "user space"); + lttng_domain_type_str(domain)); goto end_unlock; } channel_info = caa_container_of(node, struct channel_info, @@ -3332,16 +4393,23 @@ int handle_notification_thread_channel_sample( node) { const struct lttng_condition *condition; const struct lttng_action *action; - const struct lttng_trigger *trigger; - struct notification_client_list *client_list; + struct lttng_trigger *trigger; + struct notification_client_list *client_list = NULL; struct lttng_evaluation *evaluation = NULL; + bool client_list_is_empty; + ret = 0; trigger = trigger_list_element->trigger; condition = lttng_trigger_get_const_condition(trigger); assert(condition); action = lttng_trigger_get_const_action(trigger); + + if (!lttng_trigger_is_ready_to_fire(trigger)) { + goto put_list; + } /* Notify actions are the only type currently supported. */ + /* TODO support other type of action */ assert(lttng_action_get_type_const(action) == LTTNG_ACTION_TYPE_NOTIFY); @@ -3351,12 +4419,13 @@ int handle_notification_thread_channel_sample( */ client_list = get_client_list_from_condition(state, condition); assert(client_list); - if (cds_list_empty(&client_list->list)) { + client_list_is_empty = cds_list_empty(&client_list->list); + if (client_list_is_empty) { /* * No clients interested in the evaluation's result, * skip it. */ - continue; + goto put_list; } ret = evaluate_buffer_condition(condition, &evaluation, state, @@ -3366,11 +4435,11 @@ int handle_notification_thread_channel_sample( latest_session_consumed_total, channel_info); if (caa_unlikely(ret)) { - goto end_unlock; + goto put_list; } if (caa_likely(!evaluation)) { - continue; + goto put_list; } /* Dispatch evaluation result to all clients. */ @@ -3379,8 +4448,10 @@ int handle_notification_thread_channel_sample( channel_info->session_info->uid, channel_info->session_info->gid); lttng_evaluation_destroy(evaluation); +put_list: + notification_client_list_put(client_list); if (caa_unlikely(ret)) { - goto end_unlock; + break; } } end_unlock: diff --git a/src/bin/lttng-sessiond/notification-thread-events.h b/src/bin/lttng-sessiond/notification-thread-events.h index 2f699bf45..7affb752d 100644 --- a/src/bin/lttng-sessiond/notification-thread-events.h +++ b/src/bin/lttng-sessiond/notification-thread-events.h @@ -44,4 +44,8 @@ int handle_notification_thread_channel_sample( struct notification_thread_state *state, int pipe, enum lttng_domain_type domain); +int handle_notification_thread_event( + struct notification_thread_state *state, int pipe, + enum lttng_domain_type domain); + #endif /* NOTIFICATION_THREAD_EVENTS_H */ diff --git a/src/bin/lttng-sessiond/notification-thread-internal.h b/src/bin/lttng-sessiond/notification-thread-internal.h index 5242695f0..81c329e24 100644 --- a/src/bin/lttng-sessiond/notification-thread-internal.h +++ b/src/bin/lttng-sessiond/notification-thread-internal.h @@ -8,9 +8,18 @@ #ifndef NOTIFICATION_THREAD_INTERNAL_H #define NOTIFICATION_THREAD_INTERNAL_H +#include +#include #include -#include +#include #include +#include +#include +#include +#include "notification-thread.h" + +struct lttng_evaluation; +struct notification_thread_handle; struct channel_key { uint64_t key; @@ -64,4 +73,180 @@ struct channel_info { struct rcu_head rcu_node; }; +/* + * Facilities to carry the different notifications type in the action + * processing code path. + */ +struct lttng_trigger_notification { + uint64_t id; + enum lttng_domain_type type; + size_t capture_buf_size; + char *capture_buffer; +}; + +struct notification_client_list_element { + struct notification_client *client; + struct cds_list_head node; +}; + +/* + * Thread safety of notification_client and notification_client_list. + * + * The notification thread (main thread) and the action executor + * interact through client lists. Hence, when the action executor + * thread looks-up the list of clients subscribed to a given + * condition, it will acquire a reference to the list and lock it + * while attempting to communicate with the various clients. + * + * It is not necessary to reference-count clients as they are guaranteed + * to be 'alive' if they are present in a list and that list is locked. Indeed, + * removing references to the client from those subscription lists is part of + * the work performed on destruction of a client. + * + * No provision for other access scenarios are taken into account; + * this is the bare minimum to make these accesses safe and the + * notification thread's state is _not_ "thread-safe" in any general + * sense. + */ +struct notification_client_list { + pthread_mutex_t lock; + struct urcu_ref ref; + const struct lttng_trigger *trigger; + struct cds_list_head list; + /* Weak reference to container. */ + struct cds_lfht *notification_trigger_clients_ht; + struct cds_lfht_node notification_trigger_clients_ht_node; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; +}; + +struct notification_client { + /* Nests within the notification_client_list lock. */ + pthread_mutex_t lock; + notification_client_id id; + int socket; + /* Client protocol version. */ + uint8_t major, minor; + uid_t uid; + gid_t gid; + /* + * Indicates if the credentials and versions of the client have been + * checked. + */ + bool validated; + /* + * Conditions to which the client's notification channel is subscribed. + * List of struct lttng_condition_list_node. The condition member is + * owned by the client. + */ + struct cds_list_head condition_list; + struct cds_lfht_node client_socket_ht_node; + struct cds_lfht_node client_id_ht_node; + struct { + /* + * If a client's communication is inactive, it means that a + * fatal error has occurred (could be either a protocol error or + * the socket API returned a fatal error). No further + * communication should be attempted; the client is queued for + * clean-up. + */ + bool active; + struct { + /* + * During the reception of a message, the reception + * buffers' "size" is set to contain the current + * message's complete payload. + */ + struct lttng_dynamic_buffer buffer; + /* Bytes left to receive for the current message. */ + size_t bytes_to_receive; + /* Type of the message being received. */ + enum lttng_notification_channel_message_type msg_type; + /* + * Indicates whether or not credentials are expected + * from the client. + */ + bool expect_creds; + /* + * Indicates whether or not credentials were received + * from the client. + */ + bool creds_received; + /* Only used during credentials reception. */ + lttng_sock_cred creds; + } inbound; + struct { + /* + * Indicates whether or not a notification addressed to + * this client was dropped because a command reply was + * already buffered. + * + * A notification is dropped whenever the buffer is not + * empty. + */ + bool dropped_notification; + /* + * Indicates whether or not a command reply is already + * buffered. In this case, it means that the client is + * not consuming command replies before emitting a new + * one. This could be caused by a protocol error or a + * misbehaving/malicious client. + */ + bool queued_command_reply; + struct lttng_dynamic_buffer buffer; + } outbound; + } communication; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; +}; + +enum client_transmission_status { + CLIENT_TRANSMISSION_STATUS_COMPLETE, + CLIENT_TRANSMISSION_STATUS_QUEUED, + /* Communication failure. */ + CLIENT_TRANSMISSION_STATUS_FAIL, + /* Fatal error. */ + CLIENT_TRANSMISSION_STATUS_ERROR, +}; + +LTTNG_HIDDEN +bool notification_client_list_get(struct notification_client_list *list); + +LTTNG_HIDDEN +void notification_client_list_put(struct notification_client_list *list); + +typedef int (*report_client_transmission_result_cb)( + struct notification_client *client, + enum client_transmission_status status, + void *user_data); + +LTTNG_HIDDEN +int notification_client_list_send_evaluation( + struct notification_client_list *list, + const struct lttng_condition *condition, + const struct lttng_evaluation *evaluation, + const struct lttng_credentials *trigger_creds, + const struct lttng_credentials *source_object_creds, + report_client_transmission_result_cb client_report, + void *user_data); + +LTTNG_HIDDEN +int notification_thread_client_communication_update( + struct notification_thread_handle *handle, + notification_client_id id, + enum client_transmission_status transmission_status); +/* + * Takes ownership of the payload if present. + */ +LTTNG_HIDDEN +struct lttng_trigger_notification *lttng_trigger_notification_create( + uint64_t id, + enum lttng_domain_type domain, + char *payload, + size_t payload_size); + +LTTNG_HIDDEN +void lttng_trigger_notification_destroy( + struct lttng_trigger_notification *trigger_notification); + #endif /* NOTIFICATION_THREAD_INTERNAL_H */ diff --git a/src/bin/lttng-sessiond/notification-thread.c b/src/bin/lttng-sessiond/notification-thread.c index 7dadb2c67..0d88a796a 100644 --- a/src/bin/lttng-sessiond/notification-thread.c +++ b/src/bin/lttng-sessiond/notification-thread.c @@ -29,6 +29,9 @@ #include "health-sessiond.h" #include "thread.h" +#include "kernel.h" +#include + #include #include #include @@ -70,14 +73,27 @@ void notification_thread_handle_destroy( PERROR("close kernel consumer channel monitoring pipe"); } } + + /* TODO: refactor this if needed. Lifetime of the kernel notification + * event source. + * event_trigger_sources.kernel_tracer is owned by the main thread and + * is closed at this point. + */ + handle->event_trigger_sources.kernel_tracer = -1; end: free(handle); } +/* + * TODO: refactor this if needed. Lifetime of the kernel notification event source. + * The kernel_notification_monitor_fd ownwership remain to the main thread. + * This is because we need to close this fd before removing the modules. + */ struct notification_thread_handle *notification_thread_handle_create( struct lttng_pipe *ust32_channel_monitor_pipe, struct lttng_pipe *ust64_channel_monitor_pipe, - struct lttng_pipe *kernel_channel_monitor_pipe) + struct lttng_pipe *kernel_channel_monitor_pipe, + int kernel_notification_monitor_fd) { int ret; struct notification_thread_handle *handle; @@ -88,6 +104,8 @@ struct notification_thread_handle *notification_thread_handle_create( goto end; } + handle->event_trigger_sources.kernel_tracer = -1; + sem_init(&handle->ready, 0, 0); event_pipe = lttng_pipe_open(FD_CLOEXEC); @@ -135,6 +153,14 @@ struct notification_thread_handle *notification_thread_handle_create( } else { handle->channel_monitoring_pipes.kernel_consumer = -1; } + + handle->event_trigger_sources.kernel_tracer = kernel_notification_monitor_fd; + + CDS_INIT_LIST_HEAD(&handle->event_trigger_sources.list); + ret = pthread_mutex_init(&handle->event_trigger_sources.lock, NULL); + if (ret) { + goto error; + } end: return handle; error: @@ -266,14 +292,15 @@ int init_poll_set(struct lttng_poll_event *poll_set, int ret; /* - * Create pollset with size 5: + * Create pollset with size 6: * - notification channel socket (listen for new connections), * - command queue event fd (internal sessiond commands), * - consumerd (32-bit user space) channel monitor pipe, * - consumerd (64-bit user space) channel monitor pipe, * - consumerd (kernel) channel monitor pipe. + * - kernel trigger event pipe, */ - ret = lttng_poll_create(poll_set, 5, LTTNG_CLOEXEC); + ret = lttng_poll_create(poll_set, 6, LTTNG_CLOEXEC); if (ret < 0) { goto end; } @@ -305,7 +332,7 @@ int init_poll_set(struct lttng_poll_event *poll_set, goto error; } if (handle->channel_monitoring_pipes.kernel_consumer < 0) { - goto end; + goto skip_kernel_consumer; } ret = lttng_poll_add(poll_set, handle->channel_monitoring_pipes.kernel_consumer, @@ -314,6 +341,19 @@ int init_poll_set(struct lttng_poll_event *poll_set, ERR("[notification-thread] Failed to add kernel channel monitoring pipe fd to pollset"); goto error; } + +skip_kernel_consumer: + if (handle->event_trigger_sources.kernel_tracer < 0) { + goto end; + } + + ret = lttng_poll_add(poll_set, + handle->event_trigger_sources.kernel_tracer, + LPOLLIN | LPOLLERR); + if (ret < 0) { + ERR("[notification-thread] Failed to add kernel trigger notification monitoring pipe fd to pollset"); + goto error; + } end: return ret; error: @@ -332,6 +372,10 @@ void fini_thread_state(struct notification_thread_state *state) ret = cds_lfht_destroy(state->client_socket_ht, NULL); assert(!ret); } + if (state->client_id_ht) { + ret = cds_lfht_destroy(state->client_id_ht, NULL); + assert(!ret); + } if (state->triggers_ht) { ret = handle_notification_thread_trigger_unregister_all(state); assert(!ret); @@ -359,6 +403,14 @@ void fini_thread_state(struct notification_thread_state *state) ret = cds_lfht_destroy(state->sessions_ht, NULL); assert(!ret); } + if (state->triggers_by_name_ht) { + ret = cds_lfht_destroy(state->triggers_by_name_ht, NULL); + assert(!ret); + } + if (state->trigger_tokens_ht) { + ret = cds_lfht_destroy(state->trigger_tokens_ht, NULL); + assert(!ret); + } /* * Must be destroyed after all channels have been destroyed. * See comment in struct lttng_session_trigger_list. @@ -371,6 +423,9 @@ void fini_thread_state(struct notification_thread_state *state) notification_channel_socket_destroy( state->notification_channel_socket); } + if (state->executor) { + action_executor_destroy(state->executor); + } lttng_poll_clean(&state->events); } @@ -397,6 +452,7 @@ int init_thread_state(struct notification_thread_handle *handle, memset(state, 0, sizeof(*state)); state->notification_channel_socket = -1; + state->trigger_id.token_generator = 1; lttng_poll_init(&state->events); ret = notification_channel_socket_create(); @@ -424,6 +480,12 @@ int init_thread_state(struct notification_thread_handle *handle, goto error; } + state->client_id_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->client_id_ht) { + goto error; + } + state->channel_triggers_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); if (!state->channel_triggers_ht) { @@ -463,6 +525,20 @@ int init_thread_state(struct notification_thread_handle *handle, if (!state->triggers_ht) { goto error; } + state->triggers_by_name_ht = cds_lfht_new(DEFAULT_HT_SIZE, + 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->triggers_by_name_ht) { + goto error; + } + state->trigger_tokens_ht = cds_lfht_new(DEFAULT_HT_SIZE, + 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->trigger_tokens_ht) { + goto error; + } + state->executor = action_executor_create(handle); + if (!state->executor) { + goto error; + } mark_thread_as_ready(handle); end: return 0; @@ -507,6 +583,54 @@ end: return ret; } +static int handle_trigger_event_pipe(int fd, + uint32_t revents, + struct notification_thread_handle *handle, + struct notification_thread_state *state) +{ + int ret = 0; + enum lttng_domain_type domain; + + if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ret = lttng_poll_del(&state->events, fd); + if (ret) { + ERR("[notification-thread] Failed to remove event monitoring pipe from poll set"); + } + goto end; + } + + if (fd == handle->event_trigger_sources.kernel_tracer) { + domain = LTTNG_DOMAIN_KERNEL; + } else { + domain = LTTNG_DOMAIN_UST; + } + + ret = handle_notification_thread_event(state, fd, domain); + if (ret) { + ERR("[notification-thread] Event sample handling error occurred for fd: %d", fd); + ret = -1; + goto end; + } +end: + return ret; +} + +static bool fd_is_event_source(struct notification_thread_handle *handle, int fd) +{ + struct notification_event_trigger_source_element *source_element, *tmp; + cds_list_for_each_entry_safe(source_element, tmp, + &handle->event_trigger_sources.list, node) { + if (source_element->fd != fd) { + continue; + } + return true; + } + if (fd == handle->event_trigger_sources.kernel_tracer) { + return true; + } + return false; +} + /* * This thread services notification channel clients and commands received * from various lttng-sessiond components over a command queue. @@ -594,6 +718,11 @@ void *thread_notification(void *data) if (ret) { goto error; } + } else if (fd_is_event_source(handle, fd)) { + ret = handle_trigger_event_pipe(fd, revents, handle, &state); + if (ret) { + goto error; + } } else { /* Activity on a client's socket. */ if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { diff --git a/src/bin/lttng-sessiond/notification-thread.h b/src/bin/lttng-sessiond/notification-thread.h index 3d766780e..71468f9be 100644 --- a/src/bin/lttng-sessiond/notification-thread.h +++ b/src/bin/lttng-sessiond/notification-thread.h @@ -8,16 +8,32 @@ #ifndef NOTIFICATION_THREAD_H #define NOTIFICATION_THREAD_H -#include -#include -#include -#include -#include +#include "action-executor.h" +#include "thread.h" #include #include +#include +#include #include #include -#include "thread.h" +#include +#include +#include + +typedef uint64_t notification_client_id; + +struct notification_event_trigger_source_element { + int fd; + struct cds_list_head node; +}; + +struct notification_trigger_tokens_ht_element { + uint64_t token; + struct lttng_trigger *trigger; + struct cds_lfht_node node; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; +}; struct notification_thread_handle { /* @@ -39,6 +55,16 @@ struct notification_thread_handle { int ust64_consumer; int kernel_consumer; } channel_monitoring_pipes; + /* + * Read side of pipes used to reveice event trigger generetated by + * registered applications + */ + struct { + /* List of notification_event_trigger_source_element */ + struct cds_list_head list; + pthread_mutex_t lock; + int kernel_tracer; + } event_trigger_sources; /* Used to wait for the launch of the notification thread. */ sem_t ready; }; @@ -49,9 +75,14 @@ struct notification_thread_handle { * In order to speed-up and simplify queries, hash tables providing the * following associations are maintained: * - * - client_socket_ht: associate a client's socket (fd) to its "struct client" - * This hash table owns the "struct client" which must thus be - * disposed-of on removal from the hash table. + * - client_socket_ht: associate a client's socket (fd) to its + * "struct notification_client". + * This hash table owns the "struct notification_client" which must + * thus be disposed-of on removal from the hash table. + * + * - client_id_ht: associate a client's id to its "struct notification_client" + * This hash table holds a _weak_ reference to the + * "struct notification_client". * * - channel_triggers_ht: * associates a channel key to a list of @@ -99,9 +130,13 @@ struct notification_thread_handle { * channels through their struct channel_info (ref-counting is used). * * - triggers_ht: - * associates a condition to a struct lttng_trigger_ht_element. + * associates a trigger to a struct lttng_trigger_ht_element. * The hash table holds the ownership of the * lttng_trigger_ht_elements along with the triggers themselves. + * - triggers_by_name_ht: + * associates a trigger name to a struct lttng_trigger_ht_element. + * The hash table does not hold any ownership and is used strictly + * for lookup on registration. * * The thread reacts to the following internal events: * 1) creation of a tracing channel, @@ -145,6 +180,7 @@ struct notification_thread_handle { * notification_trigger_clients_ht, * - add trigger to channel_triggers_ht (if applicable), * - add trigger to session_triggers_ht (if applicable), + * - add trigger to triggers_by_name_ht * - add trigger to triggers_ht * - evaluate the trigger's condition right away to react if that condition * is true from the beginning. @@ -154,6 +190,7 @@ struct notification_thread_handle { * - remove the trigger from the notification_trigger_clients_ht, * - remove trigger from channel_triggers_ht (if applicable), * - remove trigger from session_triggers_ht (if applicable), + * - remove trigger to triggers_by_name_ht * - remove trigger from triggers_ht * * 5) Reception of a channel monitor sample from the consumer daemon @@ -168,9 +205,11 @@ struct notification_thread_handle { * 7) Session rotation completed * * 8) Connection of a client - * - add client socket to the client_socket_ht. + * - add client socket to the client_socket_ht, + * - add client socket to the client_id_ht. * * 9) Disconnection of a client + * - remove client socket from the client_id_ht, * - remove client socket from the client_socket_ht, * - traverse all conditions to which the client is subscribed and remove * the client from the notification_trigger_clients_ht. @@ -191,6 +230,7 @@ struct notification_thread_state { int notification_channel_socket; struct lttng_poll_event events; struct cds_lfht *client_socket_ht; + struct cds_lfht *client_id_ht; struct cds_lfht *channel_triggers_ht; struct cds_lfht *session_triggers_ht; struct cds_lfht *channel_state_ht; @@ -198,13 +238,22 @@ struct notification_thread_state { struct cds_lfht *channels_ht; struct cds_lfht *sessions_ht; struct cds_lfht *triggers_ht; + struct cds_lfht *triggers_by_name_ht; + struct cds_lfht *trigger_tokens_ht; + struct { + uint64_t token_generator; + uint64_t name_offset; + } trigger_id; + notification_client_id next_notification_client_id; + struct action_executor *executor; }; /* notification_thread_data takes ownership of the channel monitor pipes. */ struct notification_thread_handle *notification_thread_handle_create( struct lttng_pipe *ust32_channel_monitor_pipe, struct lttng_pipe *ust64_channel_monitor_pipe, - struct lttng_pipe *kernel_channel_monitor_pipe); + struct lttng_pipe *kernel_channel_monitor_pipe, + int kernel_notification_monitor_fd); void notification_thread_handle_destroy( struct notification_thread_handle *handle); struct lttng_thread *launch_notification_thread( diff --git a/src/bin/lttng-sessiond/rotate.c b/src/bin/lttng-sessiond/rotate.c index e2a3ef9ea..6813ce8c2 100644 --- a/src/bin/lttng-sessiond/rotate.c +++ b/src/bin/lttng-sessiond/rotate.c @@ -88,6 +88,9 @@ int subscribe_session_consumed_size_rotation(struct ltt_session *session, uint64 goto end; } + lttng_trigger_set_credentials( + session->rotate_trigger, session->uid, session->gid); + nc_status = lttng_notification_channel_subscribe( rotate_notification_channel, session->rotate_condition); if (nc_status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { diff --git a/src/bin/lttng-sessiond/thread.c b/src/bin/lttng-sessiond/thread.c index ed090f6d6..ae7f45fd9 100644 --- a/src/bin/lttng-sessiond/thread.c +++ b/src/bin/lttng-sessiond/thread.c @@ -61,9 +61,10 @@ void *launch_thread(void *data) void *ret; struct lttng_thread *thread = (struct lttng_thread *) data; - DBG("Launching \"%s\" thread", thread->name); + logger_set_thread_name(thread->name, true); + DBG("Entering thread entry point"); ret = thread->entry(thread->data); - DBG("Thread \"%s\" has returned", thread->name); + DBG("Thread entry point has returned"); return ret; } @@ -164,20 +165,22 @@ bool _lttng_thread_shutdown(struct lttng_thread *thread) result = false; goto end; } - /* Release the list's reference to the thread. */ - cds_list_del(&thread->node); - lttng_thread_put(thread); + DBG("Joined thread \"%s\"", thread->name); end: return result; } bool lttng_thread_shutdown(struct lttng_thread *thread) { - bool result; - - pthread_mutex_lock(&thread_list.lock); - result = _lttng_thread_shutdown(thread); - pthread_mutex_unlock(&thread_list.lock); + const bool result = _lttng_thread_shutdown(thread); + + if (result) { + /* Release the list's reference to the thread. */ + pthread_mutex_lock(&thread_list.lock); + cds_list_del(&thread->node); + lttng_thread_put(thread); + pthread_mutex_unlock(&thread_list.lock); + } return result; } diff --git a/src/bin/lttng-sessiond/trace-kernel.c b/src/bin/lttng-sessiond/trace-kernel.c index 044fc98d2..4ba6db3fc 100644 --- a/src/bin/lttng-sessiond/trace-kernel.c +++ b/src/bin/lttng-sessiond/trace-kernel.c @@ -15,7 +15,17 @@ #include #include #include - +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -62,7 +72,7 @@ struct ltt_kernel_channel *trace_kernel_get_channel_by_name( struct ltt_kernel_event *trace_kernel_find_event( char *name, struct ltt_kernel_channel *channel, enum lttng_event_type type, - struct lttng_filter_bytecode *filter) + struct lttng_bytecode *filter) { struct ltt_kernel_event *ev; int found = 0; @@ -131,6 +141,29 @@ struct ltt_kernel_event *trace_kernel_get_event_by_name( } } +struct ltt_kernel_token_event_rule *trace_kernel_find_trigger_by_token( + struct ltt_kernel_token_event_rule_list *list, + uint64_t token) +{ + struct ltt_kernel_token_event_rule *token_event_rule; + int found = 0; + + assert(list); + + cds_list_for_each_entry(token_event_rule, &list->head, list) { + if (token_event_rule->token == token) { + found = 1; + } + break; + } + if (found) { + DBG("Found token event rule %" PRIu64, token); + return token_event_rule; + } else { + return NULL; + } +} + /* * Allocate and initialize a kernel session data structure. * @@ -321,7 +354,7 @@ error: */ enum lttng_error_code trace_kernel_create_event( struct lttng_event *ev, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct ltt_kernel_event **kernel_event) { enum lttng_error_code ret; @@ -479,6 +512,162 @@ error: return ret; } +/* + * Allocate and initialize a kernel token event rule. + * + * Return pointer to structure or NULL. + */ +enum lttng_error_code trace_kernel_create_token_event_rule( + struct lttng_event_rule *event_rule, + uint64_t token, + struct ltt_kernel_token_event_rule **kernel_token_event_rule) +{ + enum lttng_error_code ret = LTTNG_OK; + struct ltt_kernel_token_event_rule *local_kernel_token_event_rule; + + assert(kernel_token_event_rule); + + local_kernel_token_event_rule = zmalloc(sizeof(struct ltt_kernel_token_event_rule)); + if (local_kernel_token_event_rule == NULL) { + PERROR("Failed to allocate ltt_kernel_token_event_rule structure"); + ret = LTTNG_ERR_NOMEM; + goto error; + } + + local_kernel_token_event_rule->fd = -1; + local_kernel_token_event_rule->enabled = 1; + local_kernel_token_event_rule->token = token; + + /* Get the reference of the event rule */ + if (!lttng_event_rule_get(event_rule)) { + assert(0); + } + + local_kernel_token_event_rule->event_rule = event_rule; + /* The event rule still own the filter and bytecode */ + local_kernel_token_event_rule->filter = lttng_event_rule_get_filter_bytecode(event_rule); + + DBG3("[trace] Kernel token event rule %" PRIu64 " allocated", local_kernel_token_event_rule->token); +error: + *kernel_token_event_rule = local_kernel_token_event_rule; + return ret; + +} + +/* + * Initialize a kernel trigger from an event rule. + */ +enum lttng_error_code trace_kernel_init_trigger_from_event_rule(const struct lttng_event_rule *rule, + struct lttng_kernel_trigger *kernel_trigger) +{ + enum lttng_error_code ret; + enum lttng_event_rule_status status; + const char *name = NULL; + + /* TODO: do this for now but have disucssion on if this could be the + * responsability of the event_rule itself ala + * "lttng_even_rule_generate_kernel_trigger" + */ + switch (lttng_event_rule_get_type(rule)) { + case LTTNG_EVENT_RULE_TYPE_KPROBE: + kernel_trigger->instrumentation = LTTNG_KERNEL_KPROBE; + kernel_trigger->u.kprobe.addr = lttng_event_rule_kprobe_get_address(rule); + kernel_trigger->u.kprobe.offset = lttng_event_rule_kprobe_get_offset(rule); + strncpy(kernel_trigger->u.kprobe.symbol_name, + lttng_event_rule_kprobe_get_symbol_name(rule), LTTNG_KERNEL_SYM_NAME_LEN); + kernel_trigger->u.kprobe.symbol_name[LTTNG_KERNEL_SYM_NAME_LEN - 1] = '\0'; + (void) lttng_event_rule_kprobe_get_name(rule, &name); + ret = LTTNG_OK; + break; + case LTTNG_EVENT_RULE_TYPE_UPROBE: + { + const struct lttng_userspace_probe_location* location = NULL; + const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; + + status = lttng_event_rule_uprobe_get_location(rule, &location); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + + kernel_trigger->instrumentation = LTTNG_KERNEL_UPROBE; + + /* + * Only the elf lookup method is supported at the moment. + */ + lookup = lttng_userspace_probe_location_get_lookup_method( + location); + if (!lookup) { + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + + /* + * From the kernel tracer's perspective, all userspace probe + * event types are all the same: a file and an offset. + */ + switch (lttng_userspace_probe_location_lookup_method_get_type(lookup)) { + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF: + /* Get the file descriptor on the target binary. */ + kernel_trigger->u.uprobe.fd = + lttng_userspace_probe_location_function_get_binary_fd(location); + + break; + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: + /* Get the file descriptor on the target binary. */ + kernel_trigger->u.uprobe.fd = + lttng_userspace_probe_location_tracepoint_get_binary_fd(location); + break; + default: + DBG("Unsupported lookup method type"); + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + + (void) lttng_event_rule_uprobe_get_name(rule, &name); + + ret = LTTNG_OK; + break; + } + case LTTNG_EVENT_RULE_TYPE_KRETPROBE: + assert("Not supported" && 0); + kernel_trigger->instrumentation = LTTNG_KERNEL_KRETPROBE; + kernel_trigger->u.kretprobe.addr = lttng_event_rule_kretprobe_get_address(rule); + kernel_trigger->u.kretprobe.offset = lttng_event_rule_kretprobe_get_offset(rule); + strncpy(kernel_trigger->u.kretprobe.symbol_name, + lttng_event_rule_kretprobe_get_symbol_name(rule), LTTNG_KERNEL_SYM_NAME_LEN); + kernel_trigger->u.kretprobe.symbol_name[LTTNG_KERNEL_SYM_NAME_LEN - 1] = '\0'; + (void) lttng_event_rule_kretprobe_get_name(rule, &name); + ret = LTTNG_OK; + break; + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + /* TODO: assert his is a kernel domain event-rule */ + kernel_trigger->instrumentation = LTTNG_KERNEL_TRACEPOINT; + (void) lttng_event_rule_tracepoint_get_pattern(rule, &name); + ret = LTTNG_OK; + break; + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + kernel_trigger->instrumentation = LTTNG_KERNEL_SYSCALL; + (void) lttng_event_rule_syscall_get_pattern(rule, &name); + ret = LTTNG_OK; + break; + default: + ERR("Unknown kernel event rule instrumentation type (%d)", lttng_event_rule_get_type(rule)); + ret = LTTNG_ERR_INVALID; + goto error; + } + + /* + * WTF is LTTNG_EVENT_ALL??? and LTTNG_EVENT_FUNTION_ENTRY????? + */ + + /* Copy event name */ + strncpy(kernel_trigger->name, name, LTTNG_KERNEL_SYM_NAME_LEN); + kernel_trigger->name[LTTNG_KERNEL_SYM_NAME_LEN - 1] = '\0'; + +error: + return ret; +} /* * Allocate and initialize a kernel metadata. * @@ -634,6 +823,33 @@ void trace_kernel_destroy_event(struct ltt_kernel_event *event) free(event); } +/* + * Cleanup kernel event structure. + */ +void trace_kernel_destroy_token_event_rule(struct ltt_kernel_token_event_rule *event) +{ + /* TODO: review in depth to ensure adequate disposing */ + assert(event); + + /* Remove from event list */ + cds_list_del(&event->list); + + if (event->fd >= 0) { + int ret; + + DBG("[trace] Closing ,token event rule fd %d", event->fd); + /* Close kernel fd */ + ret = close(event->fd); + if (ret) { + PERROR("close"); + } + } else { + DBG("[trace] Tearing down token event rule (no associated fd)"); + } + + lttng_event_rule_put(event->event_rule); + free(event); +} /* * Cleanup kernel context structure. */ diff --git a/src/bin/lttng-sessiond/trace-kernel.h b/src/bin/lttng-sessiond/trace-kernel.h index dd6f21edf..1f0751116 100644 --- a/src/bin/lttng-sessiond/trace-kernel.h +++ b/src/bin/lttng-sessiond/trace-kernel.h @@ -23,6 +23,11 @@ struct ltt_kernel_event_list { struct cds_list_head head; }; +/* Kernel event rule token list */ +struct ltt_kernel_token_event_rule_list { + struct cds_list_head head; +}; + /* Channel stream list */ struct ltt_kernel_stream_list { struct cds_list_head head; @@ -48,7 +53,19 @@ struct ltt_kernel_event { struct lttng_kernel_event *event; struct cds_list_head list; char *filter_expression; - struct lttng_filter_bytecode *filter; + struct lttng_bytecode *filter; + struct lttng_userspace_probe_location *userspace_probe_location; +}; + +/* Kernel event */ +struct ltt_kernel_token_event_rule { + int fd; + int enabled; + enum lttng_event_type type; + struct lttng_event_rule *event_rule; + struct cds_list_head list; + uint64_t token; + const struct lttng_bytecode *filter; struct lttng_userspace_probe_location *userspace_probe_location; }; @@ -132,10 +149,14 @@ struct ltt_kernel_event *trace_kernel_get_event_by_name( struct ltt_kernel_event *trace_kernel_find_event( char *name, struct ltt_kernel_channel *channel, enum lttng_event_type type, - struct lttng_filter_bytecode *filter); + struct lttng_bytecode *filter); struct ltt_kernel_channel *trace_kernel_get_channel_by_name( const char *name, struct ltt_kernel_session *session); +struct ltt_kernel_token_event_rule *trace_kernel_find_trigger_by_token( + struct ltt_kernel_token_event_rule_list *list, + uint64_t token); + /* * Create functions malloc() the data structure. */ @@ -143,15 +164,21 @@ struct ltt_kernel_session *trace_kernel_create_session(void); struct ltt_kernel_channel *trace_kernel_create_channel( struct lttng_channel *chan); enum lttng_error_code trace_kernel_create_event(struct lttng_event *ev, - char *filter_expression, struct lttng_filter_bytecode *filter, + char *filter_expression, struct lttng_bytecode *filter, struct ltt_kernel_event **kernel_event); struct ltt_kernel_metadata *trace_kernel_create_metadata(void); struct ltt_kernel_stream *trace_kernel_create_stream(const char *name, unsigned int count); struct ltt_kernel_context *trace_kernel_create_context( struct lttng_kernel_context *ctx); +enum lttng_error_code trace_kernel_create_token_event_rule( + struct lttng_event_rule *event_rule, + uint64_t token, + struct ltt_kernel_token_event_rule **kernel_token_event_rule); struct ltt_kernel_context *trace_kernel_copy_context( struct ltt_kernel_context *ctx); +enum lttng_error_code trace_kernel_init_trigger_from_event_rule(const struct lttng_event_rule *rule, + struct lttng_kernel_trigger *kernel_trigger); /* * Destroy functions free() the data structure and remove from linked list if @@ -163,6 +190,7 @@ void trace_kernel_destroy_channel(struct ltt_kernel_channel *channel); void trace_kernel_destroy_event(struct ltt_kernel_event *event); void trace_kernel_destroy_stream(struct ltt_kernel_stream *stream); void trace_kernel_destroy_context(struct ltt_kernel_context *ctx); +void trace_kernel_destroy_token_event_rule(struct ltt_kernel_token_event_rule *rule); void trace_kernel_free_session(struct ltt_kernel_session *session); #endif /* _LTT_TRACE_KERNEL_H */ diff --git a/src/bin/lttng-sessiond/trace-ust.c b/src/bin/lttng-sessiond/trace-ust.c index 80dd8dc54..7a813bee5 100644 --- a/src/bin/lttng-sessiond/trace-ust.c +++ b/src/bin/lttng-sessiond/trace-ust.c @@ -194,7 +194,7 @@ error: * MUST be acquired before calling this. */ struct ltt_ust_event *trace_ust_find_event(struct lttng_ht *ht, - char *name, struct lttng_filter_bytecode *filter, + char *name, struct lttng_bytecode *filter, enum lttng_ust_loglevel_type loglevel_type, int loglevel_value, struct lttng_event_exclusion *exclusion) { @@ -446,7 +446,7 @@ end: */ enum lttng_error_code trace_ust_create_event(struct lttng_event *ev, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, bool internal_event, struct ltt_ust_event **ust_event) diff --git a/src/bin/lttng-sessiond/trace-ust.h b/src/bin/lttng-sessiond/trace-ust.h index ecd0b8771..53e1ab2b5 100644 --- a/src/bin/lttng-sessiond/trace-ust.h +++ b/src/bin/lttng-sessiond/trace-ust.h @@ -24,7 +24,7 @@ struct agent; struct ltt_ust_ht_key { const char *name; - const struct lttng_filter_bytecode *filter; + const struct lttng_bytecode *filter; enum lttng_ust_loglevel_type loglevel_type; int loglevel_value; const struct lttng_event_exclusion *exclusion; @@ -43,7 +43,7 @@ struct ltt_ust_event { struct lttng_ust_event attr; struct lttng_ht_node_str node; char *filter_expression; - struct lttng_filter_bytecode *filter; + struct lttng_bytecode *filter; struct lttng_event_exclusion *exclusion; /* * An internal event is an event which was created by the session daemon @@ -182,7 +182,7 @@ int trace_ust_ht_match_event_by_name(struct cds_lfht_node *node, * Lookup functions. NULL is returned if not found. */ struct ltt_ust_event *trace_ust_find_event(struct lttng_ht *ht, - char *name, struct lttng_filter_bytecode *filter, + char *name, struct lttng_bytecode *filter, enum lttng_ust_loglevel_type loglevel_type, int loglevel_value, struct lttng_event_exclusion *exclusion); struct ltt_ust_channel *trace_ust_find_channel_by_name(struct lttng_ht *ht, @@ -198,7 +198,7 @@ struct ltt_ust_channel *trace_ust_create_channel(struct lttng_channel *attr, enum lttng_domain_type domain); enum lttng_error_code trace_ust_create_event(struct lttng_event *ev, char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, bool internal_event, struct ltt_ust_event **ust_event); struct ltt_ust_context *trace_ust_create_context( @@ -270,7 +270,7 @@ struct ltt_ust_channel *trace_ust_create_channel(struct lttng_channel *attr, static inline enum lttng_error_code trace_ust_create_event(struct lttng_event *ev, const char *filter_expression, - struct lttng_filter_bytecode *filter, + struct lttng_bytecode *filter, struct lttng_event_exclusion *exclusion, bool internal_event, struct ltt_ust_event **ust_event) { @@ -310,7 +310,7 @@ int trace_ust_match_context(const struct ltt_ust_context *uctx, } static inline struct ltt_ust_event *trace_ust_find_event(struct lttng_ht *ht, - char *name, struct lttng_filter_bytecode *filter, + char *name, struct lttng_bytecode *filter, enum lttng_ust_loglevel_type loglevel_type, int loglevel_value, struct lttng_event_exclusion *exclusion) { diff --git a/src/bin/lttng-sessiond/ust-abi-internal.h b/src/bin/lttng-sessiond/ust-abi-internal.h index 99da583d5..bb8b4f68f 100644 --- a/src/bin/lttng-sessiond/ust-abi-internal.h +++ b/src/bin/lttng-sessiond/ust-abi-internal.h @@ -87,6 +87,30 @@ struct lttng_ust_stream { */ } LTTNG_PACKED; +#define LTTNG_UST_TRIGGER_PADDING1 16 +#define LTTNG_UST_TRIGGER_PADDING2 (LTTNG_UST_SYM_NAME_LEN + 32) +struct lttng_ust_trigger { + uint64_t id; + enum lttng_ust_instrumentation instrumentation; + char name[LTTNG_UST_SYM_NAME_LEN]; /* event name */ + + enum lttng_ust_loglevel_type loglevel_type; + int loglevel; /* value, -1: all */ + char padding[LTTNG_UST_TRIGGER_PADDING1]; + + /* Per instrumentation type configuration */ + union { + char padding[LTTNG_UST_TRIGGER_PADDING2]; + } u; +} LTTNG_PACKED; + +#define LTTNG_TRIGGER_NOTIFICATION_PADDING 32 +struct lttng_ust_trigger_notification { + uint64_t id; + uint16_t capture_buf_size; + char padding[LTTNG_TRIGGER_NOTIFICATION_PADDING]; +} LTTNG_PACKED; + #define LTTNG_UST_EVENT_PADDING1 16 #define LTTNG_UST_EVENT_PADDING2 (LTTNG_UST_SYM_NAME_LEN + 32) struct lttng_ust_event { @@ -250,6 +274,16 @@ struct lttng_ust_filter_bytecode { char data[0]; } LTTNG_PACKED; +#define CAPTURE_BYTECODE_MAX_LEN 65536 +#define LTTNG_UST_CAPTURE_PADDING 32 +struct lttng_ust_capture_bytecode { + uint32_t len; + uint32_t reloc_offset; + uint64_t seqnum; + char padding[LTTNG_UST_CAPTURE_PADDING]; + char data[0]; +} LTTNG_PACKED; + #define LTTNG_UST_EXCLUSION_PADDING 32 struct lttng_ust_event_exclusion { uint32_t count; @@ -305,6 +339,9 @@ struct lttng_ust_event_exclusion { #define LTTNG_UST_FILTER _UST_CMD(0xA0) #define LTTNG_UST_EXCLUSION _UST_CMD(0xA1) +#define LTTNG_UST_TRIGGER_SEND_FD _UST_CMD(0xB0) +#define LTTNG_UST_TRIGGER_CREATE _UST_CMDW(0xB1, struct lttng_ust_trigger) + #define LTTNG_UST_ROOT_HANDLE 0 struct lttng_ust_obj; diff --git a/src/bin/lttng-sessiond/ust-app.c b/src/bin/lttng-sessiond/ust-app.c index a4fea821a..82b215e5d 100644 --- a/src/bin/lttng-sessiond/ust-app.c +++ b/src/bin/lttng-sessiond/ust-app.c @@ -19,7 +19,16 @@ #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "buffer-registry.h" @@ -34,6 +43,7 @@ #include "lttng-sessiond.h" #include "notification-thread-commands.h" #include "rotate.h" +#include "event.h" struct lttng_ht *ust_app_ht; struct lttng_ht *ust_app_ht_by_sock; @@ -313,6 +323,34 @@ void delete_ust_app_event(int sock, struct ust_app_event *ua_event, free(ua_event); } +/* + * Delete ust app token event_rule safely. RCU read lock must be held before calling + * this function. TODO: or does it???? + */ +static +void delete_ust_app_token_event_rule(int sock, struct ust_app_token_event_rule *ua_token, + struct ust_app *app) +{ + int ret; + + assert(ua_token); + + if (ua_token->exclusion != NULL) + free(ua_token->exclusion); + if (ua_token->obj != NULL) { + pthread_mutex_lock(&app->sock_lock); + ret = ustctl_release_object(sock, ua_token->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0 && ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { + ERR("UST app sock %d release event obj failed with ret %d", + sock, ret); + } + free(ua_token->obj); + } + lttng_trigger_put(ua_token->trigger); + free(ua_token); +} + /* * Release ust data object of the given stream. * @@ -898,6 +936,8 @@ void delete_ust_app(struct ust_app *app) { int ret, sock; struct ust_app_session *ua_sess, *tmp_ua_sess; + struct lttng_ht_iter iter; + struct ust_app_token_event_rule *token; /* * The session list lock must be held during this function to guarantee @@ -917,9 +957,23 @@ void delete_ust_app(struct ust_app *app) rcu_read_unlock(); } + /* Wipe token associated with the app */ + cds_lfht_for_each_entry(app->tokens_ht->ht, &iter.iter, token, + node.node) { + ret = lttng_ht_del(app->tokens_ht, &iter); + assert(!ret); + delete_ust_app_token_event_rule(app->sock, token, app); + } + ht_cleanup_push(app->sessions); ht_cleanup_push(app->ust_sessions_objd); ht_cleanup_push(app->ust_objd); + ht_cleanup_push(app->tokens_ht); + + ustctl_release_object(sock, app->token_communication.handle); + free(app->token_communication.handle); + + lttng_pipe_destroy(app->token_communication.trigger_event_pipe); /* * Wait until we have deleted the application from the sock hash table @@ -1125,6 +1179,51 @@ error: return NULL; } +/* + * Alloc new UST app token event rule. + */ +static struct ust_app_token_event_rule *alloc_ust_app_token_event_rule( + struct lttng_trigger *trigger) +{ + struct ust_app_token_event_rule *ua_token; + struct lttng_condition *condition = NULL; + struct lttng_event_rule *event_rule = NULL; + + ua_token = zmalloc(sizeof(struct ust_app_token_event_rule)); + if (ua_token == NULL) { + PERROR("Failed to allocate ust_app_token_event_rule structure"); + goto error; + } + + /* Get reference of the trigger */ + /* TODO should this be like lttng_event_rule_get with a returned bool? */ + lttng_trigger_get(trigger); + + ua_token->enabled = 1; + ua_token->token = lttng_trigger_get_key(trigger); + lttng_ht_node_init_u64(&ua_token->node, ua_token->token); + + condition = lttng_trigger_get_condition(trigger); + assert(condition); + assert(lttng_condition_get_type(condition) == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT); + + assert(LTTNG_CONDITION_STATUS_OK == lttng_condition_event_rule_get_rule_no_const(condition, &event_rule)); + assert(event_rule); + + ua_token->trigger = trigger; + ua_token->filter = lttng_event_rule_get_filter_bytecode(event_rule); + ua_token->exclusion = lttng_event_rule_generate_exclusions(event_rule); + + /* TODO put capture here? or later*/ + + DBG3("UST app token event rule %" PRIu64 " allocated", ua_token->token); + + return ua_token; + +error: + return NULL; +} + /* * Alloc new UST app context. */ @@ -1166,50 +1265,51 @@ error: } /* - * Allocate a filter and copy the given original filter. + * Create a liblttng-ust filter bytecode from given bytecode. * * Return allocated filter or NULL on error. */ -static struct lttng_filter_bytecode *copy_filter_bytecode( - struct lttng_filter_bytecode *orig_f) +static struct lttng_ust_filter_bytecode * +create_ust_filter_bytecode_from_bytecode(const struct lttng_bytecode *orig_f) { - struct lttng_filter_bytecode *filter = NULL; + struct lttng_ust_filter_bytecode *filter = NULL; /* Copy filter bytecode */ filter = zmalloc(sizeof(*filter) + orig_f->len); if (!filter) { - PERROR("zmalloc alloc filter bytecode"); + PERROR("zmalloc alloc ust filter bytecode"); goto error; } + assert(sizeof(struct lttng_bytecode) == + sizeof(struct lttng_ust_filter_bytecode)); memcpy(filter, orig_f, sizeof(*filter) + orig_f->len); - error: return filter; } /* - * Create a liblttng-ust filter bytecode from given bytecode. + * Create a liblttng-ust capture bytecode from given bytecode. * * Return allocated filter or NULL on error. */ -static struct lttng_ust_filter_bytecode *create_ust_bytecode_from_bytecode( - struct lttng_filter_bytecode *orig_f) +static struct lttng_ust_capture_bytecode * +create_ust_capture_bytecode_from_bytecode(const struct lttng_bytecode *orig_f) { - struct lttng_ust_filter_bytecode *filter = NULL; + struct lttng_ust_capture_bytecode *capture = NULL; - /* Copy filter bytecode */ - filter = zmalloc(sizeof(*filter) + orig_f->len); - if (!filter) { - PERROR("zmalloc alloc ust filter bytecode"); + /* Copy capture bytecode */ + capture = zmalloc(sizeof(*capture) + orig_f->len); + if (!capture) { + PERROR("zmalloc alloc ust capture bytecode"); goto error; } - assert(sizeof(struct lttng_filter_bytecode) == - sizeof(struct lttng_ust_filter_bytecode)); - memcpy(filter, orig_f, sizeof(*filter) + orig_f->len); + assert(sizeof(struct lttng_bytecode) == + sizeof(struct lttng_ust_capture_bytecode)); + memcpy(capture, orig_f, sizeof(*capture) + orig_f->len); error: - return filter; + return capture; } /* @@ -1264,7 +1364,7 @@ error: * Return an ust_app_event object or NULL on error. */ static struct ust_app_event *find_ust_app_event(struct lttng_ht *ht, - const char *name, const struct lttng_filter_bytecode *filter, + const char *name, const struct lttng_bytecode *filter, int loglevel_value, const struct lttng_event_exclusion *exclusion) { @@ -1297,6 +1397,32 @@ end: return event; } +/* + * Lookup for an ust app tokens based on a token id. + * + * Return an ust_app_token_event_rule object or NULL on error. + */ +static struct ust_app_token_event_rule *find_ust_app_token_event_rule(struct lttng_ht *ht, + uint64_t token) +{ + struct lttng_ht_iter iter; + struct lttng_ht_node_u64 *node; + struct ust_app_token_event_rule *token_event_rule = NULL; + + assert(ht); + + lttng_ht_lookup(ht, &token, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (node == NULL) { + DBG2("UST app token %" PRIu64 " not found", token); + goto end; + } + + token_event_rule = caa_container_of(node, struct ust_app_token_event_rule, node); +end: + return token_event_rule; +} + /* * Create the channel context on the tracer. * @@ -1343,33 +1469,79 @@ error: /* * Set the filter on the tracer. */ -static -int set_ust_event_filter(struct ust_app_event *ua_event, - struct ust_app *app) +static int set_ust_filter(struct ust_app *app, + const struct lttng_bytecode *bytecode, + struct lttng_ust_object_data *ust_object) { int ret; struct lttng_ust_filter_bytecode *ust_bytecode = NULL; health_code_update(); - if (!ua_event->filter) { - ret = 0; + ust_bytecode = create_ust_filter_bytecode_from_bytecode(bytecode); + if (!ust_bytecode) { + ret = -LTTNG_ERR_NOMEM; goto error; } + pthread_mutex_lock(&app->sock_lock); + ret = ustctl_set_filter(app->sock, ust_bytecode, + ust_object); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { + ERR("UST app set filter failed for object %p of app (pid: %d) " + "with ret %d", ust_object, app->pid, ret); + } else { + /* + * This is normal behavior, an application can die during the + * creation process. Don't report an error so the execution can + * continue normally. + */ + ret = 0; + DBG3("UST app set filter. Application is dead."); + } + goto error; + } + + DBG2("UST filter set for object %p successfully", ust_object); + +error: + health_code_update(); + free(ust_bytecode); + return ret; +} - ust_bytecode = create_ust_bytecode_from_bytecode(ua_event->filter); +/* + * Set a capture bytecode for the passed object. + * The seqnum enforce the ordering at runtime and on reception. + */ +static int set_ust_capture(struct ust_app *app, + const struct lttng_bytecode *bytecode, + unsigned int seqnum, + struct lttng_ust_object_data *ust_object) +{ + int ret; + struct lttng_ust_capture_bytecode *ust_bytecode = NULL; + + health_code_update(); + + ust_bytecode = create_ust_capture_bytecode_from_bytecode(bytecode); if (!ust_bytecode) { ret = -LTTNG_ERR_NOMEM; goto error; } + + /* Set the seqnum */ + ust_bytecode->seqnum = seqnum; + pthread_mutex_lock(&app->sock_lock); - ret = ustctl_set_filter(app->sock, ust_bytecode, - ua_event->obj); + ret = ustctl_set_capture(app->sock, ust_bytecode, + ust_object); pthread_mutex_unlock(&app->sock_lock); if (ret < 0) { if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { - ERR("UST app event %s filter failed for app (pid: %d) " - "with ret %d", ua_event->attr.name, app->pid, ret); + ERR("UST app set capture failed for object %p of app (pid: %d) " + "with ret %d", ust_object, app->pid, ret); } else { /* * This is normal behavior, an application can die during the @@ -1377,12 +1549,12 @@ int set_ust_event_filter(struct ust_app_event *ua_event, * continue normally. */ ret = 0; - DBG3("UST app filter event failed. Application is dead."); + DBG3("UST app set capture. Application is dead."); } goto error; } - DBG2("UST filter set successfully for event %s", ua_event->name); + DBG2("UST capture set for object %p successfully", ust_object); error: health_code_update(); @@ -1414,33 +1586,30 @@ end: /* * Set event exclusions on the tracer. */ -static -int set_ust_event_exclusion(struct ust_app_event *ua_event, - struct ust_app *app) +static int set_ust_exclusions(struct ust_app *app, + struct lttng_event_exclusion *exclusions, + struct lttng_ust_object_data *ust_object) { int ret; - struct lttng_ust_event_exclusion *ust_exclusion = NULL; + struct lttng_ust_event_exclusion *ust_exclusions = NULL; - health_code_update(); + assert(exclusions && exclusions->count > 0); - if (!ua_event->exclusion || !ua_event->exclusion->count) { - ret = 0; - goto error; - } + health_code_update(); - ust_exclusion = create_ust_exclusion_from_exclusion( - ua_event->exclusion); - if (!ust_exclusion) { + ust_exclusions = create_ust_exclusion_from_exclusion( + exclusions); + if (!ust_exclusions) { ret = -LTTNG_ERR_NOMEM; goto error; } pthread_mutex_lock(&app->sock_lock); - ret = ustctl_set_exclusion(app->sock, ust_exclusion, ua_event->obj); + ret = ustctl_set_exclusion(app->sock, ust_exclusions, ust_object); pthread_mutex_unlock(&app->sock_lock); if (ret < 0) { if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { - ERR("UST app event %s exclusions failed for app (pid: %d) " - "with ret %d", ua_event->attr.name, app->pid, ret); + ERR("UST app exclusions failed for object %p of app (pid: %d) " + "with ret %d", ust_object, app->pid, ret); } else { /* * This is normal behavior, an application can die during the @@ -1448,37 +1617,36 @@ int set_ust_event_exclusion(struct ust_app_event *ua_event, * continue normally. */ ret = 0; - DBG3("UST app event exclusion failed. Application is dead."); + DBG3("UST app set exclusions failed. Application is dead."); } goto error; } - DBG2("UST exclusion set successfully for event %s", ua_event->name); + DBG2("UST exclusions set successfully for object %p", ust_object); error: health_code_update(); - free(ust_exclusion); + free(ust_exclusions); return ret; } /* * Disable the specified event on to UST tracer for the UST session. */ -static int disable_ust_event(struct ust_app *app, - struct ust_app_session *ua_sess, struct ust_app_event *ua_event) +static int disable_ust_object(struct ust_app *app, + struct lttng_ust_object_data *object) { int ret; health_code_update(); pthread_mutex_lock(&app->sock_lock); - ret = ustctl_disable(app->sock, ua_event->obj); + ret = ustctl_disable(app->sock, object); pthread_mutex_unlock(&app->sock_lock); if (ret < 0) { if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { - ERR("UST app event %s disable failed for app (pid: %d) " - "and session handle %d with ret %d", - ua_event->attr.name, app->pid, ua_sess->handle, ret); + ERR("UST app disable failed for object %p app (pid: %d) with ret %d", + object, app->pid, ret); } else { /* * This is normal behavior, an application can die during the @@ -1491,8 +1659,8 @@ static int disable_ust_event(struct ust_app *app, goto error; } - DBG2("UST app event %s disabled successfully for app (pid: %d)", - ua_event->attr.name, app->pid); + DBG2("UST app object %p disabled successfully for app (pid: %d)", + object, app->pid); error: health_code_update(); @@ -1580,21 +1748,19 @@ error: /* * Enable the specified event on to UST tracer for the UST session. */ -static int enable_ust_event(struct ust_app *app, - struct ust_app_session *ua_sess, struct ust_app_event *ua_event) +static int enable_ust_object(struct ust_app *app, struct lttng_ust_object_data *ust_object) { int ret; health_code_update(); pthread_mutex_lock(&app->sock_lock); - ret = ustctl_enable(app->sock, ua_event->obj); + ret = ustctl_enable(app->sock, ust_object); pthread_mutex_unlock(&app->sock_lock); if (ret < 0) { if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { - ERR("UST app event %s enable failed for app (pid: %d) " - "and session handle %d with ret %d", - ua_event->attr.name, app->pid, ua_sess->handle, ret); + ERR("UST app enable failed for object %p app (pid: %d) with ret %d", + ust_object, app->pid, ret); } else { /* * This is normal behavior, an application can die during the @@ -1602,13 +1768,13 @@ static int enable_ust_event(struct ust_app *app, * continue normally. */ ret = 0; - DBG3("UST app enable event failed. Application is dead."); + DBG3("UST app enable failed. Application is dead."); } goto error; } - DBG2("UST app event %s enabled successfully for app (pid: %d)", - ua_event->attr.name, app->pid); + DBG2("UST app object %p enabled successfully for app (pid: %d)", + ust_object, app->pid); error: health_code_update(); @@ -1704,14 +1870,14 @@ int create_ust_event(struct ust_app *app, struct ust_app_session *ua_sess, ua_event->handle = ua_event->obj->handle; - DBG2("UST app event %s created successfully for pid:%d", - ua_event->attr.name, app->pid); + DBG2("UST app event %s created successfully for pid:%d object: %p", + ua_event->attr.name, app->pid, ua_event->obj); health_code_update(); /* Set filter if one is present. */ if (ua_event->filter) { - ret = set_ust_event_filter(ua_event, app); + ret = set_ust_filter(app, ua_event->filter, ua_event->obj); if (ret < 0) { goto error; } @@ -1719,7 +1885,7 @@ int create_ust_event(struct ust_app *app, struct ust_app_session *ua_sess, /* Set exclusions for the event */ if (ua_event->exclusion) { - ret = set_ust_event_exclusion(ua_event, app); + ret = set_ust_exclusions(app, ua_event->exclusion, ua_event->obj); if (ret < 0) { goto error; } @@ -1731,7 +1897,7 @@ int create_ust_event(struct ust_app *app, struct ust_app_session *ua_sess, * We now need to explicitly enable the event, since it * is now disabled at creation. */ - ret = enable_ust_event(app, ua_sess, ua_event); + ret = enable_ust_object(app, ua_event->obj); if (ret < 0) { /* * If we hit an EPERM, something is wrong with our enable call. If @@ -1758,6 +1924,186 @@ error: return ret; } +static +void init_ust_trigger_from_event_rule(const struct lttng_event_rule *rule, struct lttng_ust_trigger *trigger) +{ + enum lttng_event_rule_status status; + enum lttng_loglevel_type loglevel_type; + enum lttng_ust_loglevel_type ust_loglevel_type = LTTNG_UST_LOGLEVEL_ALL; + int loglevel = -1; + const char *pattern; + + /* For now only LTTNG_EVENT_RULE_TYPE_TRACEPOINT are supported */ + assert(lttng_event_rule_get_type(rule) == LTTNG_EVENT_RULE_TYPE_TRACEPOINT); + + memset(trigger, 0, sizeof(*trigger)); + + if (lttng_event_rule_is_agent(rule)) { + /* + * Special event for agents + * The actual meat of the event is in the filter that will be + * attached later on. + * Set the default values for the agent event. + */ + pattern = event_get_default_agent_ust_name(lttng_event_rule_get_domain_type(rule)); + loglevel = 0; + ust_loglevel_type = LTTNG_UST_LOGLEVEL_ALL; + } else { + status = lttng_event_rule_tracepoint_get_pattern(rule, &pattern); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + /* At this point this is a fatal error */ + assert(0); + } + + status = lttng_event_rule_tracepoint_get_loglevel_type( + rule, &loglevel_type); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + /* At this point this is a fatal error */ + assert(0); + } + + switch (loglevel_type) { + case LTTNG_EVENT_LOGLEVEL_ALL: + ust_loglevel_type = LTTNG_UST_LOGLEVEL_ALL; + break; + case LTTNG_EVENT_LOGLEVEL_RANGE: + ust_loglevel_type = LTTNG_UST_LOGLEVEL_RANGE; + break; + case LTTNG_EVENT_LOGLEVEL_SINGLE: + ust_loglevel_type = LTTNG_UST_LOGLEVEL_SINGLE; + break; + } + + if (loglevel_type != LTTNG_EVENT_LOGLEVEL_ALL) { + status = lttng_event_rule_tracepoint_get_loglevel( + rule, &loglevel); + assert(status == LTTNG_EVENT_RULE_STATUS_OK); + } + } + + trigger->instrumentation = LTTNG_UST_TRACEPOINT; + strncpy(trigger->name, pattern, LTTNG_UST_SYM_NAME_LEN - 1); + trigger->loglevel_type = ust_loglevel_type; + trigger->loglevel = loglevel; +} + +/* + * Create the specified event rule token onto the UST tracer for a UST app. + */ +static +int create_ust_token_event_rule(struct ust_app *app, struct ust_app_token_event_rule *ua_token) +{ + int ret = 0; + struct lttng_ust_trigger trigger; + struct lttng_condition *condition = NULL; + struct lttng_event_rule *event_rule = NULL; + unsigned int capture_bytecode_count = 0; + + health_code_update(); + assert(app->token_communication.handle); + + condition = lttng_trigger_get_condition(ua_token->trigger); + assert(condition); + assert(lttng_condition_get_type(condition) == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT); + + lttng_condition_event_rule_get_rule_no_const(condition, &event_rule); + assert(event_rule); + assert(lttng_event_rule_get_type(event_rule) == LTTNG_EVENT_RULE_TYPE_TRACEPOINT); + /* Should we also test for UST at this point, or do we trust all the + * upper level? */ + + init_ust_trigger_from_event_rule(event_rule, &trigger); + trigger.id = ua_token->token; + + /* Create UST trigger on tracer */ + pthread_mutex_lock(&app->sock_lock); + ret = ustctl_create_trigger(app->sock, &trigger, app->token_communication.handle, &ua_token->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { + abort(); + ERR("Error ustctl create trigger %s for app pid: %d with ret %d", + trigger.name, app->pid, ret); + } else { + /* + * This is normal behavior, an application can die during the + * creation process. Don't report an error so the execution can + * continue normally. + */ + ret = 0; + DBG3("UST app create event failed. Application is dead."); + } + goto error; + } + + ua_token->handle = ua_token->obj->handle; + + DBG2("UST app event %s created successfully for pid:%d object: %p", + trigger.name, app->pid, ua_token->obj); + + health_code_update(); + + /* Set filter if one is present. */ + if (ua_token->filter) { + ret = set_ust_filter(app, ua_token->filter, ua_token->obj); + if (ret < 0) { + goto error; + } + } + + /* Set exclusions for the event */ + if (ua_token->exclusion) { + ret = set_ust_exclusions(app, ua_token->exclusion, ua_token->obj); + if (ret < 0) { + goto error; + } + } + + /* Set the capture bytecode + * TODO: do we want to emulate what is done with exclusion and provide + * and object with a count of capture bytecode? instead of multiple + * call? + * */ + capture_bytecode_count = lttng_trigger_get_capture_bytecode_count(ua_token->trigger); + for (unsigned int i = 0; i < capture_bytecode_count; i++) { + const struct lttng_bytecode *capture_bytecode = lttng_trigger_get_capture_bytecode_at_index(ua_token->trigger, i); + ret = set_ust_capture(app, capture_bytecode, i, ua_token->obj); + if (ret < 0) { + goto error; + } + } + + /* + * We now need to explicitly enable the event, since it + * is disabled at creation. + */ + ret = enable_ust_object(app, ua_token->obj); + if (ret < 0) { + /* + * If we hit an EPERM, something is wrong with our enable call. If + * we get an EEXIST, there is a problem on the tracer side since we + * just created it. + */ + switch (ret) { + case -LTTNG_UST_ERR_PERM: + /* Code flow problem */ + assert(0); + case -LTTNG_UST_ERR_EXIST: + /* It's OK for our use case. */ + ret = 0; + break; + default: + break; + } + goto error; + } + ua_token->enabled = true; + +error: + health_code_update(); + return ret; +} + /* * Copy data between an UST app event and a LTT event. */ @@ -1776,7 +2122,7 @@ static void shadow_copy_event(struct ust_app_event *ua_event, /* Copy filter bytecode */ if (uevent->filter) { - ua_event->filter = copy_filter_bytecode(uevent->filter); + ua_event->filter = bytecode_copy(uevent->filter); /* Filter might be NULL here in case of ENONEM. */ } @@ -2341,7 +2687,7 @@ int enable_ust_app_event(struct ust_app_session *ua_sess, { int ret; - ret = enable_ust_event(app, ua_sess, ua_event); + ret = enable_ust_object(app, ua_event->obj); if (ret < 0) { goto error; } @@ -2360,7 +2706,7 @@ static int disable_ust_app_event(struct ust_app_session *ua_sess, { int ret; - ret = disable_ust_event(app, ua_sess, ua_event); + ret = disable_ust_object(app, ua_event->obj); if (ret < 0) { goto error; } @@ -3175,6 +3521,57 @@ error: return ret; } +/* + * Create UST app event and create it on the tracer side. + * + * Called with ust app session mutex held. + */ +static +int create_ust_app_token_event_rule(struct lttng_trigger *trigger, + struct ust_app *app) +{ + int ret = 0; + struct ust_app_token_event_rule *ua_token; + + ua_token = alloc_ust_app_token_event_rule(trigger); + if (ua_token == NULL) { + ret = -ENOMEM; + goto end; + } + + /* Create it on the tracer side */ + ret = create_ust_token_event_rule(app, ua_token); + if (ret < 0) { + /* + * Not found previously means that it does not exist on the + * tracer. If the application reports that the event existed, + * it means there is a bug in the sessiond or lttng-ust + * (or corruption, etc.) + */ + if (ret == -LTTNG_UST_ERR_EXIST) { + ERR("Tracer for application reported that a token event rule being created already existed: " + "token = \"%" PRIu64 "\", pid = %d, ppid = %d, uid = %d, gid = %d", + lttng_trigger_get_key(trigger), + app->pid, app->ppid, app->uid, + app->gid); + } + goto error; + } + + lttng_ht_add_unique_u64(app->tokens_ht, &ua_token->node); + + DBG2("UST app create token event rule %" PRIu64 " for PID %d completed", lttng_trigger_get_key(trigger), + app->pid); + +end: + return ret; + +error: + /* Valid. Calling here is already in a read side lock */ + delete_ust_app_token_event_rule(-1, ua_token, app); + return ret; +} + /* * Create UST metadata and open it on the tracer side. * @@ -3319,6 +3716,7 @@ error: struct ust_app *ust_app_create(struct ust_register_msg *msg, int sock) { struct ust_app *lta = NULL; + struct lttng_pipe *trigger_event_source_pipe = NULL; assert(msg); assert(sock >= 0); @@ -3335,12 +3733,20 @@ struct ust_app *ust_app_create(struct ust_register_msg *msg, int sock) goto error; } + trigger_event_source_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!trigger_event_source_pipe) { + PERROR("Open trigger pipe"); + goto error; + } + lta = zmalloc(sizeof(struct ust_app)); if (lta == NULL) { PERROR("malloc"); goto error; } + lta->token_communication.trigger_event_pipe = trigger_event_source_pipe; + lta->ppid = msg->ppid; lta->uid = msg->uid; lta->gid = msg->gid; @@ -3359,6 +3765,7 @@ struct ust_app *ust_app_create(struct ust_register_msg *msg, int sock) lta->ust_objd = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); lta->ust_sessions_objd = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); lta->notify_sock = -1; + lta->tokens_ht = lttng_ht_new(0, LTTNG_HT_TYPE_U64); /* Copy name and make sure it's NULL terminated. */ strncpy(lta->name, msg->name, sizeof(lta->name)); @@ -3445,6 +3852,47 @@ int ust_app_version(struct ust_app *app) return ret; } +/* + * Setup the base trigger group. + * + * Return 0 on success else a negative value either an errno code or a + * LTTng-UST error code. + */ +int ust_app_setup_trigger_group(struct ust_app *app) +{ + int ret; + int writefd; + struct lttng_ust_object_data *group = NULL; + enum lttng_error_code lttng_ret; + + assert(app); + + /* Get the write side of the pipe */ + writefd = lttng_pipe_get_writefd(app->token_communication.trigger_event_pipe); + + pthread_mutex_lock(&app->sock_lock); + ret = ustctl_create_trigger_group(app->sock, writefd, &group); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + ERR("UST app %d create_trigger_group failed with ret %d", app->sock, ret); + goto end; + } + + app->token_communication.handle = group; + + lttng_ret = notification_thread_command_add_application( + notification_thread_handle, app->token_communication.trigger_event_pipe); + if (lttng_ret != LTTNG_OK) { + /* TODO: error */ + ret = - 1; + ERR("Failed to add channel to notification thread"); + goto end; + } + +end: + return ret; +} + /* * Unregister app by removing it from the global traceable app list and freeing * the data struct. @@ -3453,6 +3901,7 @@ int ust_app_version(struct ust_app *app) */ void ust_app_unregister(int sock) { + enum lttng_error_code ret_code; struct ust_app *lta; struct lttng_ht_node_ulong *node; struct lttng_ht_iter ust_app_sock_iter; @@ -3558,6 +4007,13 @@ void ust_app_unregister(int sock) lta->pid); } + ret_code = notification_thread_command_remove_application( + notification_thread_handle, + lta->token_communication.trigger_event_pipe); + if (ret_code != LTTNG_OK) { + ERR("Failed to remove application from notification thread"); + } + /* Free memory */ call_rcu(<a->pid_n.head, delete_ust_app_rcu); @@ -4994,6 +5450,124 @@ end: return ret; } +static +void ust_app_synchronize_tokens(struct ust_app *app) +{ + int ret = 0; + enum lttng_error_code ret_code; + enum lttng_trigger_status t_status; + struct lttng_ht_iter app_trigger_iter; + struct lttng_triggers *triggers; + struct ust_app_token_event_rule *token_event_rule_element; + unsigned int count; + + rcu_read_lock(); + /* TODO: is this necessary to protect against new trigger being added ? + * notification_trigger_tokens_ht is still the backing data structure + * for this listing. Leave it there for now. + */ + pthread_mutex_lock(¬ification_trigger_tokens_ht_lock); + ret_code = notification_thread_command_get_tokens( + notification_thread_handle, &triggers); + if (ret_code != LTTNG_OK) { + ret = -1; + goto end; + } + + assert(triggers); + + t_status = lttng_triggers_get_count(triggers, &count); + if (t_status != LTTNG_TRIGGER_STATUS_OK) { + ret = -1; + goto end; + } + + for (unsigned int i = 0; i < count; i++) { + struct lttng_condition *condition; + struct lttng_event_rule *event_rule; + struct lttng_trigger *trigger; + struct ust_app_token_event_rule *ua_token; + uint64_t token; + + trigger = lttng_triggers_get_pointer_of_index(triggers, i); + assert(trigger); + + /* TODO: error checking and type checking */ + token = lttng_trigger_get_key(trigger); + condition = lttng_trigger_get_condition(trigger); + (void) lttng_condition_event_rule_get_rule_no_const(condition, &event_rule); + + if (lttng_event_rule_get_domain_type(event_rule) == LTTNG_DOMAIN_KERNEL) { + /* Skip kernel related trigger */ + continue; + } + + /* Iterate over all known token trigger */ + ua_token = find_ust_app_token_event_rule(app->tokens_ht, token); + if (!ua_token) { + ret = create_ust_app_token_event_rule(trigger, app); + if (ret < 0) { + goto end; + } + } + } + + /* Remove all unknown trigger from the app + * TODO find a way better way then this, do it on the unregister command + * and be specific on the token to remove instead of going over all + * trigger known to the app. This is sub optimal. + */ + cds_lfht_for_each_entry (app->tokens_ht->ht, &app_trigger_iter.iter, + token_event_rule_element, node.node) { + uint64_t token; + bool found = false; + + token = token_event_rule_element->token; + + /* + * Check if the app event trigger still exists on the + * notification side. + * TODO: might want to change the backing data struct of the + * lttng_triggers object to allow quick lookup? + * For kernel mostly all of this can be removed once we delete + * on a per trigger basis. + */ + + for (unsigned int i = 0; i < count; i++) { + struct lttng_trigger *trigger; + uint64_t inner_token; + + trigger = lttng_triggers_get_pointer_of_index( + triggers, i); + assert(trigger); + + inner_token = lttng_trigger_get_key(trigger); + + if (inner_token == token) { + found = true; + break; + } + } + + if (found) { + /* Still valid */ + continue; + } + + /* TODO: This is fucking ugly API for fuck sake */ + assert(!lttng_ht_del(app->tokens_ht, &app_trigger_iter)); + + (void) disable_ust_object(app, token_event_rule_element->obj); + + delete_ust_app_token_event_rule(app->sock, token_event_rule_element, app); + } +end: + lttng_triggers_destroy(triggers); + rcu_read_unlock(); + pthread_mutex_unlock(¬ification_trigger_tokens_ht_lock); + return; +} + /* * The caller must ensure that the application is compatible and is tracked * by the process attribute trackers. @@ -5135,6 +5709,21 @@ void ust_app_global_update(struct ltt_ust_session *usess, struct ust_app *app) } } +void ust_app_global_update_tokens(struct ust_app *app) +{ + DBG2("UST app global update token for app sock %d", app->sock); + + if (!app->compatible) { + return; + } + if (app->token_communication.handle == NULL) { + WARN("UST app global update token for app sock %d skipped since communcation handle is null", app->sock); + return; + } + + ust_app_synchronize_tokens(app); +} + /* * Called with session lock held. */ @@ -5150,6 +5739,18 @@ void ust_app_global_update_all(struct ltt_ust_session *usess) rcu_read_unlock(); } +void ust_app_global_update_all_tokens(void) +{ + struct lttng_ht_iter iter; + struct ust_app *app; + + rcu_read_lock(); + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + ust_app_global_update_tokens(app); + } + rcu_read_unlock(); +} + /* * Add context to a specific channel for global UST domain. */ diff --git a/src/bin/lttng-sessiond/ust-app.h b/src/bin/lttng-sessiond/ust-app.h index 6f3588a54..fab503c1b 100644 --- a/src/bin/lttng-sessiond/ust-app.h +++ b/src/bin/lttng-sessiond/ust-app.h @@ -22,7 +22,7 @@ /* Process name (short). */ #define UST_APP_PROCNAME_LEN 16 -struct lttng_filter_bytecode; +struct lttng_bytecode; struct lttng_ust_filter_bytecode; extern int ust_consumerd64_fd, ust_consumerd32_fd; @@ -39,7 +39,7 @@ struct ust_app_notify_sock_obj { struct ust_app_ht_key { const char *name; - const struct lttng_filter_bytecode *filter; + const struct lttng_bytecode *filter; enum lttng_ust_loglevel_type loglevel_type; const struct lttng_event_exclusion *exclusion; }; @@ -108,7 +108,20 @@ struct ust_app_event { struct lttng_ust_event attr; char name[LTTNG_UST_SYM_NAME_LEN]; struct lttng_ht_node_str node; - struct lttng_filter_bytecode *filter; + struct lttng_bytecode *filter; + struct lttng_event_exclusion *exclusion; +}; + +struct ust_app_token_event_rule { + int enabled; + int handle; + struct lttng_ust_object_data *obj; + struct lttng_trigger *trigger; + uint64_t token; + struct lttng_ht_node_u64 node; + /* The event_rule object own this pointer */ + const struct lttng_bytecode *filter; + /* The event_rule object own this pointer */ struct lttng_event_exclusion *exclusion; }; @@ -292,6 +305,14 @@ struct ust_app { * Used for path creation */ time_t registration_time; + /* + * Trigger + */ + struct { + struct lttng_ust_object_data *handle; + struct lttng_pipe *trigger_event_pipe; + } token_communication; + struct lttng_ht *tokens_ht; }; #ifdef HAVE_LIBLTTNG_UST_CTL @@ -319,6 +340,8 @@ int ust_app_add_ctx_channel_glb(struct ltt_ust_session *usess, struct ltt_ust_channel *uchan, struct ltt_ust_context *uctx); void ust_app_global_update(struct ltt_ust_session *usess, struct ust_app *app); void ust_app_global_update_all(struct ltt_ust_session *usess); +void ust_app_global_update_tokens(struct ust_app *app); +void ust_app_global_update_all_tokens(void); void ust_app_clean_list(void); int ust_app_ht_alloc(void); @@ -355,6 +378,8 @@ int ust_app_release_object(struct ust_app *app, struct lttng_ust_object_data *data); enum lttng_error_code ust_app_clear_session(struct ltt_session *session); +int ust_app_setup_trigger_group(struct ust_app *app); + static inline int ust_app_supported(void) { @@ -443,6 +468,17 @@ static inline void ust_app_global_update(struct ltt_ust_session *usess, struct ust_app *app) {} static inline +void ust_app_global_update_tokens(struct ust_app *app) +{} +static inline +void ust_app_global_update_all_tokens(void) +{} +static inline +int ust_app_setup_trigger_group(struct ust_app *app) +{ + return 0; +} +static inline int ust_app_disable_channel_glb(struct ltt_ust_session *usess, struct ltt_ust_channel *uchan) { diff --git a/src/bin/lttng-sessiond/ust-ctl-internal.h b/src/bin/lttng-sessiond/ust-ctl-internal.h index 7c245bdce..0e17f4f83 100644 --- a/src/bin/lttng-sessiond/ust-ctl-internal.h +++ b/src/bin/lttng-sessiond/ust-ctl-internal.h @@ -79,12 +79,22 @@ int ustctl_set_filter(int sock, struct lttng_ust_filter_bytecode *bytecode, struct lttng_ust_object_data *obj_data); int ustctl_set_exclusion(int sock, struct lttng_ust_event_exclusion *exclusion, struct lttng_ust_object_data *obj_data); +int ustctl_set_capture(int sock, struct lttng_ust_capture_bytecode *bytecode, + struct lttng_ust_object_data *obj_data); int ustctl_enable(int sock, struct lttng_ust_object_data *object); int ustctl_disable(int sock, struct lttng_ust_object_data *object); int ustctl_start_session(int sock, int handle); int ustctl_stop_session(int sock, int handle); +int ustctl_create_trigger_group(int sock, + int pipe_fd, + struct lttng_ust_object_data **trigger_group_handle); +int ustctl_create_trigger(int sock, + struct lttng_ust_trigger *trigger, + struct lttng_ust_object_data *trigger_group_handle, + struct lttng_ust_object_data **trigger_data); + /* * ustctl_tracepoint_list returns a tracepoint list handle, or negative * error value. diff --git a/src/bin/lttng/Makefile.am b/src/bin/lttng/Makefile.am index d094c5aaf..96d7114a2 100644 --- a/src/bin/lttng/Makefile.am +++ b/src/bin/lttng/Makefile.am @@ -29,7 +29,11 @@ lttng_SOURCES = command.h conf.c conf.h commands/start.c \ commands/enable_rotation.c \ commands/disable_rotation.c \ commands/clear.c \ - utils.c utils.h lttng.c + commands/add_trigger.c \ + commands/list_triggers.c \ + commands/remove_trigger.c \ + utils.c utils.h lttng.c \ + uprobe.c uprobe.h lttng_CFLAGS = $(AM_CFLAGS) $(POPT_CFLAGS) @@ -37,4 +41,6 @@ lttng_LDADD = $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la \ $(top_builddir)/src/common/libcommon.la \ $(top_builddir)/src/common/config/libconfig.la \ $(top_builddir)/src/common/string-utils/libstring-utils.la \ + $(top_builddir)/src/common/filter/libfilter.la \ + $(top_builddir)/src/common/argpar/libargpar.la \ $(POPT_LIBS) diff --git a/src/bin/lttng/command.h b/src/bin/lttng/command.h index 8f1c7be40..bf0045210 100644 --- a/src/bin/lttng/command.h +++ b/src/bin/lttng/command.h @@ -77,6 +77,9 @@ DECL_COMMAND(rotate); DECL_COMMAND(enable_rotation); DECL_COMMAND(disable_rotation); DECL_COMMAND(clear); +DECL_COMMAND(add_trigger); +DECL_COMMAND(list_triggers); +DECL_COMMAND(remove_trigger); extern int cmd_help(int argc, const char **argv, const struct cmd_struct commands[]); diff --git a/src/bin/lttng/commands/add_trigger.c b/src/bin/lttng/commands/add_trigger.c new file mode 100644 index 000000000..b18d6d519 --- /dev/null +++ b/src/bin/lttng/commands/add_trigger.c @@ -0,0 +1,2031 @@ +#include + +#include "../command.h" +#include "../uprobe.h" + +#include "common/argpar/argpar.h" +#include "common/dynamic-array.h" +#include "common/string-utils/string-utils.h" +#include "common/utils.h" +#include "lttng/condition/event-rule.h" +#include "lttng/event-internal.h" +#include "lttng/event-expr.h" +#include +#include "lttng/event-rule/kprobe.h" +#include "lttng/event-rule/syscall.h" +#include +#include "lttng/event-rule/uprobe.h" +#include "common/filter/filter-ast.h" +#include "common/filter/filter-ir.h" +#include "common/dynamic-array.h" + +#ifdef LTTNG_EMBED_HELP +static const char help_msg[] = +#include +; +#endif + +enum { + OPT_HELP, + OPT_LIST_OPTIONS, + + OPT_CONDITION, + OPT_ACTION, + OPT_ID, + OPT_FIRE_ONCE_AFTER, + OPT_FIRE_EVERY, + + OPT_ALL, + OPT_FILTER, + OPT_EXCLUDE, + OPT_LOGLEVEL, + OPT_LOGLEVEL_ONLY, + + OPT_USERSPACE, + OPT_KERNEL, + OPT_LOG4J, + OPT_JUL, + OPT_PYTHON, + + OPT_FUNCTION, + OPT_PROBE, + OPT_USERSPACE_PROBE, + OPT_SYSCALL, + OPT_TRACEPOINT, + + OPT_NAME, + OPT_MAX_SIZE, + OPT_DATA_URL, + OPT_CTRL_URL, + + OPT_CAPTURE, +}; + +static const struct argpar_opt_descr event_rule_opt_descrs[] = { + { OPT_ALL, 'a', "all", false }, + { OPT_FILTER, 'f', "filter", true }, + { OPT_EXCLUDE, 'x', "exclude", true }, + { OPT_LOGLEVEL, '\0', "loglevel", true }, + { OPT_LOGLEVEL_ONLY, '\0', "loglevel-only", true }, + + /* Domains */ + { OPT_USERSPACE, 'u', "userspace", false }, + { OPT_KERNEL, 'k', "kernel", false }, + { OPT_LOG4J, 'l', "log4j", false }, + { OPT_JUL, 'j', "jul", false }, + { OPT_PYTHON, 'p', "python", false }, + + /* Event rule types */ + { OPT_FUNCTION, '\0', "function", true }, + { OPT_PROBE, '\0', "probe", true }, + { OPT_USERSPACE_PROBE, '\0', "userspace-probe", true }, + { OPT_SYSCALL, '\0', "syscall" }, + { OPT_TRACEPOINT, '\0', "tracepoint" }, + + /* Capture descriptor */ + { OPT_CAPTURE, '\0', "capture", true }, + + ARGPAR_OPT_DESCR_SENTINEL +}; + +static +bool assign_domain_type(enum lttng_domain_type *dest, + enum lttng_domain_type src) +{ + bool ret; + + if (*dest == LTTNG_DOMAIN_NONE || *dest == src) { + *dest = src; + ret = true; + } else { + ERR("Multiple domains specified."); + ret = false; + } + + return ret; +} + +static +bool assign_event_rule_type(enum lttng_event_rule_type *dest, + enum lttng_event_rule_type src) +{ + bool ret; + + if (*dest == LTTNG_EVENT_RULE_TYPE_UNKNOWN || *dest == src) { + *dest = src; + ret = true; + } else { + ERR("Multiple event type not supported."); + ret = false; + } + + return ret; +} + +static +bool assign_string(char **dest, const char *src, const char *opt_name) +{ + bool ret; + + if (*dest) { + ERR( + "Duplicate %s given.", opt_name); + goto error; + } + + *dest = strdup(src); + if (!*dest) { + ERR("Failed to allocate %s string.", opt_name); + goto error; + } + + ret = true; + goto end; + +error: + ret = false; + +end: + return ret; +} + +/* This is defined in enable_events.c. */ +LTTNG_HIDDEN +int create_exclusion_list_and_validate(const char *event_name, + const char *exclusions_arg, + char ***exclusion_list); + +/* + * Parse `str` as a log level in domain `domain_type`. Return -1 if the string + * is not recognized as a valid log level. + */ +static +int parse_loglevel_string(const char *str, enum lttng_domain_type domain_type) +{ + + switch (domain_type) { + case LTTNG_DOMAIN_UST: + return loglevel_str_to_value(str); + + case LTTNG_DOMAIN_LOG4J: + return loglevel_log4j_str_to_value(str); + + case LTTNG_DOMAIN_JUL: + return loglevel_jul_str_to_value(str); + + case LTTNG_DOMAIN_PYTHON: + return loglevel_python_str_to_value(str); + + default: + /* Invalid domain type. */ + abort(); + } +} + +static +struct lttng_event_expr *ir_op_load_expr_to_event_expr( + struct ir_load_expression *load_exp, const char *capture_str) +{ + struct ir_load_expression_op *load_expr_op = load_exp->child; + struct lttng_event_expr *event_expr = NULL; + char *provider_name = NULL; + + switch (load_expr_op->type) { + case IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT: + { + const char *field_name; + + load_expr_op = load_expr_op->next; + assert(load_expr_op); + assert(load_expr_op->type == IR_LOAD_EXPRESSION_GET_SYMBOL); + field_name = load_expr_op->u.symbol; + assert(field_name); + + event_expr = lttng_event_expr_event_payload_field_create(field_name); + if (!event_expr) { + fprintf(stderr, "Failed to create payload field event expression.\n"); + goto error; + } + + break; + } + + case IR_LOAD_EXPRESSION_GET_CONTEXT_ROOT: + { + const char *field_name; + + load_expr_op = load_expr_op->next; + assert(load_expr_op); + assert(load_expr_op->type == IR_LOAD_EXPRESSION_GET_SYMBOL); + field_name = load_expr_op->u.symbol; + assert(field_name); + + event_expr = lttng_event_expr_channel_context_field_create(field_name); + if (!event_expr) { + fprintf(stderr, "Failed to create channel context field event expression.\n"); + goto error; + } + + break; + } + + case IR_LOAD_EXPRESSION_GET_APP_CONTEXT_ROOT: + { + const char *field_name; + const char *colon; + const char *type_name; + + load_expr_op = load_expr_op->next; + assert(load_expr_op); + assert(load_expr_op->type == IR_LOAD_EXPRESSION_GET_SYMBOL); + field_name = load_expr_op->u.symbol; + assert(field_name); + + /* + * The field name needs to be of the form PROVIDER:TYPE. We + * split it here. + */ + colon = strchr(field_name, ':'); + if (!colon) { + fprintf(stderr, "Invalid app-specific context field name: missing colon in `%s`.\n", + field_name); + goto error; + } + + type_name = colon + 1; + if (*type_name == '\0') { + fprintf(stderr, + "Invalid app-specific context field name: missing type name after colon in `%s`.\n", + field_name); + goto error; + } + + provider_name = strndup(field_name, colon - field_name); + if (!provider_name) { + fprintf(stderr, "Failed to allocate string.\n"); + goto error; + } + + event_expr = lttng_event_expr_app_specific_context_field_create( + provider_name, type_name); + if (!event_expr) { + fprintf(stderr, + "Failed to create app-specific context field event expression.\n"); + goto error; + } + + break; + } + + default: + fprintf(stderr, "%s: unexpected load expr type %d.\n", + __func__, load_expr_op->type); + abort(); + } + + load_expr_op = load_expr_op->next; + + /* There may be a single array index after that. */ + if (load_expr_op->type == IR_LOAD_EXPRESSION_GET_INDEX) { + uint64_t index = load_expr_op->u.index; + struct lttng_event_expr *index_event_expr; + + index_event_expr = lttng_event_expr_array_field_element_create(event_expr, index); + if (!index_event_expr) { + fprintf(stderr, "Failed to create array field element event expression.\n"); + goto error; + } + + event_expr = index_event_expr; + load_expr_op = load_expr_op->next; + } + + switch (load_expr_op->type) { + case IR_LOAD_EXPRESSION_LOAD_FIELD: + /* + * This is what we expect, IR_LOAD_EXPRESSION_LOAD_FIELD is + * always found at the end of the chain. + */ + break; + case IR_LOAD_EXPRESSION_GET_SYMBOL: + fprintf(stderr, "Error: While parsing expression `%s`: Capturing subfields is not supported.\n", + capture_str); + goto error; + + default: + fprintf(stderr, "%s: unexpected load expression operator %s.\n", + __func__, ir_load_expression_type_str(load_expr_op->type)); + abort(); + } + + goto end; + +error: + lttng_event_expr_destroy(event_expr); + event_expr = NULL; + +end: + free(provider_name); + + return event_expr; +} + +static +struct lttng_event_expr *ir_op_load_to_event_expr(struct ir_op *ir, + const char *capture_str) +{ + struct lttng_event_expr *event_expr = NULL; + + assert(ir->op == IR_OP_LOAD); + + switch (ir->data_type) { + case IR_DATA_EXPRESSION: + { + struct ir_load_expression *ir_load_expr = ir->u.load.u.expression; + event_expr = ir_op_load_expr_to_event_expr(ir_load_expr, capture_str); + break; + } + + default: + fprintf(stderr, "%s: unexpected data type: %s.\n", __func__, + ir_data_type_str(ir->data_type)); + abort(); + } + + return event_expr; +} + +static +struct lttng_event_expr *ir_op_root_to_event_expr(struct ir_op *ir, + const char *capture_str) +{ + struct lttng_event_expr *event_expr = NULL; + + assert(ir->op == IR_OP_ROOT); + ir = ir->u.root.child; + + switch (ir->op) { + case IR_OP_LOAD: + event_expr = ir_op_load_to_event_expr(ir, capture_str); + break; + + case IR_OP_BINARY: + fprintf(stderr, "Error: While parsing expression `%s`: Binary operators are not allowed in capture expressions.\n", + capture_str); + break; + + case IR_OP_UNARY: + fprintf(stderr, "Error: While parsing expression `%s`: Unary operators are not allowed in capture expressions.\n", + capture_str); + break; + + case IR_OP_LOGICAL: + fprintf(stderr, "Error: While parsing expression `%s`: Logical operators are not allowed in capture expressions.\n", + capture_str); + break; + + default: + fprintf(stderr, "%s: unexpected IR op type: %s.\n", __func__, + ir_op_type_str(ir->op)); + abort(); + } + + return event_expr; +} + +static +void destroy_event_expr(void *ptr) +{ + lttng_event_expr_destroy(ptr); +} + +struct parse_event_rule_res { + /* Owned by this */ + struct lttng_event_rule *er; + + /* Array of `struct lttng_event_expr *` */ + struct lttng_dynamic_pointer_array capture_descriptors; +}; + +static +struct parse_event_rule_res parse_event_rule(int *argc, const char ***argv) +{ + enum lttng_domain_type domain_type = LTTNG_DOMAIN_NONE; + enum lttng_event_rule_type event_rule_type = LTTNG_EVENT_RULE_TYPE_UNKNOWN; + struct argpar_state *state; + struct argpar_item *item = NULL; + char *error = NULL; + int consumed_args = -1; + struct lttng_userspace_probe_location *userspace_probe_location = NULL; + struct parse_event_rule_res res = { 0 }; + struct lttng_event_expr *event_expr = NULL; + struct filter_parser_ctx *parser_ctx = NULL; + + /* Was the -a/--all flag provided? */ + bool all_events = false; + + /* Tracepoint name (non-option argument) */ + const char *tracepoint_name = NULL; + + /* Holds the argument of --probe / --userspace-probe. */ + char *source = NULL; + + /* Filter */ + char *filter = NULL; + + /* Exclude */ + char *exclude = NULL; + char **exclusion_list = NULL; + + /* Log level */ + char *loglevel_str = NULL; + bool loglevel_only = false; + + lttng_dynamic_pointer_array_init(&res.capture_descriptors, + destroy_event_expr); + state = argpar_state_create(*argc, *argv, event_rule_opt_descrs); + if (!state) { + ERR("Failed to allocate an argpar state."); + goto error; + } + + while (true) { + enum argpar_state_parse_next_status status; + + ARGPAR_ITEM_DESTROY_AND_RESET(item); + status = argpar_state_parse_next(state, &item, &error); + if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { + ERR("%s", error); + goto error; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { + /* Just stop parsing here. */ + break; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + break; + } + + assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt *item_opt = + (struct argpar_item_opt *) item; + + switch (item_opt->descr->id) { + /* Domains */ + case OPT_USERSPACE: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_UST)) { + goto error; + } + break; + + case OPT_KERNEL: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_KERNEL)) { + goto error; + } + break; + + case OPT_LOG4J: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_LOG4J)) { + goto error; + } + break; + + case OPT_JUL: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_JUL)) { + goto error; + } + break; + + case OPT_PYTHON: + if (!assign_domain_type(&domain_type, LTTNG_DOMAIN_PYTHON)) { + goto error; + } + break; + + /* Event rule types */ + case OPT_FUNCTION: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_KRETPROBE)) { + goto error; + } + break; + + case OPT_PROBE: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_KPROBE)) { + goto error; + } + + if (!assign_string(&source, item_opt->arg, "source")) { + goto error; + } + + break; + + case OPT_USERSPACE_PROBE: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_UPROBE)) { + goto error; + } + + if (!assign_string(&source, item_opt->arg, "source")) { + goto error; + } + break; + + case OPT_SYSCALL: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_SYSCALL)) { + goto error; + } + break; + + case OPT_TRACEPOINT: + if (!assign_event_rule_type(&event_rule_type, + LTTNG_EVENT_RULE_TYPE_TRACEPOINT)) { + goto error; + } + break; + + case OPT_ALL: + all_events = true; + break; + + case OPT_FILTER: + if (!assign_string(&filter, item_opt->arg, "--filter/-f")) { + goto error; + } + break; + + case OPT_EXCLUDE: + if (!assign_string(&exclude, item_opt->arg, "--exclude/-x")) { + goto error; + } + break; + + case OPT_LOGLEVEL: + case OPT_LOGLEVEL_ONLY: + if (!assign_string(&loglevel_str, item_opt->arg, "--loglevel/--loglevel-only")) { + goto error; + } + + loglevel_only = item_opt->descr->id == OPT_LOGLEVEL_ONLY; + break; + + case OPT_CAPTURE: + { + const char *capture_str = item_opt->arg; + int ret; + + ret = filter_parser_ctx_create_from_filter_expression( + capture_str, &parser_ctx); + if (ret) { + fprintf(stderr, "Failed to parse capture expression `%s`.\n", capture_str); + goto error; + } + + event_expr = ir_op_root_to_event_expr(parser_ctx->ir_root, + capture_str); + if (!event_expr) { + /* ir_op_root_to_event_expr has printed an error message. */ + goto error; + } + + ret = lttng_dynamic_pointer_array_add_pointer( + &res.capture_descriptors, + event_expr); + if (ret) { + goto error; + } + + /* The ownership of event expression was transferred to the dynamic array. */ + event_expr = NULL; + + break; + } + + default: + abort(); + } + } else { + struct argpar_item_non_opt *item_non_opt = + (struct argpar_item_non_opt *) item; + + /* + * Don't accept two non-option arguments/tracepoint + * names. + */ + if (tracepoint_name) { + ERR( + "Unexpected argument: %s", + item_non_opt->arg); + goto error; + } + + tracepoint_name = item_non_opt->arg; + } + } + + if (event_rule_type == LTTNG_EVENT_RULE_TYPE_UNKNOWN) { + event_rule_type = LTTNG_EVENT_RULE_TYPE_TRACEPOINT; + } + + /* + * Option -a is applicable to event rules of type tracepoint and + * syscall, and it is equivalent to using "*" as the tracepoint name. + */ + if (all_events) { + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + break; + default: + ERR("Can't use -a/--all with event rule of type %s.", + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + + if (tracepoint_name) { + ERR("Can't provide a tracepoint name with -a/--all."); + goto error; + } + + /* In which case, it's equivalent to tracepoint name "*". */ + tracepoint_name = "*"; + } + + /* + * A tracepoint name (or -a, for the event rule types that accept it) + * is required. + */ + if (!tracepoint_name) { + ERR("Need to provide either a tracepoint name or -a/--all."); + goto error; + } + + /* + * We don't support multiple tracepoint names for now. + */ + if (strchr(tracepoint_name, ',')) { + ERR("multiple tracepoint names are not supported at the moment."); + goto error; + } + + /* + * Update *argc and *argv so our caller can keep parsing what follows. + */ + consumed_args = argpar_state_get_ingested_orig_args(state); + assert(consumed_args >= 0); + *argc -= consumed_args; + *argv += consumed_args; + + /* Need to specify a domain. */ + if (domain_type == LTTNG_DOMAIN_NONE) { + ERR("Please specify a domain (-k/-u/-j)."); + goto error; + } + + /* Validate event rule type against domain. */ + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_KPROBE: + case LTTNG_EVENT_RULE_TYPE_KRETPROBE: + case LTTNG_EVENT_RULE_TYPE_UPROBE: + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + if (domain_type != LTTNG_DOMAIN_KERNEL) { + ERR("Event type not available for user-space tracing"); + goto error; + } + break; + + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + break; + + default: + abort(); + } + + /* + * Adding a filter to a probe, function or userspace-probe would be + * denied by the kernel tracer as it's not supported at the moment. We + * do an early check here to warn the user. + */ + if (filter && domain_type == LTTNG_DOMAIN_KERNEL) { + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + break; + default: + ERR("Filter expressions are not supported for %s events", + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + } + + /* If --exclude/-x was passed, split it into an exclusion list. */ + if (exclude) { + if (domain_type != LTTNG_DOMAIN_UST) { + ERR("Event name exclusions are not yet implemented for %s events", + get_domain_str(domain_type)); + goto error; + } + + + if (create_exclusion_list_and_validate(tracepoint_name, exclude, + &exclusion_list) != 0) { + ERR("Failed to create exclusion list."); + goto error; + } + } + + if (loglevel_str && event_rule_type != LTTNG_EVENT_RULE_TYPE_TRACEPOINT) { + ERR("Log levels are only application to tracepoint event rules."); + goto error; + } + + /* Finally, create the event rule object. */ + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + { + enum lttng_event_rule_status event_rule_status; + + res.er = lttng_event_rule_tracepoint_create(domain_type); + if (!res.er) { + ERR("Failed to create tracepoint event rule."); + goto error; + } + + /* Set pattern. */ + event_rule_status = + lttng_event_rule_tracepoint_set_pattern(res.er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set tracepoint pattern."); + goto error; + } + + /* Set filter. */ + if (filter) { + event_rule_status = + lttng_event_rule_tracepoint_set_filter( + res.er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set tracepoint filter expression."); + goto error; + } + } + + /* Set exclusion list. */ + if (exclusion_list) { + int n; + + /* Count number of items in exclusion list. */ + for (n = 0; exclusion_list[n]; n++); + + event_rule_status = + lttng_event_rule_tracepoint_set_exclusions( + res.er, + n, (const char **) exclusion_list); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set tracepoint exclusion list."); + goto error; + } + } + + if (loglevel_str) { + int loglevel; + + if (domain_type == LTTNG_DOMAIN_KERNEL) { + ERR("Log levels are not supported by the kernel tracer."); + goto error; + } + + loglevel = parse_loglevel_string( + loglevel_str, domain_type); + if (loglevel < 0) { + ERR("Failed to parse `%s` as a log level.", loglevel_str); + goto error; + } + + if (loglevel_only) { + event_rule_status = + lttng_event_rule_tracepoint_set_loglevel( + res.er, loglevel); + } else { + event_rule_status = + lttng_event_rule_tracepoint_set_loglevel_range( + res.er, loglevel); + } + + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set log level."); + goto error; + } + } + + break; + } + + case LTTNG_EVENT_RULE_TYPE_KPROBE: + { + enum lttng_event_rule_status event_rule_status; + + res.er = lttng_event_rule_kprobe_create(); + if (!res.er) { + ERR("Failed to create kprobe event rule."); + goto error; + } + + event_rule_status = lttng_event_rule_kprobe_set_name(res.er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set kprobe event rule's name."); + goto error; + } + + assert(source); + event_rule_status = lttng_event_rule_kprobe_set_source(res.er, source); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set kprobe event rule's source."); + goto error; + } + + break; + } + + case LTTNG_EVENT_RULE_TYPE_UPROBE: + { + int ret; + enum lttng_event_rule_status event_rule_status; + + ret = parse_userspace_probe_opts(source, &userspace_probe_location); + if (ret) { + ERR("Failed to parse userspace probe location."); + goto error; + } + + res.er = lttng_event_rule_uprobe_create(); + if (!res.er) { + ERR("Failed to create userspace probe event rule."); + goto error; + } + + event_rule_status = lttng_event_rule_uprobe_set_location(res.er, userspace_probe_location); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set userspace probe event rule's location."); + goto error; + } + + event_rule_status = lttng_event_rule_uprobe_set_name(res.er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set userspace probe event rule's name."); + goto error; + } + + break; + } + + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + { + enum lttng_event_rule_status event_rule_status; + + res.er = lttng_event_rule_syscall_create(); + if (!res.er) { + ERR("Failed to create syscall event rule."); + goto error; + } + + event_rule_status = lttng_event_rule_syscall_set_pattern(res.er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set syscall event rule's pattern."); + goto error; + } + + if (filter) { + event_rule_status = lttng_event_rule_syscall_set_filter( + res.er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set syscall event rule's filter expression."); + goto error; + } + } + + break; + } + + default: + ERR("%s: I don't support event rules of type `%s` at the moment.", __func__, + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + + goto end; + +error: + lttng_event_rule_destroy(res.er); + res.er = NULL; + lttng_dynamic_pointer_array_reset(&res.capture_descriptors); + +end: + if (parser_ctx) { + filter_parser_ctx_free(parser_ctx); + } + + lttng_event_expr_destroy(event_expr); + argpar_item_destroy(item); + free(error); + argpar_state_destroy(state); + free(filter); + free(exclude); + free(loglevel_str); + strutils_free_null_terminated_array_of_strings(exclusion_list); + lttng_userspace_probe_location_destroy(userspace_probe_location); + return res; +} + +static +struct lttng_condition *handle_condition_event(int *argc, const char ***argv) +{ + struct parse_event_rule_res res; + struct lttng_condition *c; + size_t i; + + res = parse_event_rule(argc, argv); + if (!res.er) { + c = NULL; + goto error; + } + + c = lttng_condition_event_rule_create(res.er); + if (!c) { + goto error; + } + + /* Event rule ownership moved to `c` */ + res.er = NULL; + + for (i = 0; i < lttng_dynamic_pointer_array_get_count(&res.capture_descriptors); + i++) { + enum lttng_condition_status status; + struct lttng_event_expr **expr = + lttng_dynamic_array_get_element( + &res.capture_descriptors.array, i); + + assert(expr); + assert(*expr); + status = lttng_condition_event_rule_append_capture_descriptor( + c, *expr); + if (status != LTTNG_CONDITION_STATUS_OK) { + if (status == LTTNG_CONDITION_STATUS_UNSUPPORTED) { + ERR("The capture feature is unsupported by the event-rule type"); + } + goto error; + } + + /* Ownership of event expression moved to `c` */ + *expr = NULL; + } + + goto end; + +error: + lttng_condition_destroy(c); + c = NULL; + +end: + lttng_dynamic_pointer_array_reset(&res.capture_descriptors); + lttng_event_rule_destroy(res.er); + return c; +} + +static +struct lttng_condition *handle_condition_session_consumed_size(int *argc, const char ***argv) +{ + struct lttng_condition *cond = NULL; + struct argpar_state *state = NULL; + struct argpar_item *item = NULL; + const char *threshold_arg = NULL; + const char *session_name_arg = NULL; + uint64_t threshold; + char *error = NULL; + enum lttng_condition_status condition_status; + + state = argpar_state_create(*argc, *argv, event_rule_opt_descrs); + if (!state) { + ERR("Failed to allocate an argpar state."); + goto error; + } + + while (true) { + enum argpar_state_parse_next_status status; + + ARGPAR_ITEM_DESTROY_AND_RESET(item); + status = argpar_state_parse_next(state, &item, &error); + if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { + ERR("%s", error); + goto error; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { + /* Just stop parsing here. */ + break; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + break; + } + + assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt *item_opt = + (struct argpar_item_opt *) item; + + switch (item_opt->descr->id) { + default: + abort(); + } + } else { + struct argpar_item_non_opt *item_non_opt; + + assert(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + + item_non_opt = (struct argpar_item_non_opt *) item; + + switch (item_non_opt->non_opt_index) { + case 0: + session_name_arg = item_non_opt->arg; + break; + case 1: + threshold_arg = item_non_opt->arg; + break; + default: + ERR("Unexpected argument `%s`.", + item_non_opt->arg); + goto error; + } + } + } + + *argc -= argpar_state_get_ingested_orig_args(state); + *argv += argpar_state_get_ingested_orig_args(state); + + if (!session_name_arg) { + ERR("Missing session name argument."); + goto error; + } + + if (!threshold_arg) { + ERR("Missing threshold argument."); + goto error; + } + + if (utils_parse_size_suffix(threshold_arg, &threshold) != 0) { + ERR("Failed to parse `%s` as a size.", threshold_arg); + goto error; + } + + cond = lttng_condition_session_consumed_size_create(); + if (!cond) { + ERR("Failed to allocate a session consumed size condition."); + goto error; + } + + condition_status = lttng_condition_session_consumed_size_set_session_name( + cond, session_name_arg); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to set session consumed size condition session name."); + goto error; + } + + + condition_status = lttng_condition_session_consumed_size_set_threshold( + cond, threshold); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to set session consumed size condition threshold."); + goto error; + } + + goto end; + +error: + lttng_condition_destroy(cond); + cond = NULL; + +end: + argpar_state_destroy(state); + argpar_item_destroy(item); + free(error); + return cond; +} + +static +struct lttng_condition *handle_condition_buffer_usage_high(int *argc, const char ***argv) +{ + struct lttng_condition *cond = NULL; + struct argpar_state *state = NULL; + struct argpar_item *item = NULL; + const char *threshold_arg = NULL; + const char *session_name_arg = NULL; + uint64_t threshold; + char *error = NULL; + enum lttng_condition_status condition_status; + + state = argpar_state_create(*argc, *argv, event_rule_opt_descrs); + if (!state) { + ERR("Failed to allocate an argpar state."); + goto error; + } + + while (true) { + enum argpar_state_parse_next_status status; + + ARGPAR_ITEM_DESTROY_AND_RESET(item); + status = argpar_state_parse_next(state, &item, &error); + if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { + ERR("%s", error); + goto error; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { + /* Just stop parsing here. */ + break; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + break; + } + + assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt *item_opt = + (struct argpar_item_opt *) item; + + switch (item_opt->descr->id) { + default: + abort(); + } + } else { + struct argpar_item_non_opt *item_non_opt; + + assert(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + + item_non_opt = (struct argpar_item_non_opt *) item; + + switch (item_non_opt->non_opt_index) { + case 0: + session_name_arg = item_non_opt->arg; + break; + case 1: + threshold_arg = item_non_opt->arg; + break; + default: + ERR("Unexpected argument `%s`.", + item_non_opt->arg); + goto error; + } + } + } + + *argc -= argpar_state_get_ingested_orig_args(state); + *argv += argpar_state_get_ingested_orig_args(state); + + if (!session_name_arg) { + ERR("Missing session name argument."); + goto error; + } + + if (!threshold_arg) { + ERR("Missing threshold argument."); + goto error; + } + + if (utils_parse_size_suffix(threshold_arg, &threshold) != 0) { + ERR("Failed to parse `%s` as a size.", threshold_arg); + goto error; + } + + cond = lttng_condition_session_consumed_size_create(); + if (!cond) { + ERR("Failed to allocate a session consumed size condition."); + goto error; + } + + condition_status = lttng_condition_session_consumed_size_set_session_name( + cond, session_name_arg); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to set session consumed size condition session name."); + goto error; + } + + condition_status = lttng_condition_session_consumed_size_set_threshold( + cond, threshold); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to set session consumed size condition threshold."); + goto error; + } + + goto end; + +error: + lttng_condition_destroy(cond); + cond = NULL; + +end: + argpar_state_destroy(state); + argpar_item_destroy(item); + free(error); + return cond; +} + +static +struct lttng_condition *handle_condition_buffer_usage_low(int *argc, const char ***argv) +{ + return NULL; +} + +static +struct lttng_condition *handle_condition_session_rotation_ongoing(int *argc, const char ***argv) +{ + return NULL; +} + +static +struct lttng_condition *handle_condition_session_rotation_completed(int *argc, const char ***argv) +{ + return NULL; +} + +struct condition_descr { + const char *name; + struct lttng_condition *(*handler) (int *argc, const char ***argv); +}; + +static const +struct condition_descr condition_descrs[] = { + { "on-event", handle_condition_event }, + { "on-session-consumed-size", handle_condition_session_consumed_size }, + { "on-buffer-usage-high", handle_condition_buffer_usage_high }, + { "on-buffer-usage-low", handle_condition_buffer_usage_low }, + { "on-session-rotation-ongoing", handle_condition_session_rotation_ongoing }, + { "on-session-rotation-completed", handle_condition_session_rotation_completed }, +}; + +static +struct lttng_condition *parse_condition(int *argc, const char ***argv) +{ + int i; + struct lttng_condition *cond; + const char *condition_name; + const struct condition_descr *descr = NULL; + + if (*argc == 0) { + ERR("Missing condition name."); + goto error; + } + + condition_name = (*argv)[0]; + + (*argc)--; + (*argv)++; + + for (i = 0; i < ARRAY_SIZE(condition_descrs); i++) { + if (strcmp(condition_name, condition_descrs[i].name) == 0) { + descr = &condition_descrs[i]; + break; + } + } + + if (!descr) { + ERR("Unknown condition name: %s", condition_name); + goto error; + } + + cond = descr->handler(argc, argv); + if (!cond) { + /* The handler has already printed an error message. */ + goto error; + } + + goto end; +error: + cond = NULL; +end: + return cond; +} + + +static +struct lttng_action *handle_action_notify(int *argc, const char ***argv) +{ + return lttng_action_notify_create(); +} + +static const struct argpar_opt_descr no_opt_descrs[] = { + ARGPAR_OPT_DESCR_SENTINEL +}; + +/* + * Generic handler for a kind of action that takes a session name as its sole + * argument. + */ + +static +struct lttng_action *handle_action_simple_session( + int *argc, const char ***argv, + struct lttng_action *(*create_action_cb)(void), + enum lttng_action_status (*set_session_name_cb)(struct lttng_action *, const char *), + const char *action_name) +{ + struct lttng_action *action = NULL; + struct argpar_state *state = NULL; + struct argpar_item *item = NULL; + const char *session_name_arg = NULL; + char *error = NULL; + enum lttng_action_status action_status; + + state = argpar_state_create(*argc, *argv, no_opt_descrs); + if (!state) { + ERR("Failed to allocate an argpar state."); + goto error; + } + + while (true) { + enum argpar_state_parse_next_status status; + struct argpar_item_non_opt *item_non_opt; + + ARGPAR_ITEM_DESTROY_AND_RESET(item); + status = argpar_state_parse_next(state, &item, &error); + if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { + ERR("%s", error); + goto error; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { + /* Just stop parsing here. */ + break; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + break; + } + + assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + assert(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + + item_non_opt = (struct argpar_item_non_opt *) item; + + switch (item_non_opt->non_opt_index) { + case 0: + session_name_arg = item_non_opt->arg; + break; + default: + ERR("Unexpected argument `%s`.", + item_non_opt->arg); + goto error; + } + } + + *argc -= argpar_state_get_ingested_orig_args(state); + *argv += argpar_state_get_ingested_orig_args(state); + + if (!session_name_arg) { + ERR("Missing session name."); + goto error; + } + + action = create_action_cb(); + if (!action) { + ERR( + "Failed to allocate %s session action.", action_name); + goto error; + } + + action_status = set_session_name_cb(action, session_name_arg); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR( + "Failed to set action %s session's session name.", + action_name); + goto error; + } + + goto end; + +error: + lttng_action_destroy(action); + action = NULL; + +end: + return action; +} + +static +struct lttng_action *handle_action_start_session(int *argc, + const char ***argv) +{ + return handle_action_simple_session(argc, argv, + lttng_action_start_session_create, + lttng_action_start_session_set_session_name, + "start"); +} + +static +struct lttng_action *handle_action_stop_session(int *argc, + const char ***argv) +{ + return handle_action_simple_session(argc, argv, + lttng_action_stop_session_create, + lttng_action_stop_session_set_session_name, + "stop"); +} + +static +struct lttng_action *handle_action_rotate_session(int *argc, + const char ***argv) +{ + return handle_action_simple_session(argc, argv, + lttng_action_rotate_session_create, + lttng_action_rotate_session_set_session_name, + "rotate"); +} + +static const struct argpar_opt_descr snapshot_action_opt_descrs[] = { + { OPT_NAME, 'n', "name", true }, + { OPT_MAX_SIZE, 'm', "max-size", true }, + { OPT_CTRL_URL, '\0', "ctrl-url", true }, + { OPT_DATA_URL, '\0', "data-url", true }, + ARGPAR_OPT_DESCR_SENTINEL +}; + +static +struct lttng_action *handle_action_snapshot_session(int *argc, + const char ***argv) +{ + struct lttng_action *action = NULL; + struct argpar_state *state = NULL; + struct argpar_item *item = NULL; + const char *session_name_arg = NULL; + char *snapshot_name_arg = NULL; + char *ctrl_url_arg = NULL; + char *data_url_arg = NULL; + char *max_size_arg = NULL; + const char *url_arg = NULL; + char *error = NULL; + enum lttng_action_status action_status; + struct lttng_snapshot_output *snapshot_output = NULL; + int ret; + + state = argpar_state_create(*argc, *argv, snapshot_action_opt_descrs); + if (!state) { + ERR("Failed to allocate an argpar state."); + goto error; + } + + while (true) { + enum argpar_state_parse_next_status status; + + ARGPAR_ITEM_DESTROY_AND_RESET(item); + status = argpar_state_parse_next(state, &item, &error); + if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { + ERR("%s", error); + goto error; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { + /* Just stop parsing here. */ + break; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + break; + } + + assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt *item_opt = + (struct argpar_item_opt *) item; + + switch (item_opt->descr->id) { + case OPT_NAME: + if (!assign_string(&snapshot_name_arg, item_opt->arg, "--name/-n")) { + goto error; + } + break; + + case OPT_MAX_SIZE: + if (!assign_string(&max_size_arg, item_opt->arg, "--max-size/-m")) { + goto error; + } + break; + + case OPT_CTRL_URL: + if (!assign_string(&ctrl_url_arg, item_opt->arg, "--ctrl-url")) { + goto error; + } + break; + + case OPT_DATA_URL: + if (!assign_string(&data_url_arg, item_opt->arg, "--data-url")) { + goto error; + } + break; + + default: + abort(); + } + } else { + struct argpar_item_non_opt *item_non_opt; + + assert(item->type == ARGPAR_ITEM_TYPE_NON_OPT); + + item_non_opt = (struct argpar_item_non_opt *) item; + + switch (item_non_opt->non_opt_index) { + case 0: + session_name_arg = item_non_opt->arg; + break; + + // FIXME: the use of a non-option argument for this is to + // follow the syntax of `lttng snapshot record`. But otherwise, + // I think an option argument would be best. + case 1: + url_arg = item_non_opt->arg; + break; + + default: + ERR("Unexpected argument `%s`.", + item_non_opt->arg); + goto error; + } + } + } + + *argc -= argpar_state_get_ingested_orig_args(state); + *argv += argpar_state_get_ingested_orig_args(state); + + if (!session_name_arg) { + ERR("Missing session name."); + goto error; + } + + /* --ctrl-url and --data-url must come in pair. */ + if (ctrl_url_arg && !data_url_arg) { + ERR("--ctrl-url is specified, but --data-url is missing."); + goto error; + } + + if (!ctrl_url_arg && data_url_arg) { + ERR("--data-url is specified, but --ctrl-url is missing."); + goto error; + } + + /* --ctrl-url/--data-url and the non-option URL are mutually exclusive. */ + if (ctrl_url_arg && url_arg) { + ERR("Both --ctrl-url/--data-url and the non-option URL argument " + "can't be used together."); + goto error; + } + + /* + * Did the user specify an option that implies using a + * custom/unregistered output? + */ + if (url_arg || ctrl_url_arg) { + snapshot_output = lttng_snapshot_output_create(); + if (!snapshot_output) { + ERR("Failed to allocate a snapshot output."); + goto error; + } + } + + action = lttng_action_snapshot_session_create(); + if (!action) { + ERR( + "Failed to allocate snapshot session action."); + goto error; + } + + action_status = lttng_action_snapshot_session_set_session_name( + action, session_name_arg); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR( + "Failed to set action snapshot session's session name."); + goto error; + } + + if (snapshot_name_arg) { + if (!snapshot_output) { + ERR("Can't provide a snapshot output name without a snapshot output destination."); + goto error; + } + + ret = lttng_snapshot_output_set_name(snapshot_name_arg, snapshot_output); + if (ret != 0) { + ERR("Failed to set name of snapshot output."); + goto error; + } + } + + if (max_size_arg) { + uint64_t max_size; + + if (!snapshot_output) { + ERR("Can't provide a snapshot output max size without a snapshot output destination."); + goto error; + } + + ret = utils_parse_size_suffix(max_size_arg, &max_size); + if (ret != 0) { + ERR("Failed to parse `%s` as a size.", max_size_arg); + goto error; + } + + ret = lttng_snapshot_output_set_size(max_size, snapshot_output); + if (ret != 0) { + ERR("Failed to set snapshot output's max size."); + goto error; + } + } + + if (url_arg) { + /* One argument form, either net:// / net6:// or a local file path. */ + + if (strncmp(url_arg, "net://", strlen("net://")) == 0 || + strncmp(url_arg, "net6://", strlen("net6://")) == 0) { + ret = lttng_snapshot_output_set_network_url( + url_arg, snapshot_output); + if (ret != 0) { + ERR("Failed to parse %s as a network URL.", url_arg); + goto error; + } + } else { + ret = lttng_snapshot_output_set_local_path( + url_arg, snapshot_output); + if (ret != 0) { + ERR("Failed to parse %s as a local path.", url_arg); + goto error; + } + } + } + + if (ctrl_url_arg) { + /* + * Two argument form, network output with separate control and + * data URLs. + */ + ret = lttng_snapshot_output_set_network_urls( + ctrl_url_arg, data_url_arg, snapshot_output); + if (ret != 0) { + ERR("Failed to parse `%s` and `%s` as control and data URLs.", + ctrl_url_arg, data_url_arg); + goto error; + } + } + + if (snapshot_output) { + action_status = lttng_action_snapshot_session_set_output( + action, snapshot_output); + if (action_status != LTTNG_ACTION_STATUS_OK) { + ERR("Failed to set snapshot session action's output."); + goto error; + } + + /* Ownership of `snapshot_output` has been transferred to the action. */ + snapshot_output = NULL; + } + + goto end; + +error: + lttng_action_destroy(action); + action = NULL; + +end: + free(snapshot_name_arg); + free(ctrl_url_arg); + free(data_url_arg); + free(snapshot_output); + return action; +} + +struct action_descr { + const char *name; + struct lttng_action *(*handler) (int *argc, const char ***argv); +}; + +static const +struct action_descr action_descrs[] = { + { "notify", handle_action_notify }, + { "start-session", handle_action_start_session }, + { "stop-session", handle_action_stop_session }, + { "rotate-session", handle_action_rotate_session }, + { "snapshot-session", handle_action_snapshot_session }, +}; + +static +struct lttng_action *parse_action(int *argc, const char ***argv) +{ + int i; + struct lttng_action *action; + const char *action_name; + const struct action_descr *descr = NULL; + + if (*argc == 0) { + ERR("Missing action name."); + goto error; + } + + action_name = (*argv)[0]; + + (*argc)--; + (*argv)++; + + for (i = 0; i < ARRAY_SIZE(action_descrs); i++) { + if (strcmp(action_name, action_descrs[i].name) == 0) { + descr = &action_descrs[i]; + break; + } + } + + if (!descr) { + ERR("Unknown action name: %s", action_name); + goto error; + } + + action = descr->handler(argc, argv); + if (!action) { + /* The handler has already printed an error message. */ + goto error; + } + + goto end; +error: + action = NULL; +end: + return action; +} + +static const +struct argpar_opt_descr add_trigger_options[] = { + { OPT_HELP, 'h', "help", false }, + { OPT_LIST_OPTIONS, '\0', "list-options", false }, + { OPT_CONDITION, '\0', "condition", false }, + { OPT_ACTION, '\0', "action", false }, + { OPT_ID, '\0', "id", true }, + { OPT_FIRE_ONCE_AFTER, '\0', "fire-once-after", true }, + { OPT_FIRE_EVERY, '\0', "fire-every", true }, + ARGPAR_OPT_DESCR_SENTINEL, +}; + +static +void lttng_actions_destructor(void *p) +{ + struct lttng_action *action = p; + + lttng_action_destroy(action); +} + +int cmd_add_trigger(int argc, const char **argv) +{ + int ret; + int my_argc = argc - 1; + const char **my_argv = argv + 1; + struct lttng_condition *condition = NULL; + struct lttng_dynamic_pointer_array actions; + struct argpar_state *argpar_state = NULL; + struct argpar_item *argpar_item = NULL; + struct lttng_action *action_group = NULL; + struct lttng_action *action = NULL; + struct lttng_trigger *trigger = NULL; + char *error = NULL; + char *id = NULL; + int i; + char *fire_once_after_str = NULL; + char *fire_every_str = NULL; + + lttng_dynamic_pointer_array_init(&actions, lttng_actions_destructor); + + while (true) { + enum argpar_state_parse_next_status status; + struct argpar_item_opt *item_opt; + int ingested_args; + + argpar_state_destroy(argpar_state); + argpar_state = argpar_state_create(my_argc, my_argv, + add_trigger_options); + if (!argpar_state) { + ERR("Failed to create argpar state."); + goto error; + } + + status = argpar_state_parse_next(argpar_state, &argpar_item, &error); + + if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { + ERR("%s", error); + goto error; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { + ERR("%s", error); + goto error; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + break; + } + + assert(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + + if (argpar_item->type == ARGPAR_ITEM_TYPE_NON_OPT) { + struct argpar_item_non_opt *item_non_opt = + (struct argpar_item_non_opt *) argpar_item; + + ERR("Unexpected argument `%s`.", + item_non_opt->arg); + goto error; + } + + item_opt = (struct argpar_item_opt *) argpar_item; + + ingested_args = argpar_state_get_ingested_orig_args( + argpar_state); + + my_argc -= ingested_args; + my_argv += ingested_args; + + switch (item_opt->descr->id) { + case OPT_HELP: + SHOW_HELP(); + ret = 0; + goto end; + + case OPT_LIST_OPTIONS: + list_cmd_options_argpar(stdout, add_trigger_options); + ret = 0; + goto end; + + case OPT_CONDITION: + { + if (condition) { + ERR("A --condition was already given."); + goto error; + } + + condition = parse_condition(&my_argc, &my_argv); + if (!condition) { + /* + * An error message was already printed by + * parse_condition. + */ + goto error; + } + + break; + } + + case OPT_ACTION: + { + action = parse_action(&my_argc, &my_argv); + if (!action) { + /* + * An error message was already printed by + * parse_condition. + */ + goto error; + } + + ret = lttng_dynamic_pointer_array_add_pointer( + &actions, action); + if (ret) { + ERR("Failed to add pointer to pointer array."); + goto error; + } + + /* Ownership of the action was transferred to the group. */ + action = NULL; + + break; + } + + case OPT_ID: + { + if (!assign_string(&id, item_opt->arg, "--id")) { + goto error; + } + + break; + } + + case OPT_FIRE_ONCE_AFTER: + { + if (!assign_string(&fire_once_after_str, item_opt->arg, + "--fire-once-after")) { + goto error; + } + break; + } + + case OPT_FIRE_EVERY: + { + if (!assign_string(&fire_every_str, item_opt->arg, + "--fire-every")) { + goto error; + } + break; + } + + default: + abort(); + } + } + + if (!condition) { + ERR("Missing --condition."); + goto error; + } + + if (lttng_dynamic_pointer_array_get_count(&actions) == 0) { + ERR("Need at least one --action."); + goto error; + } + + if (fire_every_str && fire_once_after_str) { + ERR("Can't specify both --fire-once-after and --fire-every."); + goto error; + } + + action_group = lttng_action_group_create(); + if (!action_group) { + goto error; + } + + for (i = 0; i < lttng_dynamic_pointer_array_get_count(&actions); i++) { + enum lttng_action_status status; + + action = lttng_dynamic_pointer_array_steal_pointer(&actions, i); + + status = lttng_action_group_add_action( + action_group, action); + if (status != LTTNG_ACTION_STATUS_OK) { + goto error; + } + + /* Ownership of the action was transferred to the group. */ + action = NULL; + } + + + trigger = lttng_trigger_create(condition, action_group); + if (!trigger) { + goto error; + } + + if (id) { + enum lttng_trigger_status trigger_status = + lttng_trigger_set_name(trigger, id); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + ERR("Failed to set trigger id."); + goto error; + } + } + + if (fire_once_after_str) { + unsigned long long threshold; + enum lttng_trigger_status trigger_status; + + if (utils_parse_unsigned_long_long(fire_once_after_str, &threshold) != 0) { + ERR("Failed to parse `%s` as an integer.", fire_once_after_str); + goto error; + } + + trigger_status = lttng_trigger_set_firing_policy(trigger, + LTTNG_TRIGGER_FIRE_ONCE_AFTER_N, threshold); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + ERR("Failed to set trigger's firing policy."); + goto error; + } + } + + if (fire_every_str) { + unsigned long long threshold; + enum lttng_trigger_status trigger_status; + + if (utils_parse_unsigned_long_long(fire_every_str, &threshold) != 0) { + ERR("Failed to parse `%s` as an integer.", fire_every_str); + goto error; + } + + trigger_status = lttng_trigger_set_firing_policy(trigger, + LTTNG_TRIGGER_FIRE_EVERY_N, threshold); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + ERR("Failed to set trigger's firing policy."); + goto error; + } + } + + ret = lttng_register_trigger(trigger); + if (ret) { + ERR("Failed to register trigger: %s.", + lttng_strerror(ret)); + goto error; + } + + MSG("Trigger registered successfully."); + + goto end; + +error: + ret = 1; + +end: + argpar_state_destroy(argpar_state); + lttng_dynamic_pointer_array_reset(&actions); + lttng_condition_destroy(condition); + lttng_action_destroy(action_group); + lttng_trigger_destroy(trigger); + free(id); + free(fire_once_after_str); + free(fire_every_str); + // TODO: check what else to free + + return ret; +} diff --git a/src/bin/lttng/commands/enable_events.c b/src/bin/lttng/commands/enable_events.c index f41dc9eca..2187f7029 100644 --- a/src/bin/lttng/commands/enable_events.c +++ b/src/bin/lttng/commands/enable_events.c @@ -26,7 +26,10 @@ /* Mi dependancy */ #include +#include + #include "../command.h" +#include "../uprobe.h" #if (LTTNG_SYMBOL_NAME_LEN == 256) #define LTTNG_SYMBOL_NAME_LEN_SCANF_IS_A_BROKEN_API "255" @@ -174,397 +177,11 @@ end: return ret; } -/* - * Walk the directories in the PATH environment variable to find the target - * binary passed as parameter. - * - * On success, the full path of the binary is copied in binary_full_path out - * parameter. This buffer is allocated by the caller and must be at least - * LTTNG_PATH_MAX bytes long. - * On failure, returns -1; - */ -static int walk_command_search_path(const char *binary, char *binary_full_path) -{ - char *tentative_binary_path = NULL; - char *command_search_path = NULL; - char *curr_search_dir_end = NULL; - char *curr_search_dir = NULL; - struct stat stat_output; - int ret = 0; - - command_search_path = lttng_secure_getenv("PATH"); - if (!command_search_path) { - ret = -1; - goto end; - } - - /* - * Duplicate the $PATH string as the char pointer returned by getenv() should - * not be modified. - */ - command_search_path = strdup(command_search_path); - if (!command_search_path) { - ret = -1; - goto end; - } - - /* - * This char array is used to concatenate path to binary to look for - * the binary. - */ - tentative_binary_path = zmalloc(LTTNG_PATH_MAX * sizeof(char)); - if (!tentative_binary_path) { - ret = -1; - goto alloc_error; - } - - curr_search_dir = command_search_path; - do { - /* - * Split on ':'. The return value of this call points to the - * matching character. - */ - curr_search_dir_end = strchr(curr_search_dir, ':'); - if (curr_search_dir_end != NULL) { - /* - * Add a NULL byte to the end of the first token so it - * can be used as a string. - */ - curr_search_dir_end[0] = '\0'; - } - - /* Empty the tentative path */ - memset(tentative_binary_path, 0, LTTNG_PATH_MAX * sizeof(char)); - - /* - * Build the tentative path to the binary using the current - * search directory and the name of the binary. - */ - ret = snprintf(tentative_binary_path, LTTNG_PATH_MAX, "%s/%s", - curr_search_dir, binary); - if (ret < 0) { - goto free_binary_path; - } - if (ret < LTTNG_PATH_MAX) { - /* - * Use STAT(2) to see if the file exists. - */ - ret = stat(tentative_binary_path, &stat_output); - if (ret == 0) { - /* - * Verify that it is a regular file or a - * symlink and not a special file (e.g. - * device). - */ - if (S_ISREG(stat_output.st_mode) - || S_ISLNK(stat_output.st_mode)) { - /* - * Found a match, set the out parameter - * and return success. - */ - ret = lttng_strncpy(binary_full_path, - tentative_binary_path, - LTTNG_PATH_MAX); - if (ret == -1) { - ERR("Source path does not fit " - "in destination buffer."); - } - goto free_binary_path; - } - } - } - /* Go to the next entry in the $PATH variable. */ - curr_search_dir = curr_search_dir_end + 1; - } while (curr_search_dir_end != NULL); - -free_binary_path: - free(tentative_binary_path); -alloc_error: - free(command_search_path); -end: - return ret; -} - -/* - * Check if the symbol field passed by the user is in fact an address or an - * offset from a symbol. Those two instrumentation types are not supported yet. - * It's expected to be a common mistake because of the existing --probe option - * that does support these formats. - * - * Here are examples of these unsupported formats for the --userspace-probe - * option: - * elf:/path/to/binary:0x400430 - * elf:/path/to/binary:4194364 - * elf:/path/to/binary:my_symbol+0x323 - * elf:/path/to/binary:my_symbol+43 - */ -static int warn_userspace_probe_syntax(const char *symbol) -{ - int ret; - - /* Check if the symbol field is an hex address. */ - ret = sscanf(symbol, "0x%*x"); - if (ret > 0) { - /* If there is a match, print a warning and return an error. */ - ERR("Userspace probe on address not supported yet."); - ret = CMD_UNSUPPORTED; - goto error; - } - - /* Check if the symbol field is an decimal address. */ - ret = sscanf(symbol, "%*u"); - if (ret > 0) { - /* If there is a match, print a warning and return an error. */ - ERR("Userspace probe on address not supported yet."); - ret = CMD_UNSUPPORTED; - goto error; - } - - /* Check if the symbol field is symbol+hex_offset. */ - ret = sscanf(symbol, "%*[^+]+0x%*x"); - if (ret > 0) { - /* If there is a match, print a warning and return an error. */ - ERR("Userspace probe on symbol+offset not supported yet."); - ret = CMD_UNSUPPORTED; - goto error; - } - - /* Check if the symbol field is symbol+decimal_offset. */ - ret = sscanf(symbol, "%*[^+]+%*u"); - if (ret > 0) { - /* If there is a match, print a warning and return an error. */ - ERR("Userspace probe on symbol+offset not supported yet."); - ret = CMD_UNSUPPORTED; - goto error; - } - - ret = 0; - -error: - return ret; -} - -/* - * Parse userspace probe options - * Set the userspace probe fields in the lttng_event struct and set the - * target_path to the path to the binary. - */ -static int parse_userspace_probe_opts(struct lttng_event *ev, char *opt) -{ - int ret = CMD_SUCCESS; - int num_token; - char **tokens; - char *target_path = NULL; - char *unescaped_target_path = NULL; - char *real_target_path = NULL; - char *symbol_name = NULL, *probe_name = NULL, *provider_name = NULL; - struct lttng_userspace_probe_location *probe_location = NULL; - struct lttng_userspace_probe_location_lookup_method *lookup_method = - NULL; - - if (opt == NULL) { - ret = CMD_ERROR; - goto end; - } - - switch (ev->type) { - case LTTNG_EVENT_USERSPACE_PROBE: - break; - default: - assert(0); - } - - /* - * userspace probe fields are separated by ':'. - */ - tokens = strutils_split(opt, ':', 1); - num_token = strutils_array_of_strings_len(tokens); - - /* - * Early sanity check that the number of parameter is between 2 and 4 - * inclusively. - * elf:PATH:SYMBOL - * std:PATH:PROVIDER_NAME:PROBE_NAME - * PATH:SYMBOL (same behavior as ELF) - */ - if (num_token < 2 || num_token > 4) { - ret = CMD_ERROR; - goto end_string; - } - - /* - * Looking up the first parameter will tell the technique to use to - * interpret the userspace probe/function description. - */ - switch (num_token) { - case 2: - /* When the probe type is omitted we assume ELF for now. */ - case 3: - if (num_token == 3 && strcmp(tokens[0], "elf") == 0) { - target_path = tokens[1]; - symbol_name = tokens[2]; - } else if (num_token == 2) { - target_path = tokens[0]; - symbol_name = tokens[1]; - } else { - ret = CMD_ERROR; - goto end_string; - } - lookup_method = - lttng_userspace_probe_location_lookup_method_function_elf_create(); - if (!lookup_method) { - WARN("Failed to create ELF lookup method"); - ret = CMD_ERROR; - goto end_string; - } - break; - case 4: - if (strcmp(tokens[0], "sdt") == 0) { - target_path = tokens[1]; - provider_name = tokens[2]; - probe_name = tokens[3]; - } else { - ret = CMD_ERROR; - goto end_string; - } - lookup_method = - lttng_userspace_probe_location_lookup_method_tracepoint_sdt_create(); - if (!lookup_method) { - WARN("Failed to create SDT lookup method"); - ret = CMD_ERROR; - goto end_string; - } - break; - default: - ret = CMD_ERROR; - goto end_string; - } - - /* strutils_unescape_string allocates a new char *. */ - unescaped_target_path = strutils_unescape_string(target_path, 0); - if (!unescaped_target_path) { - ret = CMD_ERROR; - goto end_destroy_lookup_method; - } - - /* - * If there is not forward slash in the path. Walk the $PATH else - * expand. - */ - if (strchr(unescaped_target_path, '/') == NULL) { - /* Walk the $PATH variable to find the targeted binary. */ - real_target_path = zmalloc(LTTNG_PATH_MAX * sizeof(char)); - if (!real_target_path) { - PERROR("Error allocating path buffer"); - ret = CMD_ERROR; - goto end_destroy_lookup_method; - } - ret = walk_command_search_path(unescaped_target_path, real_target_path); - if (ret) { - ERR("Binary not found."); - ret = CMD_ERROR; - goto end_destroy_lookup_method; - } - } else { - /* - * Expand references to `/./` and `/../`. This function does not check - * if the file exists. This call returns an allocated buffer on - * success. - */ - real_target_path = utils_expand_path_keep_symlink(unescaped_target_path); - if (!real_target_path) { - ERR("Error expanding the path to binary."); - ret = CMD_ERROR; - goto end_destroy_lookup_method; - } - - /* - * Check if the file exists using access(2), If it does not, - * return an error. - */ - ret = access(real_target_path, F_OK); - if (ret) { - ERR("Cannot find binary at path: %s.", real_target_path); - ret = CMD_ERROR; - goto end_destroy_lookup_method; - } - } - - switch (lttng_userspace_probe_location_lookup_method_get_type(lookup_method)) { - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF: - /* - * Check for common mistakes in userspace probe description syntax. - */ - ret = warn_userspace_probe_syntax(symbol_name); - if (ret) { - goto end_destroy_lookup_method; - } - - probe_location = lttng_userspace_probe_location_function_create( - real_target_path, symbol_name, lookup_method); - if (!probe_location) { - WARN("Failed to create function probe location"); - ret = CMD_ERROR; - goto end_destroy_lookup_method; - } - - /* Ownership transferred to probe_location. */ - lookup_method = NULL; - - ret = lttng_event_set_userspace_probe_location(ev, probe_location); - if (ret) { - WARN("Failed to set probe location on event"); - ret = CMD_ERROR; - goto end_destroy_location; - } - break; - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: - probe_location = lttng_userspace_probe_location_tracepoint_create( - real_target_path, provider_name, probe_name, lookup_method); - if (!probe_location) { - WARN("Failed to create function probe location"); - ret = CMD_ERROR; - goto end_destroy_lookup_method; - } - - /* Ownership transferred to probe_location. */ - lookup_method = NULL; - - ret = lttng_event_set_userspace_probe_location(ev, probe_location); - if (ret) { - WARN("Failed to set probe location on event"); - ret = CMD_ERROR; - goto end_destroy_location; - } - break; - default: - ret = CMD_ERROR; - goto end_destroy_lookup_method; - } - - /* Successful parsing, now clean up everything and return. */ - goto end_string; - -end_destroy_location: - lttng_userspace_probe_location_destroy(probe_location); -end_destroy_lookup_method: - lttng_userspace_probe_location_lookup_method_destroy(lookup_method); -end_string: - strutils_free_null_terminated_array_of_strings(tokens); - /* - * Freeing both char * here makes the error handling simplier. free() - * performs not action if the pointer is NULL. - */ - free(real_target_path); - free(unescaped_target_path); -end: - return ret; -} - /* * Maps LOG4j loglevel from string to value */ -static int loglevel_log4j_str_to_value(const char *inputstr) +LTTNG_HIDDEN +int loglevel_log4j_str_to_value(const char *inputstr) { int i = 0; char str[LTTNG_SYMBOL_NAME_LEN]; @@ -607,7 +224,8 @@ static int loglevel_log4j_str_to_value(const char *inputstr) /* * Maps JUL loglevel from string to value */ -static int loglevel_jul_str_to_value(const char *inputstr) +LTTNG_HIDDEN +int loglevel_jul_str_to_value(const char *inputstr) { int i = 0; char str[LTTNG_SYMBOL_NAME_LEN]; @@ -652,7 +270,8 @@ static int loglevel_jul_str_to_value(const char *inputstr) /* * Maps Python loglevel from string to value */ -static int loglevel_python_str_to_value(const char *inputstr) +LTTNG_HIDDEN +int loglevel_python_str_to_value(const char *inputstr) { int i = 0; char str[LTTNG_SYMBOL_NAME_LEN]; @@ -691,7 +310,7 @@ static int loglevel_python_str_to_value(const char *inputstr) /* * Maps loglevel from string to value */ -static +LTTNG_HIDDEN int loglevel_str_to_value(const char *inputstr) { int i = 0; @@ -891,7 +510,16 @@ end: return ret; } -static +/* + * FIXME: find a good place to declare this since add trigger also uses it + */ +LTTNG_HIDDEN +int create_exclusion_list_and_validate(const char *event_name, + const char *exclusions_arg, + char ***exclusion_list); + + +LTTNG_HIDDEN int create_exclusion_list_and_validate(const char *event_name, const char *exclusions_arg, char ***exclusion_list) @@ -969,6 +597,7 @@ static int enable_events(char *session_name) struct lttng_event *ev; struct lttng_domain dom; char **exclusion_list = NULL; + struct lttng_userspace_probe_location *uprobe_loc = NULL; memset(&dom, 0, sizeof(dom)); @@ -1356,7 +985,9 @@ static int enable_events(char *session_name) } break; case LTTNG_EVENT_USERSPACE_PROBE: - ret = parse_userspace_probe_opts(ev, opt_userspace_probe); + assert(ev->type == LTTNG_EVENT_USERSPACE_PROBE); + + ret = parse_userspace_probe_opts(opt_userspace_probe, &uprobe_loc); if (ret) { switch (ret) { case CMD_UNSUPPORTED: @@ -1373,6 +1004,16 @@ static int enable_events(char *session_name) } goto error; } + + ret = lttng_event_set_userspace_probe_location(ev, uprobe_loc); + if (ret) { + WARN("Failed to set probe location on event"); + ret = CMD_ERROR; + goto error; + } + + /* Ownership of the uprobe location was transferred to the event. */ + uprobe_loc = NULL; break; case LTTNG_EVENT_FUNCTION: ret = parse_probe_opts(ev, opt_function); @@ -1689,6 +1330,7 @@ error: } lttng_destroy_handle(handle); strutils_free_null_terminated_array_of_strings(exclusion_list); + lttng_userspace_probe_location_destroy(uprobe_loc); /* Overwrite ret with error_holder if there was an actual error with * enabling an event. diff --git a/src/bin/lttng/commands/list_triggers.c b/src/bin/lttng/commands/list_triggers.c new file mode 100644 index 000000000..091c64c5c --- /dev/null +++ b/src/bin/lttng/commands/list_triggers.c @@ -0,0 +1,627 @@ +#include + +#include "../command.h" + +#include "common/argpar/argpar.h" +#include "common/mi-lttng.h" +#include "lttng/condition/condition-internal.h" +#include "lttng/condition/event-rule.h" +#include "lttng/domain-internal.h" +#include "lttng/event-rule/event-rule-internal.h" +#include "lttng/event-rule/kprobe.h" +#include "lttng/event-rule/kprobe-internal.h" +#include "lttng/event-rule/syscall.h" +#include "lttng/event-rule/tracepoint.h" +#include "lttng/event-rule/uprobe.h" +#include "lttng/trigger/trigger-internal.h" + +#ifdef LTTNG_EMBED_HELP +static const char help_msg[] = +#include +; +#endif + +enum { + OPT_HELP, + OPT_LIST_OPTIONS, +}; + +static const +struct argpar_opt_descr list_trigger_options[] = { + { OPT_HELP, 'h', "help", false }, + { OPT_LIST_OPTIONS, '\0', "list-options", false }, + ARGPAR_OPT_DESCR_SENTINEL, +}; + +static +void print_event_rule_tracepoint(const struct lttng_event_rule *event_rule) +{ + enum lttng_event_rule_status event_rule_status; + enum lttng_domain_type domain_type; + const char *pattern; + const char *filter; + int loglevel; + unsigned int exclusions_count; + int i; + + event_rule_status = lttng_event_rule_tracepoint_get_pattern( + event_rule, &pattern); + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK); + + event_rule_status = lttng_event_rule_tracepoint_get_domain_type( + event_rule, &domain_type); + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK); + + _MSG(" rule: %s (type: tracepoint, domain: %s", pattern, + lttng_domain_type_str(domain_type)); + + event_rule_status = lttng_event_rule_tracepoint_get_filter( + event_rule, &filter); + if (event_rule_status == LTTNG_EVENT_RULE_STATUS_OK) { + _MSG(", filter: %s", filter); + } else { + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_UNSET); + } + + event_rule_status = lttng_event_rule_tracepoint_get_loglevel( + event_rule, &loglevel); + if (event_rule_status == LTTNG_EVENT_RULE_STATUS_OK) { + enum lttng_loglevel_type loglevel_type; + const char *loglevel_op; + + event_rule_status = lttng_event_rule_tracepoint_get_loglevel_type( + event_rule, &loglevel_type); + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK); + assert(loglevel_type == LTTNG_EVENT_LOGLEVEL_RANGE || + loglevel_type == LTTNG_EVENT_LOGLEVEL_SINGLE); + + loglevel_op = (loglevel_type == LTTNG_EVENT_LOGLEVEL_RANGE ? "<=" : "=="); + + _MSG(", log level %s %s", loglevel_op, + mi_lttng_loglevel_string(loglevel, domain_type)); + } else { + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_UNSET); + } + + event_rule_status = lttng_event_rule_tracepoint_get_exclusions_count( + event_rule, &exclusions_count); + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK); + if (exclusions_count > 0) { + _MSG(", exclusions: "); + for (i = 0; i < exclusions_count; i++) { + const char *exclusion; + + event_rule_status = lttng_event_rule_tracepoint_get_exclusion_at_index( + event_rule, i, &exclusion); + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK); + + _MSG("%s%s", i > 0 ? "," : "", exclusion); + } + } + + MSG(")"); +} + +static +void print_event_rule_kprobe(const struct lttng_event_rule *event_rule) +{ + enum lttng_event_rule_status event_rule_status; + const char *name, *symbol_name; + uint64_t offset; + + event_rule_status = lttng_event_rule_kprobe_get_name(event_rule, &name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to get kprobe event rule's name."); + goto end; + } + + assert(lttng_event_rule_get_type(event_rule) == LTTNG_EVENT_RULE_TYPE_KPROBE); + + _MSG(" rule: %s (type: probe, location: ", name); + + // FIXME: When the location has been specified by address, this field + // contains the address as a string. The only downside is that we are + // missing a `0x` prefix. + symbol_name = lttng_event_rule_kprobe_get_symbol_name(event_rule); + _MSG("%s", symbol_name); + + offset = lttng_event_rule_kprobe_get_offset(event_rule); + if (offset > 0) { + _MSG("+0x%" PRIx64, offset); + } + + MSG(")"); + +end: + return; +} + +static +void print_event_rule_uprobe(const struct lttng_event_rule *event_rule) +{ + enum lttng_event_rule_status event_rule_status; + const char *name; + const struct lttng_userspace_probe_location *location; + enum lttng_userspace_probe_location_type userspace_probe_location_type; + + assert(lttng_event_rule_get_type(event_rule) == LTTNG_EVENT_RULE_TYPE_UPROBE); + + event_rule_status = lttng_event_rule_uprobe_get_name(event_rule, &name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to get uprobe event rule's name."); + goto end; + } + + event_rule_status = lttng_event_rule_uprobe_get_location(event_rule, &location); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to get uprobe event rule's location."); + goto end; + } + + _MSG(" rule: %s (type: userspace probe, location: ", name); + + userspace_probe_location_type = + lttng_userspace_probe_location_get_type(location); + + switch (userspace_probe_location_type) { + case LTTNG_USERSPACE_PROBE_LOCATION_TYPE_FUNCTION: + { + const char *binary_path, *function_name; + + binary_path = lttng_userspace_probe_location_function_get_binary_path(location); + function_name = lttng_userspace_probe_location_function_get_function_name(location); + + _MSG("%s:%s", binary_path, function_name); + break; + } + + case LTTNG_USERSPACE_PROBE_LOCATION_TYPE_TRACEPOINT: + _MSG("SDT not implemented yet"); + break; + + default: + abort(); + } + + MSG(")"); + +end: + return; +} + +static +void print_event_rule_syscall(const struct lttng_event_rule *event_rule) +{ + const char *pattern, *filter; + enum lttng_event_rule_status event_rule_status; + + assert(lttng_event_rule_get_type(event_rule) == LTTNG_EVENT_RULE_TYPE_SYSCALL); + + event_rule_status = lttng_event_rule_syscall_get_pattern(event_rule, &pattern); + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK); + + _MSG(" - rule: %s (type: syscall", pattern); + + event_rule_status = lttng_event_rule_syscall_get_filter( + event_rule, &filter); + if (event_rule_status == LTTNG_EVENT_RULE_STATUS_OK) { + _MSG(", filter: %s", filter); + } else { + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_UNSET); + } + + MSG(")"); +} + +static +void print_event_rule(const struct lttng_event_rule *event_rule) +{ + enum lttng_event_rule_type event_rule_type = + lttng_event_rule_get_type(event_rule); + + switch (event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + print_event_rule_tracepoint(event_rule); + break; + + case LTTNG_EVENT_RULE_TYPE_KPROBE: + print_event_rule_kprobe(event_rule); + break; + + case LTTNG_EVENT_RULE_TYPE_UPROBE: + print_event_rule_uprobe(event_rule); + break; + + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + print_event_rule_syscall(event_rule); + break; + + default: + abort(); + } +} + +static +void print_one_event_expr(const struct lttng_event_expr *event_expr) +{ + enum lttng_event_expr_type type; + + type = lttng_event_expr_get_type(event_expr); + + switch (type) { + case LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD: { + const char *name; + + name = lttng_event_expr_event_payload_field_get_name(event_expr); + _MSG("%s", name); + + break; + } + + case LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD: { + const char *name; + + name = lttng_event_expr_channel_context_field_get_name(event_expr); + _MSG("$ctx.%s", name); + + break; + } + + case LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD: { + const char *provider_name; + const char *type_name; + + provider_name = + lttng_event_expr_app_specific_context_field_get_provider_name( + event_expr); + type_name = + lttng_event_expr_app_specific_context_field_get_type_name( + event_expr); + + _MSG("$app.%s:%s", provider_name, type_name); + + break; + } + + case LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT: { + unsigned int index; + const struct lttng_event_expr *parent_expr; + enum lttng_event_expr_status status; + + parent_expr = lttng_event_expr_array_field_element_get_parent_expr( + event_expr); + assert(parent_expr != NULL); + + print_one_event_expr(parent_expr); + + status = lttng_event_expr_array_field_element_get_index( + event_expr, &index); + assert(status == LTTNG_EVENT_EXPR_STATUS_OK); + + _MSG("[%u]", index); + + break; + } + + default: + abort(); + } +} + +static +void print_condition_event_rule_hit(const struct lttng_condition *condition) +{ + const struct lttng_event_rule *event_rule; + enum lttng_condition_status condition_status; + unsigned int cap_desc_count, i; + + condition_status = + lttng_condition_event_rule_get_rule(condition, &event_rule); + assert(condition_status == LTTNG_CONDITION_STATUS_OK); + + print_event_rule(event_rule); + + condition_status + = lttng_condition_event_rule_get_capture_descriptor_count( + condition, &cap_desc_count); + assert(condition_status == LTTNG_CONDITION_STATUS_OK); + + if (cap_desc_count > 0) { + MSG(" captures:"); + + for (i = 0; i < cap_desc_count; i++) { + const struct lttng_event_expr *cap_desc = + lttng_condition_event_rule_get_capture_descriptor_at_index( + condition, i); + + _MSG(" - "); + print_one_event_expr(cap_desc); + MSG(""); + } + } +} + +static +void print_one_action(const struct lttng_action *action) +{ + enum lttng_action_type action_type; + enum lttng_action_status action_status; + const char *value; + + action_type = lttng_action_get_type(action); + assert(action_type != LTTNG_ACTION_TYPE_GROUP); + + switch (action_type) { + case LTTNG_ACTION_TYPE_NOTIFY: + MSG("notify"); + break; + + case LTTNG_ACTION_TYPE_START_SESSION: + action_status = lttng_action_start_session_get_session_name( + action, &value); + assert(action_status == LTTNG_ACTION_STATUS_OK); + MSG("start session `%s`", value); + break; + + case LTTNG_ACTION_TYPE_STOP_SESSION: + action_status = lttng_action_stop_session_get_session_name( + action, &value); + assert(action_status == LTTNG_ACTION_STATUS_OK); + MSG("stop session `%s`", value); + break; + + case LTTNG_ACTION_TYPE_ROTATE_SESSION: + action_status = lttng_action_rotate_session_get_session_name( + action, &value); + assert(action_status == LTTNG_ACTION_STATUS_OK); + MSG("rotate session `%s`", value); + break; + + case LTTNG_ACTION_TYPE_SNAPSHOT_SESSION: + { + const struct lttng_snapshot_output *output; + + action_status = lttng_action_snapshot_session_get_session_name( + action, &value); + assert(action_status == LTTNG_ACTION_STATUS_OK); + _MSG("snapshot session `%s`", value); + + action_status = lttng_action_snapshot_session_get_output_const( + action, &output); + if (action_status == LTTNG_ACTION_STATUS_OK) { + const char *name; + uint64_t max_size; + const char *ctrl_url, *data_url; + bool starts_with_file, starts_with_net, starts_with_net6; + + ctrl_url = lttng_snapshot_output_get_ctrl_url(output); + assert(ctrl_url && strlen(ctrl_url) > 0); + + data_url = lttng_snapshot_output_get_data_url(output); + assert(data_url); + + starts_with_file = strncmp(ctrl_url, "file://", strlen("file://")) == 0; + starts_with_net = strncmp(ctrl_url, "net://", strlen("net://")) == 0; + starts_with_net6 = strncmp(ctrl_url, "net6://", strlen("net6://")) == 0; + + if (ctrl_url[0] == '/' || starts_with_file) { + if (starts_with_file) { + ctrl_url += strlen("file://"); + } + + _MSG(", path: %s", ctrl_url); + } else if (starts_with_net || starts_with_net6) { + _MSG(", url: %s", ctrl_url); + } else { + assert(strlen(data_url) > 0); + + _MSG(", control url: %s, data url: %s", ctrl_url, data_url); + } + + name = lttng_snapshot_output_get_name(output); + assert(name); + if (strlen(name) > 0) { + _MSG(", name: %s", name); + } + + max_size = lttng_snapshot_output_get_maxsize(output); + if (max_size != -1ULL) { + _MSG(", max size: %" PRIu64, max_size); + } + } + + MSG(""); + break; + } + + default: + abort(); + } +} + +static +void print_one_trigger(const struct lttng_trigger *trigger) +{ + const struct lttng_condition *condition; + enum lttng_condition_type condition_type; + const struct lttng_action *action; + enum lttng_action_type action_type; + enum lttng_trigger_status trigger_status; + const char *name; + enum lttng_trigger_firing_policy_type policy_type; + unsigned long long threshold; + + trigger_status = lttng_trigger_get_name(trigger, &name); + assert(trigger_status == LTTNG_TRIGGER_STATUS_OK); + MSG("- id: %s", name); + + trigger_status = lttng_trigger_get_firing_policy(trigger, + &policy_type, &threshold); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + ERR("Failed to get trigger's firing policy."); + goto end; + } + + switch (policy_type) { + case LTTNG_TRIGGER_FIRE_EVERY_N: + if (threshold > 1) { + MSG(" firing policy: after every %llu occurences", threshold); + } + break; + + case LTTNG_TRIGGER_FIRE_ONCE_AFTER_N: + MSG(" firing policy: once after %llu occurences", threshold); + break; + + default: + abort(); + } + + condition = lttng_trigger_get_const_condition(trigger); + condition_type = lttng_condition_get_type(condition); + MSG(" condition: %s", + lttng_condition_type_str(condition_type)); + + switch (condition_type) { + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + print_condition_event_rule_hit(condition); + break; + + default: + MSG(" (condition type not handled in %s)", __func__); + break; + } + + action = lttng_trigger_get_const_action(trigger); + action_type = lttng_action_get_type(action); + if (action_type == LTTNG_ACTION_TYPE_GROUP) { + enum lttng_action_status action_status; + unsigned int count, i; + + MSG(" actions:"); + + action_status = lttng_action_group_get_count(action, &count); + assert(action_status == LTTNG_ACTION_STATUS_OK); + + for (i = 0; i < count; i++) { + const struct lttng_action *subaction = + lttng_action_group_get_at_index_const(action, i); + + _MSG(" "); + print_one_action(subaction); + } + } else { + _MSG(" action:"); + print_one_action(action); + } + +end: + return; +} + +static +int compare_triggers_by_name(const void *a, const void *b) +{ + const struct lttng_trigger *trigger_a = *((const struct lttng_trigger **) a); + const struct lttng_trigger *trigger_b = *((const struct lttng_trigger **) b); + const char *name_a, *name_b; + enum lttng_trigger_status trigger_status; + + trigger_status = lttng_trigger_get_name(trigger_a, &name_a); + assert(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + trigger_status = lttng_trigger_get_name(trigger_b, &name_b); + assert(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + return strcmp(name_a, name_b); +} + +int cmd_list_triggers(int argc, const char **argv) +{ + int ret; + struct argpar_parse_ret argpar_parse_ret = { 0 }; + struct lttng_triggers *triggers = NULL; + int i; + const struct lttng_trigger **sorted_triggers = NULL; + enum lttng_trigger_status trigger_status; + unsigned int num_triggers; + + argpar_parse_ret = argpar_parse(argc - 1, argv + 1, + list_trigger_options, true); + if (!argpar_parse_ret.items) { + ERR("%s", argpar_parse_ret.error); + goto error; + } + + for (i = 0; i < argpar_parse_ret.items->n_items; i++) { + struct argpar_item *item = argpar_parse_ret.items->items[i]; + + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt *item_opt = + (struct argpar_item_opt *) item; + + switch (item_opt->descr->id) { + case OPT_HELP: + SHOW_HELP(); + ret = 0; + goto end; + + case OPT_LIST_OPTIONS: + list_cmd_options_argpar(stdout, + list_trigger_options); + ret = 0; + goto end; + + default: + abort(); + } + + } else { + struct argpar_item_non_opt *item_non_opt = + (struct argpar_item_non_opt *) item; + + ERR("Unexpected argument: %s", item_non_opt->arg); + } + } + + ret = lttng_list_triggers(&triggers); + if (ret != 0) { + ERR("Error listing triggers: %s.", + lttng_strerror(ret)); + goto error; + } + + trigger_status = lttng_triggers_get_count(triggers, &num_triggers); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + ERR("Failed to get trigger count."); + goto error; + } + + sorted_triggers = calloc(num_triggers, sizeof(struct lttng_trigger *)); + if (!sorted_triggers) { + ERR("Failed to allocate array of struct lttng_trigger *."); + goto error; + } + + for (i = 0; i < num_triggers; i++) { + sorted_triggers[i] = lttng_triggers_get_at_index(triggers, i); + } + + qsort(sorted_triggers, num_triggers, sizeof(struct lttng_trigger *), + compare_triggers_by_name); + + for (i = 0; i < num_triggers; i++) { + print_one_trigger(sorted_triggers[i]); + } + + goto end; + +error: + ret = 1; + +end: + argpar_parse_ret_fini(&argpar_parse_ret); + lttng_triggers_destroy(triggers); + + return ret; +} diff --git a/src/bin/lttng/commands/remove_trigger.c b/src/bin/lttng/commands/remove_trigger.c new file mode 100644 index 000000000..d00c603a5 --- /dev/null +++ b/src/bin/lttng/commands/remove_trigger.c @@ -0,0 +1,131 @@ +#include + +#include "../command.h" + +#include "common/argpar/argpar.h" + +#ifdef LTTNG_EMBED_HELP +static const char help_msg[] = +#include +; +#endif + +enum { + OPT_HELP, + OPT_LIST_OPTIONS, +}; + +static const +struct argpar_opt_descr remove_trigger_options[] = { + { OPT_HELP, 'h', "help", false }, + { OPT_LIST_OPTIONS, '\0', "list-options", false }, + ARGPAR_OPT_DESCR_SENTINEL, +}; + +int cmd_remove_trigger(int argc, const char **argv) +{ + int ret; + struct argpar_parse_ret argpar_parse_ret = { 0 }; + const char *id = NULL; + int i; + struct lttng_triggers *triggers = NULL; + unsigned int triggers_count; + enum lttng_trigger_status trigger_status; + const struct lttng_trigger *trigger_to_remove = NULL; + + argpar_parse_ret = argpar_parse(argc - 1, argv + 1, + remove_trigger_options, true); + if (!argpar_parse_ret.items) { + ERR("%s", argpar_parse_ret.error); + goto error; + } + + for (i = 0; i < argpar_parse_ret.items->n_items; i++) { + struct argpar_item *item = argpar_parse_ret.items->items[i]; + + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt *item_opt = + (struct argpar_item_opt *) item; + + switch (item_opt->descr->id) { + case OPT_HELP: + SHOW_HELP(); + ret = 0; + goto end; + + case OPT_LIST_OPTIONS: + list_cmd_options_argpar(stdout, + remove_trigger_options); + ret = 0; + goto end; + + default: + abort(); + } + + } else { + struct argpar_item_non_opt *item_non_opt = + (struct argpar_item_non_opt *) item; + + if (id) { + ERR("Unexpected argument: %s", item_non_opt->arg); + goto error; + } + + id = item_non_opt->arg; + } + } + + if (!id) { + ERR("Missing `id` argument."); + goto error; + } + + ret = lttng_list_triggers(&triggers); + if (ret != 0) { + ERR("Failed to get the list of triggers."); + goto error; + } + + trigger_status = lttng_triggers_get_count(triggers, &triggers_count); + assert(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + for (i = 0; i < triggers_count; i++) { + const struct lttng_trigger *trigger; + const char *trigger_name; + + trigger = lttng_triggers_get_at_index(triggers, i); + trigger_status = lttng_trigger_get_name(trigger, &trigger_name); + assert(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + if (strcmp(trigger_name, id) == 0) { + trigger_to_remove = trigger; + break; + } + } + + if (!trigger_to_remove) { + ERR("Couldn't find trigger with id `%s`.", id); + goto error; + } + + ret = lttng_unregister_trigger(trigger_to_remove); + if (ret != 0) { + ERR("Failed to unregister trigger `%s`.", id); + goto error; + } + + MSG("Removed trigger `%s`.", id); + + ret = 0; + goto end; + +error: + ret = 1; + +end: + argpar_parse_ret_fini(&argpar_parse_ret); + lttng_triggers_destroy(triggers); + + return ret; +} diff --git a/src/bin/lttng/lttng.c b/src/bin/lttng/lttng.c index 4907a43ba..437a9cc4c 100644 --- a/src/bin/lttng/lttng.c +++ b/src/bin/lttng/lttng.c @@ -65,6 +65,7 @@ static struct option long_options[] = { /* First level command */ static struct cmd_struct commands[] = { { "add-context", cmd_add_context}, + { "add-trigger", cmd_add_trigger}, { "create", cmd_create}, { "clear", cmd_clear}, { "destroy", cmd_destroy}, @@ -74,9 +75,11 @@ static struct cmd_struct commands[] = { { "enable-event", cmd_enable_events}, { "help", NULL}, { "list", cmd_list}, + { "list-triggers", cmd_list_triggers}, { "load", cmd_load}, { "metadata", cmd_metadata}, { "regenerate", cmd_regenerate}, + { "remove-trigger", cmd_remove_trigger}, { "rotate", cmd_rotate}, { "enable-rotation", cmd_enable_rotation}, { "disable-rotation", cmd_disable_rotation}, diff --git a/src/bin/lttng/uprobe.c b/src/bin/lttng/uprobe.c new file mode 100644 index 000000000..c51c44af0 --- /dev/null +++ b/src/bin/lttng/uprobe.c @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2020 EfficiOS, Inc. + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "uprobe.h" + +#include +#include +#include + +#include "common/compat/getenv.h" +#include "common/string-utils/string-utils.h" +#include "common/utils.h" +#include "lttng/constant.h" + +#include "command.h" + +/* + * Walk the directories in the PATH environment variable to find the target + * binary passed as parameter. + * + * On success, the full path of the binary is copied in binary_full_path out + * parameter. This buffer is allocated by the caller and must be at least + * LTTNG_PATH_MAX bytes long. + * On failure, returns -1; + */ +static +int walk_command_search_path(const char *binary, char *binary_full_path) +{ + char *tentative_binary_path = NULL; + char *command_search_path = NULL; + char *curr_search_dir_end = NULL; + char *curr_search_dir = NULL; + struct stat stat_output; + int ret = 0; + + command_search_path = lttng_secure_getenv("PATH"); + if (!command_search_path) { + ret = -1; + goto end; + } + + /* + * Duplicate the $PATH string as the char pointer returned by getenv() should + * not be modified. + */ + command_search_path = strdup(command_search_path); + if (!command_search_path) { + ret = -1; + goto end; + } + + /* + * This char array is used to concatenate path to binary to look for + * the binary. + */ + tentative_binary_path = zmalloc(LTTNG_PATH_MAX * sizeof(char)); + if (!tentative_binary_path) { + ret = -1; + goto alloc_error; + } + + curr_search_dir = command_search_path; + do { + /* + * Split on ':'. The return value of this call points to the + * matching character. + */ + curr_search_dir_end = strchr(curr_search_dir, ':'); + if (curr_search_dir_end != NULL) { + /* + * Add a NULL byte to the end of the first token so it + * can be used as a string. + */ + curr_search_dir_end[0] = '\0'; + } + + /* Empty the tentative path */ + memset(tentative_binary_path, 0, LTTNG_PATH_MAX * sizeof(char)); + + /* + * Build the tentative path to the binary using the current + * search directory and the name of the binary. + */ + ret = snprintf(tentative_binary_path, LTTNG_PATH_MAX, "%s/%s", + curr_search_dir, binary); + if (ret < 0) { + goto free_binary_path; + } + if (ret < LTTNG_PATH_MAX) { + /* + * Use STAT(2) to see if the file exists. + */ + ret = stat(tentative_binary_path, &stat_output); + if (ret == 0) { + /* + * Verify that it is a regular file or a + * symlink and not a special file (e.g. + * device). + */ + if (S_ISREG(stat_output.st_mode) + || S_ISLNK(stat_output.st_mode)) { + /* + * Found a match, set the out parameter + * and return success. + */ + ret = lttng_strncpy(binary_full_path, + tentative_binary_path, + LTTNG_PATH_MAX); + if (ret == -1) { + ERR("Source path does not fit " + "in destination buffer."); + } + goto free_binary_path; + } + } + } + /* Go to the next entry in the $PATH variable. */ + curr_search_dir = curr_search_dir_end + 1; + } while (curr_search_dir_end != NULL); + +free_binary_path: + free(tentative_binary_path); +alloc_error: + free(command_search_path); +end: + return ret; +} + +/* + * Check if the symbol field passed by the user is in fact an address or an + * offset from a symbol. Those two instrumentation types are not supported yet. + * It's expected to be a common mistake because of the existing --probe option + * that does support these formats. + * + * Here are examples of these unsupported formats for the --userspace-probe + * option: + * elf:/path/to/binary:0x400430 + * elf:/path/to/binary:4194364 + * elf:/path/to/binary:my_symbol+0x323 + * elf:/path/to/binary:my_symbol+43 + */ +static +int warn_userspace_probe_syntax(const char *symbol) +{ + int ret; + + /* Check if the symbol field is an hex address. */ + ret = sscanf(symbol, "0x%*x"); + if (ret > 0) { + /* If there is a match, print a warning and return an error. */ + ERR("Userspace probe on address not supported yet."); + ret = CMD_UNSUPPORTED; + goto error; + } + + /* Check if the symbol field is an decimal address. */ + ret = sscanf(symbol, "%*u"); + if (ret > 0) { + /* If there is a match, print a warning and return an error. */ + ERR("Userspace probe on address not supported yet."); + ret = CMD_UNSUPPORTED; + goto error; + } + + /* Check if the symbol field is symbol+hex_offset. */ + ret = sscanf(symbol, "%*[^+]+0x%*x"); + if (ret > 0) { + /* If there is a match, print a warning and return an error. */ + ERR("Userspace probe on symbol+offset not supported yet."); + ret = CMD_UNSUPPORTED; + goto error; + } + + /* Check if the symbol field is symbol+decimal_offset. */ + ret = sscanf(symbol, "%*[^+]+%*u"); + if (ret > 0) { + /* If there is a match, print a warning and return an error. */ + ERR("Userspace probe on symbol+offset not supported yet."); + ret = CMD_UNSUPPORTED; + goto error; + } + + ret = 0; + +error: + return ret; +} + +/* + * Parse userspace probe options + * Set the userspace probe fields in the lttng_event struct and set the + * target_path to the path to the binary. + */ +LTTNG_HIDDEN +int parse_userspace_probe_opts(const char *opt, + struct lttng_userspace_probe_location **probe_location) +{ + int ret = CMD_SUCCESS; + int num_token; + char **tokens = NULL; + char *target_path = NULL; + char *unescaped_target_path = NULL; + char *real_target_path = NULL; + char *symbol_name = NULL, *probe_name = NULL, *provider_name = NULL; + struct lttng_userspace_probe_location *probe_location_local = NULL; + struct lttng_userspace_probe_location_lookup_method *lookup_method = NULL; + + assert(opt); + + /* + * userspace probe fields are separated by ':'. + */ + tokens = strutils_split(opt, ':', 1); + num_token = strutils_array_of_strings_len(tokens); + + /* + * Early sanity check that the number of parameter is between 2 and 4 + * inclusively. + * elf:PATH:SYMBOL + * std:PATH:PROVIDER_NAME:PROBE_NAME + * PATH:SYMBOL (same behavior as ELF) + */ + if (num_token < 2 || num_token > 4) { + ret = CMD_ERROR; + goto end; + } + + /* + * Looking up the first parameter will tell the technique to use to + * interpret the userspace probe/function description. + */ + switch (num_token) { + case 2: + /* When the probe type is omitted we assume ELF for now. */ + case 3: + if (num_token == 3 && strcmp(tokens[0], "elf") == 0) { + target_path = tokens[1]; + symbol_name = tokens[2]; + } else if (num_token == 2) { + target_path = tokens[0]; + symbol_name = tokens[1]; + } else { + ret = CMD_ERROR; + goto end; + } + lookup_method = + lttng_userspace_probe_location_lookup_method_function_elf_create(); + if (!lookup_method) { + WARN("Failed to create ELF lookup method"); + ret = CMD_ERROR; + goto end; + } + break; + case 4: + if (strcmp(tokens[0], "sdt") == 0) { + target_path = tokens[1]; + provider_name = tokens[2]; + probe_name = tokens[3]; + } else { + ret = CMD_ERROR; + goto end; + } + lookup_method = + lttng_userspace_probe_location_lookup_method_tracepoint_sdt_create(); + if (!lookup_method) { + WARN("Failed to create SDT lookup method"); + ret = CMD_ERROR; + goto end; + } + break; + default: + ret = CMD_ERROR; + goto end; + } + + /* strutils_unescape_string allocates a new char *. */ + unescaped_target_path = strutils_unescape_string(target_path, 0); + if (!unescaped_target_path) { + ret = CMD_ERROR; + goto end; + } + + /* + * If there is not forward slash in the path. Walk the $PATH else + * expand. + */ + if (strchr(unescaped_target_path, '/') == NULL) { + /* Walk the $PATH variable to find the targeted binary. */ + real_target_path = zmalloc(LTTNG_PATH_MAX * sizeof(char)); + if (!real_target_path) { + PERROR("Error allocating path buffer"); + ret = CMD_ERROR; + goto end; + } + ret = walk_command_search_path(unescaped_target_path, real_target_path); + if (ret) { + ERR("Binary not found."); + ret = CMD_ERROR; + goto end; + } + } else { + /* + * Expand references to `/./` and `/../`. This function does not check + * if the file exists. This call returns an allocated buffer on + * success. + */ + real_target_path = utils_expand_path_keep_symlink(unescaped_target_path); + if (!real_target_path) { + ERR("Error expanding the path to binary."); + ret = CMD_ERROR; + goto end; + } + + /* + * Check if the file exists using access(2), If it does not, + * return an error. + */ + ret = access(real_target_path, F_OK); + if (ret) { + ERR("Cannot find binary at path: %s.", real_target_path); + ret = CMD_ERROR; + goto end; + } + } + + switch (lttng_userspace_probe_location_lookup_method_get_type(lookup_method)) { + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF: + /* + * Check for common mistakes in userspace probe description syntax. + */ + ret = warn_userspace_probe_syntax(symbol_name); + if (ret) { + goto end; + } + + probe_location_local = lttng_userspace_probe_location_function_create( + real_target_path, symbol_name, lookup_method); + if (!probe_location_local) { + WARN("Failed to create function probe location"); + ret = CMD_ERROR; + goto end; + } + + /* Ownership transferred to probe_location. */ + lookup_method = NULL; + break; + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: + probe_location_local = lttng_userspace_probe_location_tracepoint_create( + real_target_path, provider_name, probe_name, lookup_method); + if (!probe_location_local) { + WARN("Failed to create function probe location"); + ret = CMD_ERROR; + goto end; + } + + /* Ownership transferred to probe_location. */ + lookup_method = NULL; + break; + default: + ret = CMD_ERROR; + goto end; + } + + /* + * Everything went fine, transfer ownership of probe location to + * caller. + */ + *probe_location = probe_location_local; + probe_location_local = NULL; + +end: + lttng_userspace_probe_location_destroy(probe_location_local); + lttng_userspace_probe_location_lookup_method_destroy(lookup_method); + strutils_free_null_terminated_array_of_strings(tokens); + /* + * Freeing both char * here makes the error handling simplier. free() + * performs not action if the pointer is NULL. + */ + free(real_target_path); + free(unescaped_target_path); + + return ret; +} diff --git a/src/bin/lttng/uprobe.h b/src/bin/lttng/uprobe.h new file mode 100644 index 000000000..852f36642 --- /dev/null +++ b/src/bin/lttng/uprobe.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2020 EfficiOS, Inc. + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#ifndef SRC_BIN_LTTNG_UPROBE_H +#define SRC_BIN_LTTNG_UPROBE_H + +#include + +struct lttng_userspace_probe_location; + +LTTNG_HIDDEN +int parse_userspace_probe_opts(const char *opt, + struct lttng_userspace_probe_location **uprobe_loc); + +#endif /* SRC_BIN_LTTNG_UPROBE_H */ diff --git a/src/bin/lttng/utils.c b/src/bin/lttng/utils.c index 28b007b24..d9fd99ad5 100644 --- a/src/bin/lttng/utils.c +++ b/src/bin/lttng/utils.c @@ -128,6 +128,25 @@ void list_cmd_options(FILE *ofp, struct poptOption *options) } } +/* + * Same as list_cmd_options, but for options specified for argpar. + */ +void list_cmd_options_argpar(FILE *ofp, const struct argpar_opt_descr *options) +{ + int i; + const struct argpar_opt_descr *option = NULL; + + for (i = 0; options[i].long_name != NULL; i++) { + option = &options[i]; + + fprintf(ofp, "--%s\n", option->long_name); + + if (isprint(option->short_name)) { + fprintf(ofp, "-%c\n", option->short_name); + } + } +} + /* * fls: returns the position of the most significant bit. * Returns 0 if no bit is set, else returns the position of the most diff --git a/src/bin/lttng/utils.h b/src/bin/lttng/utils.h index 28ebc51a8..a51c8579a 100644 --- a/src/bin/lttng/utils.h +++ b/src/bin/lttng/utils.h @@ -9,6 +9,7 @@ #define _LTTNG_UTILS_H #include +#include "common/argpar/argpar.h" #include @@ -23,6 +24,7 @@ char *get_session_name(void); char *get_session_name_quiet(void); void list_commands(struct cmd_struct *commands, FILE *ofp); void list_cmd_options(FILE *ofp, struct poptOption *options); +void list_cmd_options_argpar(FILE *ofp, const struct argpar_opt_descr *options); /* * Return the minimum order for which x <= (1UL << order). diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 7ec0f52a5..80ff4b0be 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -2,7 +2,11 @@ AUTOMAKE_OPTIONS = subdir-objects -SUBDIRS = string-utils +SUBDIRS = \ + string-utils \ + bytecode \ + filter \ + argpar # Make sure to always distribute all folders # since SUBDIRS is decided at configure time. @@ -20,7 +24,10 @@ DIST_SUBDIRS = \ config \ consumer \ string-utils \ - fd-tracker + fd-tracker \ + bytecode \ + filter \ + argpar # Common library noinst_LTLIBRARIES = libcommon.la @@ -28,21 +35,39 @@ EXTRA_DIST = mi-lttng-4.0.xsd libcommon_la_SOURCES = \ actions/action.c \ + actions/group.c \ actions/notify.c \ - buffer-usage.c \ + actions/rotate-session.c \ + actions/snapshot-session.c \ + actions/start-session.c \ + actions/stop-session.c \ buffer-view.h buffer-view.c \ common.h \ - condition.c \ + conditions/buffer-usage.c \ + conditions/condition.c \ + conditions/event-rule.c \ + conditions/session-consumed-size.c \ + conditions/session-rotation.c \ context.c context.h \ - credentials.h \ + credentials.c credentials.h \ daemonize.c daemonize.h \ defaults.c \ + domain.c \ dynamic-array.c dynamic-array.h \ dynamic-buffer.c dynamic-buffer.h \ endpoint.c \ error.c error.h \ evaluation.c \ event.c \ + event-expr.c \ + event-expr-to-bytecode.c event-expr-to-bytecode.h \ + event-field-value.c \ + event-rule/event-rule.c \ + event-rule/kprobe.c \ + event-rule/kretprobe.c \ + event-rule/syscall.c \ + event-rule/uprobe.c \ + event-rule/tracepoint.c \ filter.c filter.h \ fs-handle.c fs-handle.h fs-handle-internal.h \ futex.c futex.h \ @@ -53,9 +78,8 @@ libcommon_la_SOURCES = \ pipe.c pipe.h \ readwrite.c readwrite.h \ runas.c runas.h \ - session-consumed-size.c \ session-descriptor.c \ - session-rotation.c \ + snapshot.c snapshot.h \ spawn-viewer.c spawn-viewer.h \ time.c \ trace-chunk.c trace-chunk.h \ @@ -75,10 +99,13 @@ libcommon_la_SOURCES += \ endif libcommon_la_LIBADD = \ + $(top_builddir)/src/common/bytecode/libbytecode.la \ $(top_builddir)/src/common/config/libconfig.la \ $(top_builddir)/src/common/compat/libcompat.la \ $(top_builddir)/src/common/hashtable/libhashtable.la \ - $(top_builddir)/src/common/fd-tracker/libfd-tracker.la + $(top_builddir)/src/common/fd-tracker/libfd-tracker.la \ + $(top_builddir)/src/common/filter/libfilter.la \ + -lmsgpackc if BUILD_LIB_COMPAT SUBDIRS += compat diff --git a/src/common/actions/action.c b/src/common/actions/action.c index 4cad01b74..fd0263c23 100644 --- a/src/common/actions/action.c +++ b/src/common/actions/action.c @@ -5,24 +5,40 @@ * */ +#include +#include #include +#include #include -#include -#include +#include +#include +#include +#include -static const char *lttng_action_type_string(enum lttng_action_type action_type) +LTTNG_HIDDEN +const char *lttng_action_type_string(enum lttng_action_type action_type) { switch (action_type) { case LTTNG_ACTION_TYPE_UNKNOWN: return "UNKNOWN"; + case LTTNG_ACTION_TYPE_GROUP: + return "GROUP"; case LTTNG_ACTION_TYPE_NOTIFY: return "NOTIFY"; + case LTTNG_ACTION_TYPE_ROTATE_SESSION: + return "ROTATE_SESSION"; + case LTTNG_ACTION_TYPE_SNAPSHOT_SESSION: + return "SNAPSHOT_SESSION"; + case LTTNG_ACTION_TYPE_START_SESSION: + return "START_SESSION"; + case LTTNG_ACTION_TYPE_STOP_SESSION: + return "STOP_SESSION"; default: return "???"; } } -enum lttng_action_type lttng_action_get_type(struct lttng_action *action) +enum lttng_action_type lttng_action_get_type(const struct lttng_action *action) { return action ? action->type : LTTNG_ACTION_TYPE_UNKNOWN; } @@ -40,22 +56,46 @@ void lttng_action_init( enum lttng_action_type type, action_validate_cb validate, action_serialize_cb serialize, + action_equal_cb equal, action_destroy_cb destroy) { + urcu_ref_init(&action->ref); action->type = type; action->validate = validate; action->serialize = serialize; + action->equal = equal; action->destroy = destroy; } -void lttng_action_destroy(struct lttng_action *action) +static +void action_destroy_ref(struct urcu_ref *ref) +{ + struct lttng_action *action = + container_of(ref, struct lttng_action, ref); + + action->destroy(action); +} + +LTTNG_HIDDEN +void lttng_action_get(struct lttng_action *action) +{ + urcu_ref_get(&action->ref); +} + +LTTNG_HIDDEN +void lttng_action_put(struct lttng_action *action) { if (!action) { return; } assert(action->destroy); - action->destroy(action); + urcu_ref_put(&action->ref, action_destroy_ref); +} + +void lttng_action_destroy(struct lttng_action *action) +{ + lttng_action_put(action); } LTTNG_HIDDEN @@ -125,6 +165,25 @@ ssize_t lttng_action_create_from_buffer(const struct lttng_buffer_view *view, case LTTNG_ACTION_TYPE_NOTIFY: create_from_buffer_cb = lttng_action_notify_create_from_buffer; break; + case LTTNG_ACTION_TYPE_ROTATE_SESSION: + create_from_buffer_cb = + lttng_action_rotate_session_create_from_buffer; + break; + case LTTNG_ACTION_TYPE_SNAPSHOT_SESSION: + create_from_buffer_cb = + lttng_action_snapshot_session_create_from_buffer; + break; + case LTTNG_ACTION_TYPE_START_SESSION: + create_from_buffer_cb = + lttng_action_start_session_create_from_buffer; + break; + case LTTNG_ACTION_TYPE_STOP_SESSION: + create_from_buffer_cb = + lttng_action_stop_session_create_from_buffer; + break; + case LTTNG_ACTION_TYPE_GROUP: + create_from_buffer_cb = lttng_action_group_create_from_buffer; + break; default: ERR("Failed to create action from buffer, unhandled action type: action-type=%u (%s)", action_comm->action_type, @@ -155,3 +214,28 @@ ssize_t lttng_action_create_from_buffer(const struct lttng_buffer_view *view, end: return consumed_len; } + +LTTNG_HIDDEN +bool lttng_action_is_equal(const struct lttng_action *a, + const struct lttng_action *b) +{ + bool is_equal = false; + + if (!a || !b) { + goto end; + } + + if (a->type != b->type) { + goto end; + } + + if (a == b) { + is_equal = true; + goto end; + } + + is_equal = a->equal ? a->equal(a, b) : true; +end: + return is_equal; +} + diff --git a/src/common/actions/group.c b/src/common/actions/group.c new file mode 100644 index 000000000..4a7dd2a9d --- /dev/null +++ b/src/common/actions/group.c @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include +#include + +struct lttng_action_group { + struct lttng_action parent; + + /* The array own the action elements */ + struct lttng_dynamic_pointer_array actions; +}; + +struct lttng_action_group_comm { + uint32_t action_count; + + /* + * Variable data: each element serialized sequentially. + */ + char data[]; +} LTTNG_PACKED; + +static void destroy_lttng_action_group_element(void *ptr) +{ + struct lttng_action *element = (struct lttng_action *) ptr; + lttng_action_destroy(element); +} + +static struct lttng_action_group *action_group_from_action( + const struct lttng_action *action) +{ + assert(action); + + return container_of(action, struct lttng_action_group, parent); +} + +static const struct lttng_action_group *action_group_from_action_const( + const struct lttng_action *action) +{ + assert(action); + + return container_of(action, struct lttng_action_group, parent); +} + +static bool lttng_action_group_validate(struct lttng_action *action) +{ + unsigned int i, count; + struct lttng_action_group *action_group; + bool valid; + + assert(lttng_action_get_type(action) == LTTNG_ACTION_TYPE_GROUP); + + action_group = action_group_from_action(action); + + count = lttng_dynamic_pointer_array_get_count(&action_group->actions); + + for (i = 0; i < count; i++) { + struct lttng_action *child = + lttng_dynamic_pointer_array_get_pointer( + &action_group->actions, i); + + assert(child); + + if (!lttng_action_validate(child)) { + valid = false; + goto end; + } + } + + valid = true; + +end: + return valid; +} + +static bool lttng_action_group_is_equal(const struct lttng_action *_a, const struct lttng_action *_b) +{ + bool is_equal = false; + unsigned int i; + unsigned int a_count, b_count; + + if (lttng_action_group_get_count(_a, &a_count) != LTTNG_ACTION_STATUS_OK) { + goto end; + } + if (lttng_action_group_get_count(_b, &b_count) != LTTNG_ACTION_STATUS_OK) { + goto end; + } + + + if (a_count != b_count) { + goto end; + } + + for (i = 0; i < a_count; i++) { + const struct lttng_action *child_a = + lttng_action_group_get_at_index_const(_a, i); + const struct lttng_action *child_b = + lttng_action_group_get_at_index_const(_b, i); + + assert(child_a); + assert(child_b); + + if (!lttng_action_is_equal(child_a, child_b)) { + goto end; + } + } + + is_equal = true; +end: + return is_equal; +} + +static int lttng_action_group_serialize( + struct lttng_action *action, struct lttng_dynamic_buffer *buf) +{ + struct lttng_action_group *action_group; + struct lttng_action_group_comm comm; + int ret; + unsigned int i, count; + + assert(action); + assert(buf); + assert(lttng_action_get_type(action) == LTTNG_ACTION_TYPE_GROUP); + + action_group = action_group_from_action(action); + + DBG("Serializing action group"); + + count = lttng_dynamic_pointer_array_get_count(&action_group->actions); + + comm.action_count = count; + + ret = lttng_dynamic_buffer_append(buf, &comm, sizeof(comm)); + if (ret) { + ret = -1; + goto end; + } + + for (i = 0; i < count; i++) { + struct lttng_action *child = + lttng_dynamic_pointer_array_get_pointer( + &action_group->actions, i); + assert(child); + + ret = lttng_action_serialize(child, buf); + if (ret) { + goto end; + } + } + + ret = 0; + +end: + return ret; +} + +static void lttng_action_group_destroy(struct lttng_action *action) +{ + struct lttng_action_group *action_group; + + if (!action) { + goto end; + } + + action_group = action_group_from_action(action); + + lttng_dynamic_pointer_array_reset(&action_group->actions); + + free(action_group); + +end: + return; +} + +ssize_t lttng_action_group_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **p_action) +{ + ssize_t consumed_len; + struct lttng_action_group_comm *comm; + struct lttng_action *group; + struct lttng_action *child_action = NULL; + enum lttng_action_status status; + size_t i; + + group = lttng_action_group_create(); + if (!group) { + consumed_len = -1; + goto end; + } + + comm = (struct lttng_action_group_comm *) view->data; + + consumed_len = sizeof(struct lttng_action_group_comm); + + for (i = 0; i < comm->action_count; i++) { + ssize_t consumed_len_child; + struct lttng_buffer_view child_view; + + child_view = lttng_buffer_view_from_view( + view, consumed_len, view->size - consumed_len); + consumed_len_child = lttng_action_create_from_buffer( + &child_view, &child_action); + + status = lttng_action_group_add_action(group, child_action); + if (status != LTTNG_ACTION_STATUS_OK) { + consumed_len = -1; + goto end; + } + child_action = NULL; + + consumed_len += consumed_len_child; + } + + *p_action = group; + group = NULL; + +end: + lttng_action_group_destroy(group); + lttng_action_destroy(child_action); + + return consumed_len; +} + +struct lttng_action *lttng_action_group_create(void) +{ + struct lttng_action_group *action_group; + struct lttng_action *action; + + action_group = zmalloc(sizeof(struct lttng_action_group)); + if (!action_group) { + action = NULL; + goto end; + } + + action = &action_group->parent; + + lttng_action_init(action, LTTNG_ACTION_TYPE_GROUP, + lttng_action_group_validate, + lttng_action_group_serialize, + lttng_action_group_is_equal, + lttng_action_group_destroy); + + lttng_dynamic_pointer_array_init(&action_group->actions, + destroy_lttng_action_group_element); + +end: + return action; +} + +enum lttng_action_status lttng_action_group_add_action( + struct lttng_action *group, struct lttng_action *action) +{ + struct lttng_action_group *action_group; + enum lttng_action_status status; + int ret; + + if (!group || + (lttng_action_get_type(group) != + LTTNG_ACTION_TYPE_GROUP) || + !action) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + /* + * Don't allow adding groups in groups for now, since we're afraid of + * cycles. + */ + if (lttng_action_get_type(action) == LTTNG_ACTION_TYPE_GROUP) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_group = action_group_from_action(group); + + ret = lttng_dynamic_pointer_array_add_pointer(&action_group->actions, + action); + if (ret < 0) { + status = LTTNG_ACTION_STATUS_ERROR; + goto end; + } + + status = LTTNG_ACTION_STATUS_OK; +end: + return status; +} + +enum lttng_action_status lttng_action_group_get_count( + const struct lttng_action *group, unsigned int *count) +{ + const struct lttng_action_group *action_group; + enum lttng_action_status status = LTTNG_ACTION_STATUS_OK; + + if (!group || (lttng_action_get_type_const(group) != + LTTNG_ACTION_TYPE_GROUP)) { + status = LTTNG_ACTION_STATUS_INVALID; + *count = 0; + goto end; + } + + action_group = action_group_from_action_const(group); + + *count = lttng_dynamic_pointer_array_get_count(&action_group->actions); +end: + return status; +} + +const struct lttng_action *lttng_action_group_get_at_index_const( + const struct lttng_action *group, unsigned int index) +{ + unsigned int count; + const struct lttng_action_group *action_group; + const struct lttng_action * action = NULL; + + if (lttng_action_group_get_count(group, &count) != + LTTNG_ACTION_STATUS_OK) { + goto end; + } + + if (index >= count) { + goto end; + } + + action_group = action_group_from_action_const(group); + action = lttng_dynamic_pointer_array_get_pointer(&action_group->actions, + index); +end: + return action; +} diff --git a/src/common/actions/notify.c b/src/common/actions/notify.c index ea68c75fd..03dcce73c 100644 --- a/src/common/actions/notify.c +++ b/src/common/actions/notify.c @@ -23,6 +23,14 @@ int lttng_action_notify_serialize(struct lttng_action *action, return 0; } +static +bool lttng_action_notify_is_equal(const struct lttng_action *a, + const struct lttng_action *b) +{ + /* TODO check type ??? */ + return true; +} + struct lttng_action *lttng_action_notify_create(void) { struct lttng_action_notify *notify; @@ -34,6 +42,7 @@ struct lttng_action *lttng_action_notify_create(void) lttng_action_init(¬ify->parent, LTTNG_ACTION_TYPE_NOTIFY, NULL, lttng_action_notify_serialize, + lttng_action_notify_is_equal, lttng_action_notify_destroy); end: return ¬ify->parent; diff --git a/src/common/actions/rotate-session.c b/src/common/actions/rotate-session.c new file mode 100644 index 000000000..254bd5eac --- /dev/null +++ b/src/common/actions/rotate-session.c @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include + +struct lttng_action_rotate_session { + struct lttng_action parent; + + /* Owned by this. */ + char *session_name; +}; + +struct lttng_action_rotate_session_comm { + /* Includes the trailing \0. */ + uint32_t session_name_len; + + /* + * Variable data: + * + * - session name (null terminated) + */ + char data[]; +} LTTNG_PACKED; + +static struct lttng_action_rotate_session *action_rotate_session_from_action( + struct lttng_action *action) +{ + assert(action); + + return container_of(action, struct lttng_action_rotate_session, parent); +} + +static const struct lttng_action_rotate_session *action_rotate_session_from_action_const( + const struct lttng_action *action) +{ + assert(action); + + return container_of(action, struct lttng_action_rotate_session, parent); +} + +static bool lttng_action_rotate_session_validate(struct lttng_action *action) +{ + bool valid; + struct lttng_action_rotate_session *action_rotate_session; + + if (!action) { + valid = false; + goto end; + } + + action_rotate_session = action_rotate_session_from_action(action); + + /* A non-empty session name is mandatory. */ + if (!action_rotate_session->session_name || + strlen(action_rotate_session->session_name) == 0) { + valid = false; + goto end; + } + + valid = true; +end: + return valid; +} + +static bool lttng_action_rotate_session_is_equal(const struct lttng_action *_a, const struct lttng_action *_b) +{ + bool is_equal = false; + const struct lttng_action_rotate_session *a, *b; + + a = action_rotate_session_from_action_const(_a); + b = action_rotate_session_from_action_const(_b); + + /* Action is not valid if this is not true. */ + assert(a->session_name); + assert(b->session_name); + if (strcmp(a->session_name, b->session_name)) { + goto end; + } + + is_equal = true; +end: + return is_equal; +} +static int lttng_action_rotate_session_serialize( + struct lttng_action *action, struct lttng_dynamic_buffer *buf) +{ + struct lttng_action_rotate_session *action_rotate_session; + struct lttng_action_rotate_session_comm comm; + size_t session_name_len; + int ret; + + assert(action); + assert(buf); + + action_rotate_session = action_rotate_session_from_action(action); + + assert(action_rotate_session->session_name); + + DBG("Serializing rotate session action: session-name: %s", + action_rotate_session->session_name); + + session_name_len = strlen(action_rotate_session->session_name) + 1; + comm.session_name_len = session_name_len; + + ret = lttng_dynamic_buffer_append(buf, &comm, sizeof(comm)); + if (ret) { + ret = -1; + goto end; + } + + ret = lttng_dynamic_buffer_append(buf, + action_rotate_session->session_name, session_name_len); + if (ret) { + ret = -1; + goto end; + } + + ret = 0; +end: + return ret; +} + +static void lttng_action_rotate_session_destroy(struct lttng_action *action) +{ + struct lttng_action_rotate_session *action_rotate_session; + + if (!action) { + goto end; + } + + action_rotate_session = action_rotate_session_from_action(action); + + free(action_rotate_session->session_name); + free(action_rotate_session); + +end: + return; +} + +ssize_t lttng_action_rotate_session_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **p_action) +{ + ssize_t consumed_len; + struct lttng_action_rotate_session_comm *comm; + const char *session_name; + struct lttng_action *action; + enum lttng_action_status status; + + action = lttng_action_rotate_session_create(); + if (!action) { + consumed_len = -1; + goto end; + } + + comm = (struct lttng_action_rotate_session_comm *) view->data; + session_name = (const char *) &comm->data; + + if (!lttng_buffer_view_contains_string( + view, session_name, comm->session_name_len)) { + consumed_len = -1; + goto end; + } + + status = lttng_action_rotate_session_set_session_name( + action, session_name); + if (status != LTTNG_ACTION_STATUS_OK) { + consumed_len = -1; + goto end; + } + + consumed_len = sizeof(struct lttng_action_rotate_session_comm) + + comm->session_name_len; + *p_action = action; + action = NULL; + +end: + lttng_action_rotate_session_destroy(action); + + return consumed_len; +} + +struct lttng_action *lttng_action_rotate_session_create(void) +{ + struct lttng_action *action; + + action = zmalloc(sizeof(struct lttng_action_rotate_session)); + if (!action) { + goto end; + } + + lttng_action_init(action, LTTNG_ACTION_TYPE_ROTATE_SESSION, + lttng_action_rotate_session_validate, + lttng_action_rotate_session_serialize, + lttng_action_rotate_session_is_equal, + lttng_action_rotate_session_destroy); + +end: + return action; +} + +enum lttng_action_status lttng_action_rotate_session_set_session_name( + struct lttng_action *action, const char *session_name) +{ + struct lttng_action_rotate_session *action_rotate_session; + enum lttng_action_status status; + + if (!action || !session_name || strlen(session_name) == 0) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_rotate_session = action_rotate_session_from_action(action); + + free(action_rotate_session->session_name); + + action_rotate_session->session_name = strdup(session_name); + if (!action_rotate_session->session_name) { + status = LTTNG_ACTION_STATUS_ERROR; + goto end; + } + + status = LTTNG_ACTION_STATUS_OK; +end: + return status; +} + +enum lttng_action_status lttng_action_rotate_session_get_session_name( + const struct lttng_action *action, const char **session_name) +{ + const struct lttng_action_rotate_session *action_rotate_session; + enum lttng_action_status status; + + if (!action || !session_name) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_rotate_session = action_rotate_session_from_action_const(action); + + *session_name = action_rotate_session->session_name; + + status = LTTNG_ACTION_STATUS_OK; +end: + return status; +} diff --git a/src/common/actions/snapshot-session.c b/src/common/actions/snapshot-session.c new file mode 100644 index 000000000..8e08bcf74 --- /dev/null +++ b/src/common/actions/snapshot-session.c @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct lttng_action_snapshot_session { + struct lttng_action parent; + + /* Owned by this. */ + char *session_name; + + /* + * When non-NULL, use this custom output when taking the snapshot, + * rather than the session's registered snapshot output. + * + * Owned by this. + */ + struct lttng_snapshot_output *output; +}; + +struct lttng_action_snapshot_session_comm { + /* All string lengths include the trailing \0. */ + uint32_t session_name_len; + uint32_t snapshot_output_len; + + /* + * Variable data (all strings are null-terminated): + * + * - session name string + * - snapshot output object + * + */ + char data[]; +} LTTNG_PACKED; + +static struct lttng_action_snapshot_session * +action_snapshot_session_from_action(struct lttng_action *action) +{ + assert(action); + + return container_of( + action, struct lttng_action_snapshot_session, parent); +} + +static const struct lttng_action_snapshot_session * +action_snapshot_session_from_action_const(const struct lttng_action *action) +{ + assert(action); + + return container_of( + action, struct lttng_action_snapshot_session, parent); +} + +static bool lttng_action_snapshot_session_validate(struct lttng_action *action) +{ + bool valid = false; + struct lttng_action_snapshot_session *action_snapshot_session; + + if (!action) { + goto end; + } + + action_snapshot_session = action_snapshot_session_from_action(action); + + /* A non-empty session name is mandatory. */ + if (!action_snapshot_session->session_name || + strlen(action_snapshot_session->session_name) == 0) { + goto end; + } + + if (action_snapshot_session->output && + !lttng_snapshot_output_validate(action_snapshot_session->output)) { + goto end; + } + + valid = true; +end: + return valid; +} + +static bool lttng_action_snapshot_session_is_equal(const struct lttng_action *_a, const struct lttng_action *_b) +{ + bool is_equal = false; + const struct lttng_action_snapshot_session *a, *b; + + a = action_snapshot_session_from_action_const(_a); + b = action_snapshot_session_from_action_const(_b); + + /* Action is not valid if this is not true. */ + assert(a->session_name); + assert(b->session_name); + if (strcmp(a->session_name, b->session_name)) { + goto end; + } + + if (a->output && b->output && + !lttng_snapshot_output_is_equal(a->output, b->output)) { + goto end; + } else if (!!a->output != !!b->output) { + goto end; + } + + is_equal = true; +end: + return is_equal; +} + +static size_t serialize_strlen(const char *s) +{ + + size_t len = 0; + + if (s) { + len = strlen(s) + 1; + } + + return len; +} + +static int lttng_action_snapshot_session_serialize( + struct lttng_action *action, struct lttng_dynamic_buffer *buf) +{ + struct lttng_action_snapshot_session *action_snapshot_session; + struct lttng_action_snapshot_session_comm comm; + struct lttng_dynamic_buffer snapshot_output_buf = { 0 }; + int ret; + + assert(action); + assert(buf); + + lttng_dynamic_buffer_init(&snapshot_output_buf); + + action_snapshot_session = action_snapshot_session_from_action(action); + + assert(action_snapshot_session->session_name); + DBG("Serializing snapshot session action: session-name: %s", + action_snapshot_session->session_name); + + /* Serialize the snapshot output object first, so we know its length. */ + if (action_snapshot_session->output) { + ret = lttng_snapshot_output_serialize( + action_snapshot_session->output, &snapshot_output_buf); + if (ret) { + goto end; + } + } + + comm.session_name_len = + serialize_strlen(action_snapshot_session->session_name); + comm.snapshot_output_len = snapshot_output_buf.size; + + ret = lttng_dynamic_buffer_append(buf, &comm, sizeof(comm)); + if (ret) { + goto end; + } + + ret = lttng_dynamic_buffer_append(buf, + action_snapshot_session->session_name, + comm.session_name_len); + if (ret) { + goto end; + } + + ret = lttng_dynamic_buffer_append_buffer(buf, &snapshot_output_buf); + if (ret) { + goto end; + } + +end: + lttng_dynamic_buffer_reset(&snapshot_output_buf); + return ret; +} + +static void lttng_action_snapshot_session_destroy(struct lttng_action *action) +{ + struct lttng_action_snapshot_session *action_snapshot_session; + + if (!action) { + goto end; + } + + action_snapshot_session = action_snapshot_session_from_action(action); + + free(action_snapshot_session->session_name); + lttng_snapshot_output_destroy(action_snapshot_session->output); + free(action_snapshot_session); + +end: + return; +} + +ssize_t lttng_action_snapshot_session_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **p_action) +{ + ssize_t consumed_len; + struct lttng_action_snapshot_session_comm *comm; + const char *variable_data; + struct lttng_action *action; + enum lttng_action_status status; + struct lttng_snapshot_output *snapshot_output = NULL; + + action = lttng_action_snapshot_session_create(); + if (!action) { + goto error; + } + + comm = (struct lttng_action_snapshot_session_comm *) view->data; + variable_data = (const char *) &comm->data; + + consumed_len = sizeof(struct lttng_action_snapshot_session_comm); + + if (!lttng_buffer_view_contains_string( + view, variable_data, comm->session_name_len)) { + goto error; + } + + status = lttng_action_snapshot_session_set_session_name( + action, variable_data); + if (status != LTTNG_ACTION_STATUS_OK) { + goto error; + } + + variable_data += comm->session_name_len; + consumed_len += comm->session_name_len; + + /* If there is a snapshot output object, deserialize it. */ + if (comm->snapshot_output_len > 0) { + ssize_t snapshot_output_consumed_len; + enum lttng_action_status action_status; + struct lttng_buffer_view snapshot_output_buffer_view = + lttng_buffer_view_from_view(view, consumed_len, + comm->snapshot_output_len); + if (!snapshot_output_buffer_view.data) { + fprintf(stderr, "Failed to create buffer view for snapshot output.\n"); + goto error; + } + + snapshot_output_consumed_len = + lttng_snapshot_output_create_from_buffer( + &snapshot_output_buffer_view, + &snapshot_output); + if (snapshot_output_consumed_len != comm->snapshot_output_len) { + fprintf(stderr, + "Failed to deserialize snapshot output object: " + "consumed-len: %zd, expected-len: %" PRIu32, + snapshot_output_consumed_len, + comm->snapshot_output_len); + goto error; + } + + action_status = lttng_action_snapshot_session_set_output( + action, snapshot_output); + if (action_status != LTTNG_ACTION_STATUS_OK) { + goto error; + } + + /* Ownership has been transferred to the action. */ + snapshot_output = NULL; + } + + variable_data += comm->snapshot_output_len; + consumed_len += comm->snapshot_output_len; + *p_action = action; + action = NULL; + + goto end; + +error: + consumed_len = -1; + +end: + lttng_action_snapshot_session_destroy(action); + lttng_snapshot_output_destroy(snapshot_output); + + return consumed_len; +} + +struct lttng_action *lttng_action_snapshot_session_create(void) +{ + struct lttng_action *action; + + action = zmalloc(sizeof(struct lttng_action_snapshot_session)); + if (!action) { + goto end; + } + + lttng_action_init(action, LTTNG_ACTION_TYPE_SNAPSHOT_SESSION, + lttng_action_snapshot_session_validate, + lttng_action_snapshot_session_serialize, + lttng_action_snapshot_session_is_equal, + lttng_action_snapshot_session_destroy); + +end: + return action; +} + +enum lttng_action_status lttng_action_snapshot_session_set_session_name( + struct lttng_action *action, const char *session_name) +{ + struct lttng_action_snapshot_session *action_snapshot_session; + enum lttng_action_status status; + + if (!action || !session_name || strlen(session_name) == 0) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_snapshot_session = action_snapshot_session_from_action(action); + + free(action_snapshot_session->session_name); + + action_snapshot_session->session_name = strdup(session_name); + if (!action_snapshot_session->session_name) { + status = LTTNG_ACTION_STATUS_ERROR; + goto end; + } + + status = LTTNG_ACTION_STATUS_OK; +end: + return status; +} + +enum lttng_action_status lttng_action_snapshot_session_get_session_name( + const struct lttng_action *action, const char **session_name) +{ + const struct lttng_action_snapshot_session *action_snapshot_session; + enum lttng_action_status status; + + if (!action || !session_name) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_snapshot_session = action_snapshot_session_from_action_const(action); + + if (action_snapshot_session->session_name) { + *session_name = action_snapshot_session->session_name; + status = LTTNG_ACTION_STATUS_OK; + } else { + status = LTTNG_ACTION_STATUS_UNSET; + } + +end: + + return status; +} + +enum lttng_action_status lttng_action_snapshot_session_set_output( + struct lttng_action *action, + struct lttng_snapshot_output *output) +{ + struct lttng_action_snapshot_session *action_snapshot_session; + enum lttng_action_status status; + + if (!action || !output) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_snapshot_session = action_snapshot_session_from_action(action); + + lttng_snapshot_output_destroy(action_snapshot_session->output); + action_snapshot_session->output = output; + + status = LTTNG_ACTION_STATUS_OK; + +end: + return status; +} + +enum lttng_action_status lttng_action_snapshot_session_get_output_const( + const struct lttng_action *action, + const struct lttng_snapshot_output **output) +{ + const struct lttng_action_snapshot_session *action_snapshot_session; + enum lttng_action_status status; + + if (!action || !output) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_snapshot_session = action_snapshot_session_from_action_const(action); + + if (action_snapshot_session->output) { + *output = action_snapshot_session->output; + status = LTTNG_ACTION_STATUS_OK; + } else { + status = LTTNG_ACTION_STATUS_UNSET; + } + +end: + return status; +} diff --git a/src/common/actions/start-session.c b/src/common/actions/start-session.c new file mode 100644 index 000000000..19cc34eac --- /dev/null +++ b/src/common/actions/start-session.c @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include + +struct lttng_action_start_session { + struct lttng_action parent; + + /* Owned by this. */ + char *session_name; +}; + +struct lttng_action_start_session_comm { + /* Includes the trailing \0. */ + uint32_t session_name_len; + + /* + * Variable data: + * + * - session name (null terminated) + */ + char data[]; +} LTTNG_PACKED; + +static struct lttng_action_start_session *action_start_session_from_action( + struct lttng_action *action) +{ + assert(action); + + return container_of(action, struct lttng_action_start_session, parent); +} + +static const struct lttng_action_start_session *action_start_session_from_action_const( + const struct lttng_action *action) +{ + assert(action); + + return container_of(action, struct lttng_action_start_session, parent); +} + +static bool lttng_action_start_session_validate(struct lttng_action *action) +{ + bool valid; + struct lttng_action_start_session *action_start_session; + + if (!action) { + valid = false; + goto end; + } + + action_start_session = action_start_session_from_action(action); + + /* A non-empty session name is mandatory. */ + if (!action_start_session->session_name || + strlen(action_start_session->session_name) == 0) { + valid = false; + goto end; + } + + valid = true; +end: + return valid; +} + +static bool lttng_action_start_session_is_equal(const struct lttng_action *_a, const struct lttng_action *_b) +{ + bool is_equal = false; + struct lttng_action_start_session *a, *b; + + a = container_of(_a, struct lttng_action_start_session, parent); + b = container_of(_b, struct lttng_action_start_session, parent); + + /* Action is not valid if this is not true. */ + assert(a->session_name); + assert(b->session_name); + if (strcmp(a->session_name, b->session_name)) { + goto end; + } + + is_equal = true; +end: + return is_equal; +} + +static int lttng_action_start_session_serialize( + struct lttng_action *action, struct lttng_dynamic_buffer *buf) +{ + struct lttng_action_start_session *action_start_session; + struct lttng_action_start_session_comm comm; + size_t session_name_len; + int ret; + + assert(action); + assert(buf); + + action_start_session = action_start_session_from_action(action); + + assert(action_start_session->session_name); + + DBG("Serializing start session action: session-name: %s", + action_start_session->session_name); + + session_name_len = strlen(action_start_session->session_name) + 1; + comm.session_name_len = session_name_len; + + ret = lttng_dynamic_buffer_append(buf, &comm, sizeof(comm)); + if (ret) { + ret = -1; + goto end; + } + + ret = lttng_dynamic_buffer_append(buf, + action_start_session->session_name, session_name_len); + if (ret) { + ret = -1; + goto end; + } + + ret = 0; +end: + return ret; +} + +static void lttng_action_start_session_destroy(struct lttng_action *action) +{ + struct lttng_action_start_session *action_start_session; + + if (!action) { + goto end; + } + + action_start_session = action_start_session_from_action(action); + + free(action_start_session->session_name); + free(action_start_session); + +end: + return; +} + +ssize_t lttng_action_start_session_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **p_action) +{ + ssize_t consumed_len; + struct lttng_action_start_session_comm *comm; + const char *session_name; + struct lttng_action *action; + enum lttng_action_status status; + + action = lttng_action_start_session_create(); + if (!action) { + consumed_len = -1; + goto end; + } + + comm = (struct lttng_action_start_session_comm *) view->data; + session_name = (const char *) &comm->data; + + if (!lttng_buffer_view_contains_string( + view, session_name, comm->session_name_len)) { + consumed_len = -1; + goto end; + } + + status = lttng_action_start_session_set_session_name( + action, session_name); + if (status != LTTNG_ACTION_STATUS_OK) { + consumed_len = -1; + goto end; + } + + consumed_len = sizeof(struct lttng_action_start_session_comm) + + comm->session_name_len; + *p_action = action; + action = NULL; + +end: + lttng_action_start_session_destroy(action); + + return consumed_len; +} + +struct lttng_action *lttng_action_start_session_create(void) +{ + struct lttng_action *action; + + action = zmalloc(sizeof(struct lttng_action_start_session)); + if (!action) { + goto end; + } + + lttng_action_init(action, LTTNG_ACTION_TYPE_START_SESSION, + lttng_action_start_session_validate, + lttng_action_start_session_serialize, + lttng_action_start_session_is_equal, + lttng_action_start_session_destroy); + +end: + return action; +} + +enum lttng_action_status lttng_action_start_session_set_session_name( + struct lttng_action *action, const char *session_name) +{ + struct lttng_action_start_session *action_start_session; + enum lttng_action_status status; + + if (!action || !session_name || strlen(session_name) == 0) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_start_session = action_start_session_from_action(action); + + free(action_start_session->session_name); + + action_start_session->session_name = strdup(session_name); + if (!action_start_session->session_name) { + status = LTTNG_ACTION_STATUS_ERROR; + goto end; + } + + status = LTTNG_ACTION_STATUS_OK; +end: + return status; +} + +enum lttng_action_status lttng_action_start_session_get_session_name( + const struct lttng_action *action, const char **session_name) +{ + const struct lttng_action_start_session *action_start_session; + enum lttng_action_status status; + + if (!action || !session_name) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_start_session = action_start_session_from_action_const(action); + + *session_name = action_start_session->session_name; + + status = LTTNG_ACTION_STATUS_OK; +end: + return status; +} diff --git a/src/common/actions/stop-session.c b/src/common/actions/stop-session.c new file mode 100644 index 000000000..2f80101ae --- /dev/null +++ b/src/common/actions/stop-session.c @@ -0,0 +1,273 @@ +/* +<<<<<<< HEAD + * Copyright (C) 2019 EfficiOS, Inc. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +======= + * Copyright (C) 2019 Simon Marchi + * + * SPDX-License-Identifier: LGPL-2.1-only + * +>>>>>>> 2e5f09656... actions: introduce stop session action + */ + +#include +#include +#include +#include +#include +#include + +struct lttng_action_stop_session { + struct lttng_action parent; + + /* Owned by this. */ + char *session_name; +}; + +struct lttng_action_stop_session_comm { + /* Includes the trailing \0. */ + uint32_t session_name_len; + + /* + * Variable data: + * + * - session name (null terminated) + */ + char data[]; +} LTTNG_PACKED; + +static struct lttng_action_stop_session *action_stop_session_from_action( + struct lttng_action *action) +{ + assert(action); + + return container_of(action, struct lttng_action_stop_session, parent); +} + +static const struct lttng_action_stop_session *action_stop_session_from_action_const( + const struct lttng_action *action) +{ + assert(action); + + return container_of(action, struct lttng_action_stop_session, parent); +} + +static bool lttng_action_stop_session_validate(struct lttng_action *action) +{ + bool valid; + struct lttng_action_stop_session *action_stop_session; + + if (!action) { + valid = false; + goto end; + } + + action_stop_session = action_stop_session_from_action(action); + + /* A non-empty session name is mandatory. */ + if (!action_stop_session->session_name || + strlen(action_stop_session->session_name) == 0) { + valid = false; + goto end; + } + + valid = true; +end: + return valid; +} + +static bool lttng_action_stop_session_is_equal(const struct lttng_action *_a, const struct lttng_action *_b) +{ + bool is_equal = false; + const struct lttng_action_stop_session *a, *b; + + a = action_stop_session_from_action_const(_a); + b = action_stop_session_from_action_const(_b); + + /* Action is not valid if this is not true. */ + assert(a->session_name); + assert(b->session_name); + if (strcmp(a->session_name, b->session_name)) { + goto end; + } + + is_equal = true; +end: + return is_equal; +} + +static int lttng_action_stop_session_serialize( + struct lttng_action *action, struct lttng_dynamic_buffer *buf) +{ + struct lttng_action_stop_session *action_stop_session; + struct lttng_action_stop_session_comm comm; + size_t session_name_len; + int ret; + + assert(action); + assert(buf); + + action_stop_session = action_stop_session_from_action(action); + + assert(action_stop_session->session_name); + + DBG("Serializing stop session action: session-name: %s", + action_stop_session->session_name); + + session_name_len = strlen(action_stop_session->session_name) + 1; + comm.session_name_len = session_name_len; + + ret = lttng_dynamic_buffer_append(buf, &comm, sizeof(comm)); + if (ret) { + ret = -1; + goto end; + } + + ret = lttng_dynamic_buffer_append(buf, + action_stop_session->session_name, session_name_len); + if (ret) { + ret = -1; + goto end; + } + + ret = 0; +end: + return ret; +} + +static void lttng_action_stop_session_destroy(struct lttng_action *action) +{ + struct lttng_action_stop_session *action_stop_session; + + if (!action) { + goto end; + } + + action_stop_session = action_stop_session_from_action(action); + + free(action_stop_session->session_name); + free(action_stop_session); + +end: + return; +} + +ssize_t lttng_action_stop_session_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **p_action) +{ + ssize_t consumed_len; + struct lttng_action_stop_session_comm *comm; + const char *session_name; + struct lttng_action *action; + enum lttng_action_status status; + + action = lttng_action_stop_session_create(); + if (!action) { + consumed_len = -1; + goto end; + } + + comm = (struct lttng_action_stop_session_comm *) view->data; + session_name = (const char *) &comm->data; + + if (!lttng_buffer_view_contains_string( + view, session_name, comm->session_name_len)) { + consumed_len = -1; + goto end; + } + + status = lttng_action_stop_session_set_session_name( + action, session_name); + if (status != LTTNG_ACTION_STATUS_OK) { + consumed_len = -1; + goto end; + } + + consumed_len = sizeof(struct lttng_action_stop_session_comm) + + comm->session_name_len; + *p_action = action; + action = NULL; + +end: + lttng_action_stop_session_destroy(action); + + return consumed_len; +} + +struct lttng_action *lttng_action_stop_session_create(void) +{ + struct lttng_action *action; + + action = zmalloc(sizeof(struct lttng_action_stop_session)); + if (!action) { + goto end; + } + + lttng_action_init(action, LTTNG_ACTION_TYPE_STOP_SESSION, + lttng_action_stop_session_validate, + lttng_action_stop_session_serialize, + lttng_action_stop_session_is_equal, + lttng_action_stop_session_destroy); + +end: + return action; +} + +enum lttng_action_status lttng_action_stop_session_set_session_name( + struct lttng_action *action, const char *session_name) +{ + struct lttng_action_stop_session *action_stop_session; + enum lttng_action_status status; + + if (!action || !session_name || strlen(session_name) == 0) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_stop_session = action_stop_session_from_action(action); + + free(action_stop_session->session_name); + + action_stop_session->session_name = strdup(session_name); + if (!action_stop_session->session_name) { + status = LTTNG_ACTION_STATUS_ERROR; + goto end; + } + + status = LTTNG_ACTION_STATUS_OK; +end: + return status; +} + +enum lttng_action_status lttng_action_stop_session_get_session_name( + const struct lttng_action *action, const char **session_name) +{ + const struct lttng_action_stop_session *action_stop_session; + enum lttng_action_status status; + + if (!action || !session_name) { + status = LTTNG_ACTION_STATUS_INVALID; + goto end; + } + + action_stop_session = action_stop_session_from_action_const(action); + + *session_name = action_stop_session->session_name; + + status = LTTNG_ACTION_STATUS_OK; +end: + return status; +} diff --git a/src/common/argpar/Makefile.am b/src/common/argpar/Makefile.am new file mode 100644 index 000000000..175526abe --- /dev/null +++ b/src/common/argpar/Makefile.am @@ -0,0 +1,3 @@ +noinst_LTLIBRARIES = libargpar.la + +libargpar_la_SOURCES = argpar.c argpar.h diff --git a/src/common/argpar/argpar.c b/src/common/argpar/argpar.c new file mode 100644 index 000000000..a8977ea3b --- /dev/null +++ b/src/common/argpar/argpar.c @@ -0,0 +1,746 @@ +/* + * Copyright 2019 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "argpar.h" + +#define argpar_realloc(_ptr, _type, _nmemb) ((_type *) realloc(_ptr, (_nmemb) * sizeof(_type))) +#define argpar_calloc(_type, _nmemb) ((_type *) calloc((_nmemb), sizeof(_type))) +#define argpar_zalloc(_type) argpar_calloc(_type, 1) + +#define ARGPAR_ASSERT(_cond) assert(_cond) + +/* + * Structure holding the argpar state between successive argpar_state_parse_next + * calls. + * + * Created with `argpar_state_create` and destroyed with `argpar_state_destroy`. + */ +struct argpar_state { + /* + * Data provided by the user in argpar_state_create, does not change + * afterwards. + */ + unsigned int argc; + const char * const *argv; + const struct argpar_opt_descr *descrs; + + /* + * Index of the argument to process in the next argpar_state_parse_next + * call. + */ + unsigned int i; + + /* Counter of non-option arguments. */ + int non_opt_index; + + /* + * Short option state: if set, we are in the middle of a short option + * group, so we should resume there at the next argpar_state_parse_next + * call. + */ + const char *short_opt_ch; +}; + +static +char *argpar_vasprintf(const char *fmt, va_list args) +{ + int len1, len2; + char *str; + va_list args2; + + va_copy(args2, args); + + len1 = vsnprintf(NULL, 0, fmt, args); + if (len1 < 0) { + str = NULL; + goto end; + } + + str = malloc(len1 + 1); + if (!str) { + goto end; + } + + len2 = vsnprintf(str, len1 + 1, fmt, args2); + + ARGPAR_ASSERT(len1 == len2); + +end: + va_end(args2); + return str; +} + + +static +char *argpar_asprintf(const char *fmt, ...) +{ + va_list args; + char *str; + + va_start(args, fmt); + str = argpar_vasprintf(fmt, args); + va_end(args); + + return str; +} + +static +bool argpar_string_append_printf(char **str, const char *fmt, ...) +{ + char *new_str = NULL; + char *addendum; + bool success; + va_list args; + + ARGPAR_ASSERT(str); + + va_start(args, fmt); + addendum = argpar_vasprintf(fmt, args); + va_end(args); + + if (!addendum) { + success = false; + goto end; + } + + new_str = argpar_asprintf("%s%s", *str ? *str : "", addendum); + if (!new_str) { + success = false; + goto end; + } + + free(*str); + *str = new_str; + + success = true; + +end: + free(addendum); + + return success; +} + +ARGPAR_HIDDEN +void argpar_item_destroy(struct argpar_item *item) +{ + if (!item) { + goto end; + } + + if (item->type == ARGPAR_ITEM_TYPE_OPT) { + struct argpar_item_opt * const opt_item = (void *) item; + + free((void *) opt_item->arg); + } + + free(item); + +end: + return; +} + +static +bool push_item(struct argpar_item_array * const array, + struct argpar_item * const item) +{ + bool success; + + ARGPAR_ASSERT(array); + ARGPAR_ASSERT(item); + + if (array->n_items == array->n_alloc) { + unsigned int new_n_alloc = array->n_alloc * 2; + struct argpar_item **new_items; + + new_items = argpar_realloc(array->items, + struct argpar_item *, new_n_alloc); + if (!new_items) { + success = false; + goto end; + } + + array->n_alloc = new_n_alloc; + array->items = new_items; + } + + array->items[array->n_items] = item; + array->n_items++; + + success = true; + +end: + return success; +} + +static +void destroy_item_array(struct argpar_item_array * const array) +{ + if (array) { + unsigned int i; + + for (i = 0; i < array->n_items; i++) { + argpar_item_destroy(array->items[i]); + } + + free(array->items); + free(array); + } +} + +static +struct argpar_item_array *new_item_array(void) +{ + struct argpar_item_array *ret; + const int initial_size = 10; + + ret = argpar_zalloc(struct argpar_item_array); + if (!ret) { + goto end; + } + + ret->items = argpar_calloc(struct argpar_item *, initial_size); + if (!ret->items) { + goto error; + } + + ret->n_alloc = initial_size; + + goto end; + +error: + destroy_item_array(ret); + ret = NULL; + +end: + return ret; +} + +static +struct argpar_item_opt *create_opt_item( + const struct argpar_opt_descr * const descr, + const char * const arg) +{ + struct argpar_item_opt *opt_item = + argpar_zalloc(struct argpar_item_opt); + + if (!opt_item) { + goto end; + } + + opt_item->base.type = ARGPAR_ITEM_TYPE_OPT; + opt_item->descr = descr; + + if (arg) { + opt_item->arg = strdup(arg); + if (!opt_item->arg) { + goto error; + } + } + + goto end; + +error: + argpar_item_destroy(&opt_item->base); + opt_item = NULL; + +end: + return opt_item; +} + +static +struct argpar_item_non_opt *create_non_opt_item(const char * const arg, + const unsigned int orig_index, + const unsigned int non_opt_index) +{ + struct argpar_item_non_opt * const non_opt_item = + argpar_zalloc(struct argpar_item_non_opt); + + if (!non_opt_item) { + goto end; + } + + non_opt_item->base.type = ARGPAR_ITEM_TYPE_NON_OPT; + non_opt_item->arg = arg; + non_opt_item->orig_index = orig_index; + non_opt_item->non_opt_index = non_opt_index; + +end: + return non_opt_item; +} + +static +const struct argpar_opt_descr *find_descr( + const struct argpar_opt_descr * const descrs, + const char short_name, const char * const long_name) +{ + const struct argpar_opt_descr *descr; + + for (descr = descrs; descr->short_name || descr->long_name; descr++) { + if (short_name && descr->short_name && + short_name == descr->short_name) { + goto end; + } + + if (long_name && descr->long_name && + strcmp(long_name, descr->long_name) == 0) { + goto end; + } + } + +end: + return !descr->short_name && !descr->long_name ? NULL : descr; +} + +enum parse_orig_arg_opt_ret { + PARSE_ORIG_ARG_OPT_RET_OK, + PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT = -2, + PARSE_ORIG_ARG_OPT_RET_ERROR = -1, +}; + +static +enum parse_orig_arg_opt_ret parse_short_opts(const char * const short_opts, + const char * const next_orig_arg, + const struct argpar_opt_descr * const descrs, + struct argpar_state *state, + char **error, + struct argpar_item **item) +{ + enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + bool used_next_orig_arg = false; + + if (strlen(short_opts) == 0) { + argpar_string_append_printf(error, "Invalid argument"); + goto error; + } + + if (!state->short_opt_ch) { + state->short_opt_ch = short_opts; + } + + const char *opt_arg = NULL; + const struct argpar_opt_descr *descr; + struct argpar_item_opt *opt_item; + + /* Find corresponding option descriptor */ + descr = find_descr(descrs, *state->short_opt_ch, NULL); + if (!descr) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; + argpar_string_append_printf(error, + "Unknown option `-%c`", *state->short_opt_ch); + goto error; + } + + if (descr->with_arg) { + if (state->short_opt_ch[1]) { + /* `-oarg` form */ + opt_arg = &state->short_opt_ch[1]; + } else { + /* `-o arg` form */ + opt_arg = next_orig_arg; + used_next_orig_arg = true; + } + + /* + * We accept `-o ''` (empty option's argument), + * but not `-o` alone if an option's argument is + * expected. + */ + if (!opt_arg || (state->short_opt_ch[1] && strlen(opt_arg) == 0)) { + argpar_string_append_printf(error, + "Missing required argument for option `-%c`", + *state->short_opt_ch); + used_next_orig_arg = false; + goto error; + } + } + + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + goto error; + } + + *item = &opt_item->base; + + state->short_opt_ch++; + + if (descr->with_arg || !*state->short_opt_ch) { + /* Option has an argument: no more options */ + state->short_opt_ch = NULL; + + if (used_next_orig_arg) { + state->i += 2; + } else { + state->i += 1; + } + } + + goto end; + +error: + if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + } + +end: + return ret; +} + +static +enum parse_orig_arg_opt_ret parse_long_opt(const char * const long_opt_arg, + const char * const next_orig_arg, + const struct argpar_opt_descr * const descrs, + struct argpar_state *state, + char **error, + struct argpar_item **item) +{ + const size_t max_len = 127; + enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + const struct argpar_opt_descr *descr; + struct argpar_item_opt *opt_item; + bool used_next_orig_arg = false; + + /* Option's argument, if any */ + const char *opt_arg = NULL; + + /* Position of first `=`, if any */ + const char *eq_pos; + + /* Buffer holding option name when `long_opt_arg` contains `=` */ + char buf[max_len + 1]; + + /* Option name */ + const char *long_opt_name = long_opt_arg; + + if (strlen(long_opt_arg) == 0) { + argpar_string_append_printf(error, + "Invalid argument"); + goto error; + } + + /* Find the first `=` in original argument */ + eq_pos = strchr(long_opt_arg, '='); + if (eq_pos) { + const size_t long_opt_name_size = eq_pos - long_opt_arg; + + /* Isolate the option name */ + if (long_opt_name_size > max_len) { + argpar_string_append_printf(error, + "Invalid argument `--%s`", long_opt_arg); + goto error; + } + + memcpy(buf, long_opt_arg, long_opt_name_size); + buf[long_opt_name_size] = '\0'; + long_opt_name = buf; + } + + /* Find corresponding option descriptor */ + descr = find_descr(descrs, '\0', long_opt_name); + if (!descr) { + argpar_string_append_printf(error, + "Unknown option `--%s`", long_opt_name); + ret = PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT; + goto error; + } + + /* Find option's argument if any */ + if (descr->with_arg) { + if (eq_pos) { + /* `--long-opt=arg` style */ + opt_arg = eq_pos + 1; + } else { + /* `--long-opt arg` style */ + if (!next_orig_arg) { + argpar_string_append_printf(error, + "Missing required argument for option `--%s`", + long_opt_name); + goto error; + } + + opt_arg = next_orig_arg; + used_next_orig_arg = true; + } + } + + /* Create and append option argument */ + opt_item = create_opt_item(descr, opt_arg); + if (!opt_item) { + goto error; + } + + if (used_next_orig_arg) { + state->i += 2; + } else { + state->i += 1; + } + + *item = &opt_item->base; + goto end; + +error: + if (ret == PARSE_ORIG_ARG_OPT_RET_OK) { + ret = PARSE_ORIG_ARG_OPT_RET_ERROR; + } + +end: + return ret; +} + +static +enum parse_orig_arg_opt_ret parse_orig_arg_opt(const char * const orig_arg, + const char * const next_orig_arg, + const struct argpar_opt_descr * const descrs, + struct argpar_state *state, + char **error, + struct argpar_item **item) +{ + enum parse_orig_arg_opt_ret ret = PARSE_ORIG_ARG_OPT_RET_OK; + + ARGPAR_ASSERT(orig_arg[0] == '-'); + + if (orig_arg[1] == '-') { + /* Long option */ + ret = parse_long_opt(&orig_arg[2], + next_orig_arg, descrs, state, error, item); + } else { + /* Short option */ + ret = parse_short_opts(&orig_arg[1], + next_orig_arg, descrs, state, error, item); + } + + return ret; +} + +static +bool prepend_while_parsing_arg_to_error(char **error, + const unsigned int i, const char * const arg) +{ + char *new_error; + bool success; + + ARGPAR_ASSERT(error); + ARGPAR_ASSERT(*error); + + new_error = argpar_asprintf("While parsing argument #%u (`%s`): %s", + i + 1, arg, *error); + if (!new_error) { + success = false; + goto end; + } + + free(*error); + *error = new_error; + success = true; + +end: + return success; +} + +ARGPAR_HIDDEN +struct argpar_state *argpar_state_create( + unsigned int argc, + const char * const *argv, + const struct argpar_opt_descr * const descrs) +{ + struct argpar_state *state; + + state = argpar_zalloc(struct argpar_state); + if (!state) { + goto end; + } + + state->argc = argc; + state->argv = argv; + state->descrs = descrs; + +end: + return state; +} + +ARGPAR_HIDDEN +void argpar_state_destroy(struct argpar_state *state) +{ + free(state); +} + +ARGPAR_HIDDEN +enum argpar_state_parse_next_status argpar_state_parse_next( + struct argpar_state *state, + struct argpar_item **item, + char **error) +{ + enum argpar_state_parse_next_status status; + + ARGPAR_ASSERT(state->i <= state->argc); + + *error = NULL; + + if (state->i == state->argc) { + status = ARGPAR_STATE_PARSE_NEXT_STATUS_END; + goto end; + } + + enum parse_orig_arg_opt_ret parse_orig_arg_opt_ret; + const char * const orig_arg = state->argv[state->i]; + const char * const next_orig_arg = + state->i < (state->argc - 1) ? state->argv[state->i + 1] : NULL; + + if (orig_arg[0] != '-') { + /* Non-option argument */ + struct argpar_item_non_opt *non_opt_item = + create_non_opt_item(orig_arg, state->i, state->non_opt_index); + + if (!non_opt_item) { + status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR; + goto end; + } + + state->non_opt_index++; + state->i++; + + *item = &non_opt_item->base; + status = ARGPAR_STATE_PARSE_NEXT_STATUS_OK; + goto end; + } + + /* Option argument */ + parse_orig_arg_opt_ret = parse_orig_arg_opt(orig_arg, + next_orig_arg, state->descrs, state, error, item); + switch (parse_orig_arg_opt_ret) { + case PARSE_ORIG_ARG_OPT_RET_OK: + status = ARGPAR_STATE_PARSE_NEXT_STATUS_OK; + break; + case PARSE_ORIG_ARG_OPT_RET_ERROR_UNKNOWN_OPT: + status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT; + break;; + case PARSE_ORIG_ARG_OPT_RET_ERROR: + prepend_while_parsing_arg_to_error( + error, state->i, orig_arg); + status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR; + break; + default: + abort(); + } + +end: + return status; +} + +ARGPAR_HIDDEN +int argpar_state_get_ingested_orig_args(struct argpar_state *state) +{ + return state->i; +} + +ARGPAR_HIDDEN +struct argpar_parse_ret argpar_parse(unsigned int argc, + const char * const *argv, + const struct argpar_opt_descr * const descrs, + bool fail_on_unknown_opt) +{ + struct argpar_parse_ret parse_ret = { 0 }; + struct argpar_item *item = NULL; + struct argpar_state *state = NULL; + + parse_ret.items = new_item_array(); + if (!parse_ret.items) { + parse_ret.error = strdup("Failed to create items array."); + ARGPAR_ASSERT(parse_ret.error); + goto error; + } + + state = argpar_state_create(argc, argv, descrs); + if (!state) { + parse_ret.error = strdup("Failed to create argpar state."); + ARGPAR_ASSERT(parse_ret.error); + goto error; + } + + while (true) { + enum argpar_state_parse_next_status status; + + status = argpar_state_parse_next(state, &item, &parse_ret.error); + if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { + goto error; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_END) { + break; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { + if (fail_on_unknown_opt) { + parse_ret.ingested_orig_args = + argpar_state_get_ingested_orig_args(state); + prepend_while_parsing_arg_to_error( + &parse_ret.error, parse_ret.ingested_orig_args, + argv[parse_ret.ingested_orig_args]); + status = ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR; + goto error; + } + + free(parse_ret.error); + parse_ret.error = NULL; + break; + } + + ARGPAR_ASSERT(status == ARGPAR_STATE_PARSE_NEXT_STATUS_OK); + + if (!push_item(parse_ret.items, item)) { + goto error; + } + item = NULL; + } + + ARGPAR_ASSERT(!parse_ret.error); + parse_ret.ingested_orig_args = + argpar_state_get_ingested_orig_args(state); + goto end; + +error: + ARGPAR_ASSERT(parse_ret.error); + + /* That's how we indicate that an error occured */ + destroy_item_array(parse_ret.items); + parse_ret.items = NULL; + +end: + argpar_state_destroy(state); + argpar_item_destroy(item); + return parse_ret; +} + +ARGPAR_HIDDEN +void argpar_parse_ret_fini(struct argpar_parse_ret *ret) +{ + ARGPAR_ASSERT(ret); + + destroy_item_array(ret->items); + ret->items = NULL; + + free(ret->error); + ret->error = NULL; +} diff --git a/src/common/argpar/argpar.h b/src/common/argpar/argpar.h new file mode 100644 index 000000000..1442f5183 --- /dev/null +++ b/src/common/argpar/argpar.h @@ -0,0 +1,330 @@ +#ifndef BABELTRACE_ARGPAR_H +#define BABELTRACE_ARGPAR_H + +/* + * Copyright 2019 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * argpar is a library that provides facilities for argument parsing. + * + * Two APIs are available: + * + * - The iterator-style API, where you initialize a state object with + * `argpar_state_create`, then repeatedly call `argpar_state_parse_next` to + * get the arguments, until (1) there are no more arguments, (2) the parser + * encounters an error (e.g. unknown option) or (3) you get bored. This + * API gives you more control on when to stop parsing the arguments. + * + * - The parse-everything-in-one-shot-API, where you call `argpar_parse`, + * which parses the arguments until (1) there are not more arguments or + * (2) it encounters a parser error. It returns you a list of all the + * arguments it was able to parse, which you can consult at your leisure. + * + * The following describes how arguments are parsed, and applies to both APIs. + * + * argpar parses the arguments `argv` of which the count is `argc` using the + * sentinel-terminated (use `ARGPAR_OPT_DESCR_SENTINEL`) option + * descriptor array `descrs`. + * + * argpar considers ALL the elements of `argv`, including the* first one, so + * that you would typically pass `argc - 1` and `&argv[1]` from what main() + * receives. + * + * This argument parser supports: + * + * * Short options without an argument, possibly tied together: + * + * -f -auf -n + * + * * Short options with argument: + * + * -b 45 -f/mein/file -xyzhello + * + * * Long options without an argument: + * + * --five-guys --burger-king --pizza-hut --subway + * + * * Long options with arguments: + * + * --security enable --time=18.56 + * + * * Non-option arguments (anything else). + * + * This parser does not accept `-` or `--` as arguments. The latter + * means "end of options" for many command-line tools, but this function + * is all about keeping the order of the arguments, so it does not mean + * much to put them at the end. This has the side effect that a + * non-option argument cannot have the form of an option, for example if + * you need to pass the exact relative path `--component`. In that case, + * you would need to pass `./--component`. There's no generic way to + * escape `-` for the moment. + * + * This parser accepts duplicate options (it will output one item for each + * instance). + * + * The returned items are of the type `struct argpar_item *`. Each item + * is to be casted to the appropriate type (`struct argpar_item_opt *` or + * `struct argpar_item_non_opt *`) depending on its type. + * + * The items are returned in the same order that the arguments were parsed, + * including non-option arguments. This means, for example, that for + * + * --hello --meow=23 /path/to/file -b + * + * found items are returned in this order: option item (--hello), option item + * (--meow=23), non-option item (/path/to/file) and option item (-b). + */ + +#include + +/* Sentinel for an option descriptor array */ +#define ARGPAR_OPT_DESCR_SENTINEL { -1, '\0', NULL, false } + +/* + * ARGPAR_HIDDEN: if argpar is used in some shared library, we don't want them + * to be exported by that library, so mark them as "hidden". + * + * On Windows, symbols are local unless explicitly exported, + * see https://gcc.gnu.org/wiki/Visibility + */ +#if defined(_WIN32) || defined(__CYGWIN__) +#define ARGPAR_HIDDEN +#else +#define ARGPAR_HIDDEN __attribute__((visibility("hidden"))) +#endif + +/* Forward-declaration for the opaque type. */ +struct argpar_state; + +/* Option descriptor */ +struct argpar_opt_descr { + /* Numeric ID for this option */ + const int id; + + /* Short option character, or `\0` */ + const char short_name; + + /* Long option name (without `--`), or `NULL` */ + const char * const long_name; + + /* True if this option has an argument */ + const bool with_arg; +}; + +/* Item type */ +enum argpar_item_type { + /* Option */ + ARGPAR_ITEM_TYPE_OPT, + + /* Non-option */ + ARGPAR_ITEM_TYPE_NON_OPT, +}; + +/* Base item */ +struct argpar_item { + enum argpar_item_type type; +}; + +/* Option item */ +struct argpar_item_opt { + struct argpar_item base; + + /* Corresponding descriptor */ + const struct argpar_opt_descr *descr; + + /* Argument, or `NULL` if none */ + const char *arg; +}; + +/* Non-option item */ +struct argpar_item_non_opt { + struct argpar_item base; + + /* + * Complete argument, pointing to one of the entries of the + * original arguments (`argv`). + */ + const char *arg; + + /* Index of this argument amongst all original arguments (`argv`) */ + unsigned int orig_index; + + /* Index of this argument amongst other non-option arguments */ + unsigned int non_opt_index; +}; + +struct argpar_item_array { + /* Array of `struct argpar_item *`, or `NULL` on error */ + struct argpar_item **items; + + /* Number of used slots in `items`. */ + unsigned int n_items; + + /* Number of allocated slots in `items`. */ + unsigned int n_alloc; +}; + +/* What is returned by argpar_parse() */ +struct argpar_parse_ret { + /* Array of `struct argpar_item *`, or `NULL` on error */ + struct argpar_item_array *items; + + /* Error string, or `NULL` if none */ + char *error; + + /* Number of original arguments (`argv`) ingested */ + unsigned int ingested_orig_args; +}; + +/* + * Parses arguments in `argv` until the end is reached or an error is + * encountered. + * + * On success, this function returns an array of items + * (field `items` of `struct argpar_parse_ret`) corresponding to each parsed + * argument. + * + * In the returned structure, `ingested_orig_args` is the number of + * ingested arguments within `argv` to produce the resulting array of + * items. + * + * If `fail_on_unknown_opt` is true, then on success `ingested_orig_args` is + * equal to `argc`. Otherwise, `ingested_orig_args` contains the number of + * original arguments until an unknown _option_ occurs. For example, with + * + * --great --white contact nuance --shark nuclear + * + * if `--shark` is not described within `descrs` and + * `fail_on_unknown_opt` is false, then `ingested_orig_args` is 4 (two + * options, two non-options), whereas `argc` is 6. + * + * This makes it possible to know where a command name is, for example. + * With those arguments: + * + * --verbose --stuff=23 do-something --specific-opt -f -b + * + * and the descriptors for `--verbose` and `--stuff` only, the function + * returns the `--verbose` and `--stuff` option items, the + * `do-something` non-option item, and that three original arguments + * were ingested. This means you can start the next argument parsing + * stage, with option descriptors depending on the command name, at + * `&argv[3]`. + * + * Note that `ingested_orig_args` is not always equal to the number of + * returned items, as + * + * --hello -fdw + * + * for example contains two ingested original arguments, but four + * resulting items. + * + * On failure, the returned structure's `items` member is `NULL`, and + * the `error` string member contains details about the error. + * + * You can finalize the returned structure with + * argpar_parse_ret_fini(). + */ +ARGPAR_HIDDEN +struct argpar_parse_ret argpar_parse(unsigned int argc, + const char * const *argv, + const struct argpar_opt_descr *descrs, + bool fail_on_unknown_opt); + +/* + * Finalizes what is returned by argpar_parse(). + * + * It is safe to call argpar_parse() multiple times with the same + * structure. + */ +ARGPAR_HIDDEN +void argpar_parse_ret_fini(struct argpar_parse_ret *ret); + +/* + * Creates an instance of `struct argpar_state`. + * + * This sets up the argpar_state structure, but does not actually + * start parsing the arguments. + * + * When you are done with it, the state must be freed with + * `argpar_state_destroy`. + */ +ARGPAR_HIDDEN +struct argpar_state *argpar_state_create( + unsigned int argc, + const char * const *argv, + const struct argpar_opt_descr * const descrs); + +/* + * Destroys an instance of `struct argpar_state`. + */ +ARGPAR_HIDDEN +void argpar_state_destroy(struct argpar_state *state); + + +enum argpar_state_parse_next_status { + ARGPAR_STATE_PARSE_NEXT_STATUS_OK, + ARGPAR_STATE_PARSE_NEXT_STATUS_END, + ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT, + ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR, +}; + +/* + * Parses and returns the next argument from `state`. + * + * On success, an item describing the argument is returned in `*item` and + * ARGPAR_STATE_PARSE_NEXT_STATUS_OK is returned. The item must be freed with + * `argpar_item_destroy`. + * + * If there are no more arguments to parse, ARGPAR_STATE_PARSE_NEXT_STATUS_END + * is returned. + * + * On failure (status codes ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT and + * ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR), an error string is returned in `*error`. + * This string must be freed with `free`. + */ +enum argpar_state_parse_next_status argpar_state_parse_next( + struct argpar_state *state, + struct argpar_item **item, + char **error); + +/* + * Return the number of ingested elements from argv that were required to + * produce the previously returned items. + */ +ARGPAR_HIDDEN +int argpar_state_get_ingested_orig_args(struct argpar_state *state); + +/* + * Destroy an instance of `struct argpar_item`, as returned by + * argpar_state_parse_next. + */ +ARGPAR_HIDDEN +void argpar_item_destroy(struct argpar_item *item); + +#define ARGPAR_ITEM_DESTROY_AND_RESET(_item) \ + { \ + argpar_item_destroy(_item); \ + _item = NULL; \ + } + + +#endif /* BABELTRACE_ARGPAR_H */ diff --git a/src/common/bytecode/Makefile.am b/src/common/bytecode/Makefile.am new file mode 100644 index 000000000..9e1c6ddd1 --- /dev/null +++ b/src/common/bytecode/Makefile.am @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only + +noinst_LTLIBRARIES = libbytecode.la + +libbytecode_la_SOURCES = \ + bytecode.c bytecode.h diff --git a/src/common/bytecode/bytecode.c b/src/common/bytecode/bytecode.c new file mode 100644 index 000000000..d78cf5ade --- /dev/null +++ b/src/common/bytecode/bytecode.c @@ -0,0 +1,264 @@ +/* + * Copyright 2020 EfficiOS, Inc. + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "bytecode.h" + +#include + +#include "common/align.h" + +#define INIT_ALLOC_SIZE 4 + +static inline int get_count_order(unsigned int count) +{ + int order; + + order = lttng_fls(count) - 1; + if (count & (count - 1)) + order++; + return order; +} + +LTTNG_HIDDEN +int bytecode_init(struct lttng_bytecode_alloc **fb) +{ + uint32_t alloc_len; + + alloc_len = sizeof(struct lttng_bytecode_alloc) + INIT_ALLOC_SIZE; + *fb = calloc(alloc_len, 1); + if (!*fb) { + return -ENOMEM; + } else { + (*fb)->alloc_len = alloc_len; + return 0; + } +} + +LTTNG_HIDDEN +int32_t bytecode_reserve(struct lttng_bytecode_alloc **fb, uint32_t align, uint32_t len) +{ + int32_t ret; + uint32_t padding = offset_align((*fb)->b.len, align); + uint32_t new_len = (*fb)->b.len + padding + len; + uint32_t new_alloc_len = sizeof(struct lttng_bytecode_alloc) + new_len; + uint32_t old_alloc_len = (*fb)->alloc_len; + + if (new_len > LTTNG_FILTER_MAX_LEN) + return -EINVAL; + + if (new_alloc_len > old_alloc_len) { + struct lttng_bytecode_alloc *newptr; + + new_alloc_len = + max_t(uint32_t, 1U << get_count_order(new_alloc_len), old_alloc_len << 1); + newptr = realloc(*fb, new_alloc_len); + if (!newptr) + return -ENOMEM; + *fb = newptr; + /* We zero directly the memory from start of allocation. */ + memset(&((char *) *fb)[old_alloc_len], 0, new_alloc_len - old_alloc_len); + (*fb)->alloc_len = new_alloc_len; + } + (*fb)->b.len += padding; + ret = (*fb)->b.len; + (*fb)->b.len += len; + return ret; +} + +LTTNG_HIDDEN +int bytecode_push(struct lttng_bytecode_alloc **fb, const void *data, + uint32_t align, uint32_t len) +{ + int32_t offset; + + offset = bytecode_reserve(fb, align, len); + if (offset < 0) + return offset; + memcpy(&(*fb)->b.data[offset], data, len); + return 0; +} + +LTTNG_HIDDEN +int bytecode_push_logical(struct lttng_bytecode_alloc **fb, + struct logical_op *data, + uint32_t align, uint32_t len, + uint16_t *skip_offset) +{ + int32_t offset; + + offset = bytecode_reserve(fb, align, len); + if (offset < 0) + return offset; + memcpy(&(*fb)->b.data[offset], data, len); + *skip_offset = + (void *) &((struct logical_op *) &(*fb)->b.data[offset])->skip_offset + - (void *) &(*fb)->b.data[0]; + return 0; +} + +LTTNG_HIDDEN +int bytecode_push_get_payload_root(struct lttng_bytecode_alloc **bytecode) +{ + struct load_op *insn; + uint32_t insn_len = sizeof(struct load_op); + int ret; + + insn = calloc(insn_len, 1); + if (!insn) + return -ENOMEM; + + insn->op = BYTECODE_OP_GET_PAYLOAD_ROOT; + ret = bytecode_push(bytecode, insn, 1, insn_len); + free(insn); + + return ret; +} + +LTTNG_HIDDEN +int bytecode_push_get_context_root(struct lttng_bytecode_alloc **bytecode) +{ + struct load_op *insn; + uint32_t insn_len = sizeof(struct load_op); + int ret; + + insn = calloc(insn_len, 1); + if (!insn) + return -ENOMEM; + + insn->op = BYTECODE_OP_GET_CONTEXT_ROOT; + ret = bytecode_push(bytecode, insn, 1, insn_len); + free(insn); + + return ret; +} + +LTTNG_HIDDEN +int bytecode_push_get_app_context_root(struct lttng_bytecode_alloc **bytecode) +{ + struct load_op *insn; + uint32_t insn_len = sizeof(struct load_op); + int ret; + + insn = calloc(insn_len, 1); + if (!insn) + return -ENOMEM; + + insn->op = BYTECODE_OP_GET_APP_CONTEXT_ROOT; + ret = bytecode_push(bytecode, insn, 1, insn_len); + free(insn); + + return ret; +} + +LTTNG_HIDDEN +int bytecode_push_get_index_u64(struct lttng_bytecode_alloc **bytecode, + uint64_t index) +{ + struct load_op *insn; + uint32_t insn_len = sizeof(struct load_op) + + sizeof(struct get_index_u64); + struct get_index_u64 index_op_data; + int ret; + + insn = calloc(insn_len, 1); + if (!insn) + return -ENOMEM; + + insn->op = BYTECODE_OP_GET_INDEX_U64; + index_op_data.index = index; + memcpy(insn->data, &index_op_data, sizeof(index)); + ret = bytecode_push(bytecode, insn, 1, insn_len); + + free(insn); + + return ret; +} + +LTTNG_HIDDEN +int bytecode_push_get_symbol(struct lttng_bytecode_alloc **bytecode, + struct lttng_bytecode_alloc **bytecode_reloc, + const char *symbol) +{ + struct load_op *insn; + uint32_t insn_len = sizeof(struct load_op) + + sizeof(struct get_symbol); + struct get_symbol symbol_offset; + uint32_t reloc_offset_u32; + uint16_t reloc_offset; + uint32_t bytecode_reloc_offset_u32; + int ret; + + insn = calloc(insn_len, 1); + if (!insn) { + ret = -ENOMEM; + goto end; + } + + insn->op = BYTECODE_OP_GET_SYMBOL; + + /* + * Get offset in the reloc portion at which the symbol name + * will end up at (GET_SYMBOL's operand points there). + */ + bytecode_reloc_offset_u32 = + bytecode_get_len(&(*bytecode_reloc)->b) + + sizeof(reloc_offset); + symbol_offset.offset = (uint16_t) bytecode_reloc_offset_u32; + memcpy(insn->data, &symbol_offset, sizeof(symbol_offset)); + + /* + * Get offset in the bytecode where the opcode will end up at, + * the reloc offset points to it. + */ + reloc_offset_u32 = bytecode_get_len(&(*bytecode)->b); + if (reloc_offset_u32 > LTTNG_FILTER_MAX_LEN - 1) { + ret = -EINVAL; + goto end; + } + reloc_offset = (uint16_t) reloc_offset_u32; + + /* Append op in bytecode. */ + ret = bytecode_push(bytecode, insn, 1, insn_len); + if (ret) { + goto end; + } + + /* Append reloc offset. */ + ret = bytecode_push(bytecode_reloc, &reloc_offset, + 1, sizeof(reloc_offset)); + if (ret) { + goto end; + } + + /* Append symbol name. */ + ret = bytecode_push(bytecode_reloc, symbol, 1, strlen(symbol) + 1); + +end: + free(insn); + return ret; +} + +/* + * Allocate an lttng_bytecode object and copy the given original bytecode. + * + * Return allocated bytecode or NULL on error. + */ +struct lttng_bytecode *bytecode_copy( + const struct lttng_bytecode *orig_f) +{ + struct lttng_bytecode *bytecode = NULL; + + bytecode = zmalloc(sizeof(*bytecode) + orig_f->len); + if (!bytecode) { + goto error; + } + + memcpy(bytecode, orig_f, sizeof(*bytecode) + orig_f->len); + +error: + return bytecode; +} diff --git a/src/common/bytecode/bytecode.h b/src/common/bytecode/bytecode.h new file mode 100644 index 000000000..b519fe19a --- /dev/null +++ b/src/common/bytecode/bytecode.h @@ -0,0 +1,266 @@ +/* + * Copyright 2020 EfficiOS, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_COMMON_BYTECODE_H +#define LTTNG_COMMON_BYTECODE_H + +#include + +#include "common/macros.h" +#include "common/sessiond-comm/sessiond-comm.h" + +/* + * offsets are absolute from start of bytecode. + */ + +struct field_ref { + /* Initially, symbol offset. After link, field offset. */ + uint16_t offset; +} LTTNG_PACKED; + +struct get_symbol { + /* Symbol offset. */ + uint16_t offset; +} LTTNG_PACKED; + +struct get_index_u16 { + uint16_t index; +} LTTNG_PACKED; + +struct get_index_u64 { + uint64_t index; +} LTTNG_PACKED; + +struct literal_numeric { + int64_t v; +} LTTNG_PACKED; + +struct literal_double { + double v; +} LTTNG_PACKED; + +struct literal_string { + char string[0]; +} LTTNG_PACKED; + +enum bytecode_op { + BYTECODE_OP_UNKNOWN = 0, + + BYTECODE_OP_RETURN = 1, + + /* binary */ + BYTECODE_OP_MUL = 2, + BYTECODE_OP_DIV = 3, + BYTECODE_OP_MOD = 4, + BYTECODE_OP_PLUS = 5, + BYTECODE_OP_MINUS = 6, + BYTECODE_OP_BIT_RSHIFT = 7, + BYTECODE_OP_BIT_LSHIFT = 8, + BYTECODE_OP_BIT_AND = 9, + BYTECODE_OP_BIT_OR = 10, + BYTECODE_OP_BIT_XOR = 11, + + /* binary comparators */ + BYTECODE_OP_EQ = 12, + BYTECODE_OP_NE = 13, + BYTECODE_OP_GT = 14, + BYTECODE_OP_LT = 15, + BYTECODE_OP_GE = 16, + BYTECODE_OP_LE = 17, + + /* string binary comparator: apply to */ + BYTECODE_OP_EQ_STRING = 18, + BYTECODE_OP_NE_STRING = 19, + BYTECODE_OP_GT_STRING = 20, + BYTECODE_OP_LT_STRING = 21, + BYTECODE_OP_GE_STRING = 22, + BYTECODE_OP_LE_STRING = 23, + + /* s64 binary comparator */ + BYTECODE_OP_EQ_S64 = 24, + BYTECODE_OP_NE_S64 = 25, + BYTECODE_OP_GT_S64 = 26, + BYTECODE_OP_LT_S64 = 27, + BYTECODE_OP_GE_S64 = 28, + BYTECODE_OP_LE_S64 = 29, + + /* double binary comparator */ + BYTECODE_OP_EQ_DOUBLE = 30, + BYTECODE_OP_NE_DOUBLE = 31, + BYTECODE_OP_GT_DOUBLE = 32, + BYTECODE_OP_LT_DOUBLE = 33, + BYTECODE_OP_GE_DOUBLE = 34, + BYTECODE_OP_LE_DOUBLE = 35, + + /* Mixed S64-double binary comparators */ + BYTECODE_OP_EQ_DOUBLE_S64 = 36, + BYTECODE_OP_NE_DOUBLE_S64 = 37, + BYTECODE_OP_GT_DOUBLE_S64 = 38, + BYTECODE_OP_LT_DOUBLE_S64 = 39, + BYTECODE_OP_GE_DOUBLE_S64 = 40, + BYTECODE_OP_LE_DOUBLE_S64 = 41, + + BYTECODE_OP_EQ_S64_DOUBLE = 42, + BYTECODE_OP_NE_S64_DOUBLE = 43, + BYTECODE_OP_GT_S64_DOUBLE = 44, + BYTECODE_OP_LT_S64_DOUBLE = 45, + BYTECODE_OP_GE_S64_DOUBLE = 46, + BYTECODE_OP_LE_S64_DOUBLE = 47, + + /* unary */ + BYTECODE_OP_UNARY_PLUS = 48, + BYTECODE_OP_UNARY_MINUS = 49, + BYTECODE_OP_UNARY_NOT = 50, + BYTECODE_OP_UNARY_PLUS_S64 = 51, + BYTECODE_OP_UNARY_MINUS_S64 = 52, + BYTECODE_OP_UNARY_NOT_S64 = 53, + BYTECODE_OP_UNARY_PLUS_DOUBLE = 54, + BYTECODE_OP_UNARY_MINUS_DOUBLE = 55, + BYTECODE_OP_UNARY_NOT_DOUBLE = 56, + + /* logical */ + BYTECODE_OP_AND = 57, + BYTECODE_OP_OR = 58, + + /* load field ref */ + BYTECODE_OP_LOAD_FIELD_REF = 59, + BYTECODE_OP_LOAD_FIELD_REF_STRING = 60, + BYTECODE_OP_LOAD_FIELD_REF_SEQUENCE = 61, + BYTECODE_OP_LOAD_FIELD_REF_S64 = 62, + BYTECODE_OP_LOAD_FIELD_REF_DOUBLE = 63, + + /* load immediate from operand */ + BYTECODE_OP_LOAD_STRING = 64, + BYTECODE_OP_LOAD_S64 = 65, + BYTECODE_OP_LOAD_DOUBLE = 66, + + /* cast */ + BYTECODE_OP_CAST_TO_S64 = 67, + BYTECODE_OP_CAST_DOUBLE_TO_S64 = 68, + BYTECODE_OP_CAST_NOP = 69, + + /* get context ref */ + BYTECODE_OP_GET_CONTEXT_REF = 70, + BYTECODE_OP_GET_CONTEXT_REF_STRING = 71, + BYTECODE_OP_GET_CONTEXT_REF_S64 = 72, + BYTECODE_OP_GET_CONTEXT_REF_DOUBLE = 73, + + /* load userspace field ref */ + BYTECODE_OP_LOAD_FIELD_REF_USER_STRING = 74, + BYTECODE_OP_LOAD_FIELD_REF_USER_SEQUENCE = 75, + + /* + * load immediate star globbing pattern (literal string) + * from immediate + */ + BYTECODE_OP_LOAD_STAR_GLOB_STRING = 76, + + /* globbing pattern binary operator: apply to */ + BYTECODE_OP_EQ_STAR_GLOB_STRING = 77, + BYTECODE_OP_NE_STAR_GLOB_STRING = 78, + + /* + * Instructions for recursive traversal through composed types. + */ + BYTECODE_OP_GET_CONTEXT_ROOT = 79, + BYTECODE_OP_GET_APP_CONTEXT_ROOT = 80, + BYTECODE_OP_GET_PAYLOAD_ROOT = 81, + + BYTECODE_OP_GET_SYMBOL = 82, + BYTECODE_OP_GET_SYMBOL_FIELD = 83, + BYTECODE_OP_GET_INDEX_U16 = 84, + BYTECODE_OP_GET_INDEX_U64 = 85, + + BYTECODE_OP_LOAD_FIELD = 86, + BYTECODE_OP_LOAD_FIELD_S8 = 87, + BYTECODE_OP_LOAD_FIELD_S16 = 88, + BYTECODE_OP_LOAD_FIELD_S32 = 89, + BYTECODE_OP_LOAD_FIELD_S64 = 90, + BYTECODE_OP_LOAD_FIELD_U8 = 91, + BYTECODE_OP_LOAD_FIELD_U16 = 92, + BYTECODE_OP_LOAD_FIELD_U32 = 93, + BYTECODE_OP_LOAD_FIELD_U64 = 94, + BYTECODE_OP_LOAD_FIELD_STRING = 95, + BYTECODE_OP_LOAD_FIELD_SEQUENCE = 96, + BYTECODE_OP_LOAD_FIELD_DOUBLE = 97, + + BYTECODE_OP_UNARY_BIT_NOT = 98, + + BYTECODE_OP_RETURN_S64 = 99, + + NR_BYTECODE_OPS, +}; + +typedef uint8_t bytecode_opcode_t; + +struct load_op { + bytecode_opcode_t op; + + /* + * data to load. Size known by enum bytecode_opcode_t and null-term + * char. + */ + char data[0]; +} LTTNG_PACKED; + +struct binary_op { + bytecode_opcode_t op; +} LTTNG_PACKED; + +struct unary_op { + bytecode_opcode_t op; +} LTTNG_PACKED; + +/* skip_offset is absolute from start of bytecode */ +struct logical_op { + bytecode_opcode_t op; + uint16_t skip_offset; /* bytecode insn, if skip second test */ +} LTTNG_PACKED; + +struct cast_op { + bytecode_opcode_t op; +} LTTNG_PACKED; + +struct return_op { + bytecode_opcode_t op; +} LTTNG_PACKED; + +struct lttng_bytecode_alloc { + uint32_t alloc_len; + struct lttng_bytecode b; +}; + +LTTNG_HIDDEN int bytecode_init(struct lttng_bytecode_alloc **fb); +LTTNG_HIDDEN int32_t bytecode_reserve(struct lttng_bytecode_alloc **fb, + uint32_t align, uint32_t len); +LTTNG_HIDDEN int bytecode_push(struct lttng_bytecode_alloc **fb, + const void *data, uint32_t align, uint32_t len); +LTTNG_HIDDEN int bytecode_push_logical(struct lttng_bytecode_alloc **fb, + struct logical_op *data, uint32_t align, uint32_t len, + uint16_t *skip_offset); +LTTNG_HIDDEN int bytecode_push_get_payload_root( + struct lttng_bytecode_alloc **bytecode); +LTTNG_HIDDEN int bytecode_push_get_context_root( + struct lttng_bytecode_alloc **bytecode); +LTTNG_HIDDEN int bytecode_push_get_app_context_root( + struct lttng_bytecode_alloc **bytecode); +LTTNG_HIDDEN int bytecode_push_get_index_u64( + struct lttng_bytecode_alloc **bytecode, uint64_t index); +LTTNG_HIDDEN int bytecode_push_get_symbol( + struct lttng_bytecode_alloc **bytecode, + struct lttng_bytecode_alloc **bytecode_reloc, + const char *symbol); +LTTNG_HIDDEN struct lttng_bytecode *bytecode_copy( + const struct lttng_bytecode *orig_f); + +static inline +unsigned int bytecode_get_len(struct lttng_bytecode *bytecode) +{ + return bytecode->len; +} + +#endif /* LTTNG_COMMON_BYTECODE_H */ diff --git a/src/common/buffer-usage.c b/src/common/conditions/buffer-usage.c similarity index 99% rename from src/common/buffer-usage.c rename to src/common/conditions/buffer-usage.c index 92081c910..465a9263d 100644 --- a/src/common/buffer-usage.c +++ b/src/common/conditions/buffer-usage.c @@ -91,7 +91,8 @@ end: static int lttng_condition_buffer_usage_serialize( const struct lttng_condition *condition, - struct lttng_dynamic_buffer *buf) + struct lttng_dynamic_buffer *buf, + int *fd_to_send) { int ret; struct lttng_condition_buffer_usage *usage; @@ -149,6 +150,11 @@ int lttng_condition_buffer_usage_serialize( if (ret) { goto end; } + + if (fd_to_send) { + /* No fd to send */ + *fd_to_send = -1; + } end: return ret; } diff --git a/src/common/condition.c b/src/common/conditions/condition.c similarity index 79% rename from src/common/condition.c rename to src/common/conditions/condition.c index 076f0627c..b5e9aebbb 100644 --- a/src/common/condition.c +++ b/src/common/conditions/condition.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -55,7 +56,8 @@ end: LTTNG_HIDDEN int lttng_condition_serialize(const struct lttng_condition *condition, - struct lttng_dynamic_buffer *buf) + struct lttng_dynamic_buffer *buf, + int *fd_to_send) { int ret; struct lttng_condition_comm condition_comm = { 0 }; @@ -73,7 +75,7 @@ int lttng_condition_serialize(const struct lttng_condition *condition, goto end; } - ret = condition->serialize(condition, buf); + ret = condition->serialize(condition, buf, fd_to_send); if (ret) { goto end; } @@ -139,6 +141,9 @@ ssize_t lttng_condition_create_from_buffer( case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: create_from_buffer = lttng_condition_session_rotation_completed_create_from_buffer; break; + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + create_from_buffer = lttng_condition_event_rule_create_from_buffer; + break; default: ERR("Attempted to create condition of unknown type (%i)", (int) condition_comm->condition_type); @@ -172,3 +177,33 @@ void lttng_condition_init(struct lttng_condition *condition, { condition->type = type; } + +LTTNG_HIDDEN +const char *lttng_condition_type_str(enum lttng_condition_type type) +{ + switch (type) { + case LTTNG_CONDITION_TYPE_UNKNOWN: + return "unknown"; + + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + return "session consumed size"; + + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + return "buffer usage high"; + + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + return "buffer usage low"; + + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: + return "session rotation ongoing"; + + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: + return "session rotation completed"; + + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + return "event rule hit"; + + default: + return "???"; + } +} diff --git a/src/common/conditions/event-rule.c b/src/common/conditions/event-rule.c new file mode 100644 index 000000000..0bb2aa5a5 --- /dev/null +++ b/src/common/conditions/event-rule.c @@ -0,0 +1,1583 @@ +/* + * Copyright (C) 2017 - Jérémie Galarneau + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IS_EVENT_RULE_CONDITION(condition) ( \ + lttng_condition_get_type(condition) == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT \ + ) + +static +bool is_event_rule_evaluation(const struct lttng_evaluation *evaluation) +{ + enum lttng_condition_type type = lttng_evaluation_get_type(evaluation); + + return type == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT; +} + + +static +bool lttng_condition_event_rule_validate( + const struct lttng_condition *condition); +static +int lttng_condition_event_rule_serialize( + const struct lttng_condition *condition, + struct lttng_dynamic_buffer *buf, + int *fd_to_send); +static +bool lttng_condition_event_rule_is_equal(const struct lttng_condition *_a, + const struct lttng_condition *_b); +static +void lttng_condition_event_rule_destroy( + struct lttng_condition *condition); + + +static +bool lttng_condition_event_rule_validate( + const struct lttng_condition *condition) +{ + bool valid = false; + struct lttng_condition_event_rule *event_rule; + + if (!condition) { + goto end; + } + + event_rule = container_of(condition, + struct lttng_condition_event_rule, parent); + if (!event_rule->rule) { + ERR("Invalid session event_rule condition: a rule must be set."); + goto end; + } + + valid = lttng_event_rule_validate(event_rule->rule); +end: + return valid; +} + +/* + * Serializes the C string `str` into `buf`. + * + * Encoding is the length of `str` plus one (for the null character), + * and then the string, including its null character. + */ +static +int serialize_cstr(const char *str, struct lttng_dynamic_buffer *buf) +{ + int ret; + uint32_t len = strlen(str) + 1; + + /* Serialize the length, including the null character */ + DBG("Serializing C string's length (including null character): " + "%" PRIu32, len); + ret = lttng_dynamic_buffer_append(buf, &len, sizeof(len)); + if (ret) { + goto end; + } + + /* Serialize the string */ + DBG("Serializing C string: \"%s\"", str); + ret = lttng_dynamic_buffer_append(buf, str, len); + if (ret) { + goto end; + } + +end: + return ret; +} + +/* + * Serializes the event expression `expr` into `buf`. + */ +static +int serialize_event_expr(const struct lttng_event_expr *expr, + struct lttng_dynamic_buffer *buf) +{ + uint8_t type; + int ret; + + /* Serialize the expression's type */ + DBG("Serializing event expression's type: %d", expr->type); + type = expr->type; + ret = lttng_dynamic_buffer_append(buf, &type, sizeof(type)); + if (ret) { + goto end; + } + + /* Serialize the expression */ + switch (expr->type) { + case LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD: + case LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD: + { + const struct lttng_event_expr_field *field_expr = + container_of(expr, + const struct lttng_event_expr_field, + parent); + + /* Serialize the field name */ + DBG("Serializing field event expression's field name: \"%s\"", + field_expr->name); + ret = serialize_cstr(field_expr->name, buf); + if (ret) { + goto end; + } + + break; + } + case LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD: + { + const struct lttng_event_expr_app_specific_context_field *field_expr = + container_of(expr, + const struct lttng_event_expr_app_specific_context_field, + parent); + + /* Serialize the provider name */ + DBG("Serializing app-specific context field event expression's " + "provider name: \"%s\"", + field_expr->provider_name); + ret = serialize_cstr(field_expr->provider_name, buf); + if (ret) { + goto end; + } + + /* Serialize the type name */ + DBG("Serializing app-specific context field event expression's " + "type name: \"%s\"", + field_expr->provider_name); + ret = serialize_cstr(field_expr->type_name, buf); + if (ret) { + goto end; + } + + break; + } + case LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT: + { + const struct lttng_event_expr_array_field_element *elem_expr = + container_of(expr, + const struct lttng_event_expr_array_field_element, + parent); + uint32_t index = elem_expr->index; + + /* Serialize the index */ + DBG("Serializing array field element event expression's " + "index: %u", elem_expr->index); + ret = lttng_dynamic_buffer_append(buf, &index, sizeof(index)); + if (ret) { + goto end; + } + + /* Serialize the parent array field expression */ + DBG("Serializing array field element event expression's " + "parent array field event expression."); + ret = serialize_event_expr(elem_expr->array_field_expr, buf); + if (ret) { + goto end; + } + + break; + } + default: + break; + } + +end: + return ret; +} + +static +int lttng_condition_event_rule_serialize( + const struct lttng_condition *condition, + struct lttng_dynamic_buffer *buf, + int *fd_to_send) +{ + int ret; + struct lttng_condition_event_rule *event_rule; + enum lttng_condition_status status; + uint32_t capture_descr_count; + uint32_t i; + + if (!condition || !IS_EVENT_RULE_CONDITION(condition)) { + ret = -1; + goto end; + } + + DBG("Serializing event rule condition"); + event_rule = container_of(condition, struct lttng_condition_event_rule, + parent); + + DBG("Serializing event rule condition's event rule"); + ret = lttng_event_rule_serialize(event_rule->rule, buf, fd_to_send); + if (ret) { + goto end; + } + + status = lttng_condition_event_rule_get_capture_descriptor_count( + condition, &capture_descr_count); + if (status != LTTNG_CONDITION_STATUS_OK) { + ret = -1; + goto end; + }; + + DBG("Serializing event rule condition's capture descriptor count: %" PRIu32, + capture_descr_count); + ret = lttng_dynamic_buffer_append(buf, &capture_descr_count, + sizeof(capture_descr_count)); + if (ret) { + goto end; + } + + for (i = 0; i < capture_descr_count; i++) { + const struct lttng_capture_descriptor *desc = + lttng_condition_event_rule_get_internal_capture_descriptor_at_index( + condition, i); + + DBG("Serializing event rule condition's capture descriptor %" PRIu32, + i); + ret = serialize_event_expr(desc->event_expression, buf); + if (ret) { + goto end; + } + + /* + * Appending the internal index payload linked with the + * descriptor. + * TODO: might want to move to an englobing object to describe a + * capture descriptor publicly. + */ + ret = lttng_dynamic_buffer_append(buf, &desc->capture_index, + sizeof(desc->capture_index)); + if (ret) { + goto end; + } + } + +end: + return ret; +} + +static +bool capture_descriptors_are_equal( + const struct lttng_condition *condition_a, + const struct lttng_condition *condition_b) +{ + bool is_equal = true; + unsigned int capture_descr_count_a; + unsigned int capture_descr_count_b; + size_t i; + enum lttng_condition_status status; + + status = lttng_condition_event_rule_get_capture_descriptor_count( + condition_a, &capture_descr_count_a); + if (status != LTTNG_CONDITION_STATUS_OK) { + goto not_equal; + } + + status = lttng_condition_event_rule_get_capture_descriptor_count( + condition_b, &capture_descr_count_b); + if (status != LTTNG_CONDITION_STATUS_OK) { + goto not_equal; + } + + if (capture_descr_count_a != capture_descr_count_b) { + goto not_equal; + } + + for (i = 0; i < capture_descr_count_a; i++) { + const struct lttng_event_expr *expr_a = + lttng_condition_event_rule_get_capture_descriptor_at_index( + condition_a, + i); + const struct lttng_event_expr *expr_b = + lttng_condition_event_rule_get_capture_descriptor_at_index( + condition_b, + i); + + if (!lttng_event_expr_is_equal(expr_a, expr_b)) { + goto not_equal; + } + } + + goto end; + +not_equal: + is_equal = false; + +end: + return is_equal; +} + +static +bool lttng_condition_event_rule_is_equal(const struct lttng_condition *_a, + const struct lttng_condition *_b) +{ + bool is_equal = false; + struct lttng_condition_event_rule *a, *b; + + a = container_of(_a, struct lttng_condition_event_rule, parent); + b = container_of(_b, struct lttng_condition_event_rule, parent); + + /* Both session names must be set or both must be unset. */ + if ((a->rule && !b->rule) || + (!a->rule && b->rule)) { + WARN("Comparing session event_rule conditions with uninitialized rule."); + goto end; + } + + is_equal = lttng_event_rule_is_equal(a->rule, b->rule); + if (!is_equal) { + goto end; + } + + is_equal = capture_descriptors_are_equal(_a, _b); + +end: + return is_equal; +} + +static +void lttng_condition_event_rule_destroy( + struct lttng_condition *condition) +{ + struct lttng_condition_event_rule *event_rule; + + event_rule = container_of(condition, + struct lttng_condition_event_rule, parent); + + + lttng_event_rule_destroy(event_rule->rule); + lttng_dynamic_pointer_array_reset(&event_rule->capture_descriptors); + free(event_rule); +} + +static +void destroy_capture_descriptor(void *ptr) +{ + struct lttng_capture_descriptor *desc = + (struct lttng_capture_descriptor *) ptr; + lttng_event_expr_destroy(desc->event_expression); + free(desc); +} + +struct lttng_condition *lttng_condition_event_rule_create( + struct lttng_event_rule *rule) +{ + struct lttng_condition *parent = NULL; + struct lttng_condition_event_rule *condition = NULL; + + if (!rule) { + goto end; + } + + condition = zmalloc(sizeof(struct lttng_condition_event_rule)); + if (!condition) { + return NULL; + } + + lttng_condition_init(&condition->parent, LTTNG_CONDITION_TYPE_EVENT_RULE_HIT); + condition->parent.validate = lttng_condition_event_rule_validate, + condition->parent.serialize = lttng_condition_event_rule_serialize, + condition->parent.equal = lttng_condition_event_rule_is_equal, + condition->parent.destroy = lttng_condition_event_rule_destroy, + + condition->rule = rule; + lttng_dynamic_pointer_array_init(&condition->capture_descriptors, + destroy_capture_descriptor); + + parent = &condition->parent; +end: + return parent; +} + +static +int64_t int_from_buffer(const struct lttng_buffer_view *view, size_t size, + size_t *offset) +{ + int64_t ret; + + if (*offset + size > view->size) { + ret = -1; + goto end; + } + + switch (size) { + case 1: + ret = (int64_t) view->data[*offset]; + break; + case sizeof(int32_t): + { + int32_t s32; + + memcpy(&s32, &view->data[*offset], sizeof(s32)); + ret = (int64_t) s32; + break; + } + case sizeof(ret): + memcpy(&ret, &view->data[*offset], sizeof(ret)); + break; + default: + abort(); + } + + *offset += size; + +end: + return ret; +} +static +uint64_t uint_from_buffer(const struct lttng_buffer_view *view, size_t size, + size_t *offset) +{ + uint64_t ret; + + if (*offset + size > view->size) { + ret = UINT64_C(-1); + goto end; + } + + switch (size) { + case 1: + ret = (uint64_t) view->data[*offset]; + break; + case sizeof(uint32_t): + { + uint32_t u32; + + memcpy(&u32, &view->data[*offset], sizeof(u32)); + ret = (uint64_t) u32; + break; + } + case sizeof(ret): + memcpy(&ret, &view->data[*offset], sizeof(ret)); + break; + default: + abort(); + } + + *offset += size; + +end: + return ret; +} + +static +const char *str_from_buffer(const struct lttng_buffer_view *view, + size_t *offset) +{ + uint64_t len; + const char *ret; + + len = uint_from_buffer(view, sizeof(uint32_t), offset); + if (len == UINT64_C(-1)) { + goto error; + } + + ret = &view->data[*offset]; + + if (!lttng_buffer_view_contains_string(view, ret, len)) { + goto error; + } + + *offset += len; + goto end; + +error: + ret = NULL; + +end: + return ret; +} + +static +struct lttng_event_expr *event_expr_from_buffer( + const struct lttng_buffer_view *view, size_t *offset) +{ + struct lttng_event_expr *expr = NULL; + const char *str; + uint64_t type; + + type = uint_from_buffer(view, sizeof(uint8_t), offset); + if (type == UINT64_C(-1)) { + goto error; + } + + switch (type) { + case LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD: + str = str_from_buffer(view, offset); + if (!str) { + goto error; + } + + expr = lttng_event_expr_event_payload_field_create(str); + break; + case LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD: + str = str_from_buffer(view, offset); + if (!str) { + goto error; + } + + expr = lttng_event_expr_channel_context_field_create(str); + break; + case LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD: + { + const char *provider_name; + const char *type_name; + + provider_name = str_from_buffer(view, offset); + if (!provider_name) { + goto error; + } + + type_name = str_from_buffer(view, offset); + if (!type_name) { + goto error; + } + + expr = lttng_event_expr_app_specific_context_field_create( + provider_name, type_name); + break; + } + case LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT: + { + struct lttng_event_expr *array_field_expr; + uint64_t index; + + index = uint_from_buffer(view, sizeof(uint32_t), offset); + if (index == UINT64_C(-1)) { + goto error; + } + + /* Array field expression is the encoded after this */ + array_field_expr = event_expr_from_buffer(view, offset); + if (!array_field_expr) { + goto error; + } + + /* Move ownership of `array_field_expr` to new expression */ + expr = lttng_event_expr_array_field_element_create( + array_field_expr, (unsigned int) index); + if (!expr) { + /* `array_field_expr` not moved: destroy it */ + lttng_event_expr_destroy(array_field_expr); + } + + break; + } + default: + abort(); + } + + goto end; + +error: + lttng_event_expr_destroy(expr); + expr = NULL; + +end: + return expr; +} + +LTTNG_HIDDEN +ssize_t lttng_condition_event_rule_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_condition **_condition) +{ + ssize_t consumed_length; + size_t offset = 0; + ssize_t size; + uint64_t capture_descr_count; + uint64_t i; + struct lttng_condition *condition = NULL; + struct lttng_event_rule *event_rule = NULL; + struct lttng_buffer_view event_rule_view; + + if (!view || !_condition) { + goto error; + } + + /* Event rule */ + event_rule_view = lttng_buffer_view_from_view(view, offset, -1); + size = lttng_event_rule_create_from_buffer(&event_rule_view, + &event_rule); + if (size < 0 || !event_rule) { + goto error; + } + + /* Create condition (no capture descriptors yet) at this point */ + condition = lttng_condition_event_rule_create(event_rule); + if (!condition) { + goto error; + } + + /* Ownership moved to `condition` */ + event_rule = NULL; + + /* Capture descriptor count */ + assert(size >= 0); + offset += (size_t) size; + capture_descr_count = uint_from_buffer(view, sizeof(uint32_t), &offset); + if (capture_descr_count == UINT64_C(-1)) { + goto error; + } + + /* Capture descriptors */ + for (i = 0; i < capture_descr_count; i++) { + enum lttng_condition_status status; + struct lttng_capture_descriptor *desc; + + struct lttng_event_expr *expr = event_expr_from_buffer( + view, &offset); + int32_t payload_index = int_from_buffer(view, sizeof(int32_t), + &offset); + + if (!expr) { + goto error; + } + + /* Move ownership of `expr` to `condition` */ + status = lttng_condition_event_rule_append_capture_descriptor( + condition, expr); + if (status != LTTNG_CONDITION_STATUS_OK) { + /* `expr` not moved: destroy it */ + lttng_event_expr_destroy(expr); + goto error; + } + + /* + * Set the internal payload object for the descriptor. This can + * be used by liblttng-ctl to access capture msgpack payload on + * the client side. + */ + desc = lttng_condition_event_rule_get_internal_capture_descriptor_at_index(condition, i); + if (desc == NULL) { + goto error; + } + desc->capture_index = payload_index; + } + + consumed_length = (ssize_t) offset; + *_condition = condition; + condition = NULL; + goto end; + +error: + consumed_length = -1; + +end: + lttng_event_rule_destroy(event_rule); + lttng_condition_destroy(condition); + return consumed_length; +} + +LTTNG_HIDDEN +enum lttng_condition_status +lttng_condition_event_rule_get_rule_no_const( + const struct lttng_condition *condition, + struct lttng_event_rule **rule) +{ + struct lttng_condition_event_rule *event_rule; + enum lttng_condition_status status = LTTNG_CONDITION_STATUS_OK; + + if (!condition || !IS_EVENT_RULE_CONDITION(condition) || !rule) { + status = LTTNG_CONDITION_STATUS_INVALID; + goto end; + } + + event_rule = container_of(condition, struct lttng_condition_event_rule, + parent); + if (!event_rule->rule) { + status = LTTNG_CONDITION_STATUS_UNSET; + goto end; + } + *rule = event_rule->rule; +end: + return status; +} + +enum lttng_condition_status +lttng_condition_event_rule_get_rule( + const struct lttng_condition *condition, + const struct lttng_event_rule **rule) +{ + struct lttng_event_rule *no_const_rule = NULL; + enum lttng_condition_status status; + + status = lttng_condition_event_rule_get_rule_no_const(condition, &no_const_rule); + *rule = no_const_rule; + return status; +} + +enum lttng_condition_status +lttng_condition_event_rule_append_capture_descriptor( + struct lttng_condition *condition, + struct lttng_event_expr *expr) +{ + enum lttng_condition_status status = LTTNG_CONDITION_STATUS_OK; + struct lttng_condition_event_rule *event_rule_cond = + container_of(condition, + struct lttng_condition_event_rule, parent); + int ret; + struct lttng_capture_descriptor *descriptor = NULL; + const struct lttng_event_rule *rule = NULL; + + /* Only accept l-values */ + if (!condition || !IS_EVENT_RULE_CONDITION(condition) || !expr || + !lttng_event_expr_is_lvalue(expr)) { + status = LTTNG_CONDITION_STATUS_INVALID; + goto end; + } + + status = lttng_condition_event_rule_get_rule(condition, &rule); + if (status != LTTNG_CONDITION_STATUS_OK) { + goto end; + } + + switch(lttng_event_rule_get_type(rule)) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + /* Supported */ + status = LTTNG_CONDITION_STATUS_OK; + break; + case LTTNG_EVENT_RULE_TYPE_UNKNOWN: + status = LTTNG_CONDITION_STATUS_INVALID; + break; + default: + status = LTTNG_CONDITION_STATUS_UNSUPPORTED; + break; + } + + if (status != LTTNG_CONDITION_STATUS_OK) { + goto end; + } + + descriptor = malloc(sizeof(*descriptor)); + if (descriptor == NULL) { + status = LTTNG_CONDITION_STATUS_ERROR; + goto end; + } + + descriptor->capture_index = -1; + descriptor->event_expression = expr; + + ret = lttng_dynamic_pointer_array_add_pointer( + &event_rule_cond->capture_descriptors, descriptor); + if (ret) { + status = LTTNG_CONDITION_STATUS_ERROR; + goto end; + } + + /* Ownership is transfered to the internal capture_descriptors array */ + descriptor = NULL; +end: + free(descriptor); + return status; +} + +enum lttng_condition_status +lttng_condition_event_rule_get_capture_descriptor_count( + const struct lttng_condition *condition, unsigned int *count) +{ + enum lttng_condition_status status = LTTNG_CONDITION_STATUS_OK; + const struct lttng_condition_event_rule *event_rule_cond = + container_of(condition, + const struct lttng_condition_event_rule, + parent); + + if (!condition || !IS_EVENT_RULE_CONDITION(condition) || !count) { + status = LTTNG_CONDITION_STATUS_INVALID; + goto end; + } + + *count = lttng_dynamic_pointer_array_get_count( + &event_rule_cond->capture_descriptors); + +end: + return status; +} + +LTTNG_HIDDEN +struct lttng_capture_descriptor * +lttng_condition_event_rule_get_internal_capture_descriptor_at_index( + const struct lttng_condition *condition, unsigned int index) +{ + const struct lttng_condition_event_rule *event_rule_cond = + container_of(condition, + const struct lttng_condition_event_rule, + parent); + struct lttng_capture_descriptor *desc = NULL; + unsigned int count; + enum lttng_condition_status status; + + if (!condition || !IS_EVENT_RULE_CONDITION(condition)) { + goto end; + } + + status = lttng_condition_event_rule_get_capture_descriptor_count( + condition, &count); + if (status != LTTNG_CONDITION_STATUS_OK) { + goto end; + } + + if (index >= count) { + goto end; + } + + desc = lttng_dynamic_pointer_array_get_pointer( + &event_rule_cond->capture_descriptors, index); +end: + return desc; +} + +const struct lttng_event_expr * +lttng_condition_event_rule_get_capture_descriptor_at_index( + const struct lttng_condition *condition, unsigned int index) +{ + const struct lttng_event_expr *expr = NULL; + const struct lttng_capture_descriptor *desc = NULL; + + desc = lttng_condition_event_rule_get_internal_capture_descriptor_at_index( + condition, index); + if (desc == NULL) { + goto end; + } + expr = desc->event_expression; + +end: + return expr; +} + +LTTNG_HIDDEN +ssize_t lttng_evaluation_event_rule_create_from_buffer( + const struct lttng_condition_event_rule *condition, + const struct lttng_buffer_view *view, + struct lttng_evaluation **_evaluation) +{ + ssize_t ret, offset = 0; + const char *name; + struct lttng_evaluation *evaluation = NULL; + const struct lttng_evaluation_event_rule_comm *comm = + (const struct lttng_evaluation_event_rule_comm *) view->data; + struct lttng_buffer_view current_view; + uint32_t capture_payload_size; + const char *capture_payload = NULL; + + if (!_evaluation) { + ret = -1; + goto error; + } + + if (view->size < sizeof(*comm)) { + ret = -1; + goto error; + } + + /* Map the name, view of the payload */ + offset += sizeof(*comm); + current_view = lttng_buffer_view_from_view(view, offset, comm->trigger_name_length); + name = current_view.data; + if (!name) { + ret = -1; + goto error; + } + + if (comm->trigger_name_length == 1 || + name[comm->trigger_name_length - 1] != '\0' || + strlen(name) != comm->trigger_name_length - 1) { + /* + * Check that the name is not NULL, is NULL-terminated, and + * does not contain a NULL before the last byte. + */ + ret = -1; + goto error; + } + + offset += comm->trigger_name_length; + current_view = lttng_buffer_view_from_view(view, offset, -1); + + if (current_view.size < sizeof(capture_payload_size)) { + ret = -1; + goto error; + } + + memcpy(&capture_payload_size, current_view.data, + sizeof(capture_payload_size)); + offset += sizeof(capture_payload_size); + + if (capture_payload_size > 0) { + current_view = lttng_buffer_view_from_view(view, offset, -1); + + if (current_view.size < capture_payload_size) { + ret = -1; + goto error; + } + + capture_payload = current_view.data; + } + + evaluation = lttng_evaluation_event_rule_create(condition, name, + capture_payload, capture_payload_size, true); + if (!evaluation) { + ret = -1; + goto error; + } + + offset += capture_payload_size; + *_evaluation = evaluation; + evaluation = NULL; + ret = offset; + +error: + lttng_evaluation_destroy(evaluation); + return ret; +} + +static +int lttng_evaluation_event_rule_serialize( + const struct lttng_evaluation *evaluation, + struct lttng_dynamic_buffer *buf) +{ + int ret = 0; + struct lttng_evaluation_event_rule *hit; + struct lttng_evaluation_event_rule_comm comm; + uint32_t capture_payload_size; + + hit = container_of(evaluation, struct lttng_evaluation_event_rule, + parent); + comm.trigger_name_length = strlen(hit->name) + 1; + ret = lttng_dynamic_buffer_append(buf, &comm, sizeof(comm)); + if (ret) { + goto end; + } + ret = lttng_dynamic_buffer_append(buf, hit->name, comm.trigger_name_length); + if (ret) { + goto end; + } + + capture_payload_size = (uint32_t) hit->capture_payload.size; + ret = lttng_dynamic_buffer_append(buf, &capture_payload_size, + sizeof(capture_payload_size)); + if (ret) { + goto end; + } + + ret = lttng_dynamic_buffer_append(buf, hit->capture_payload.data, + hit->capture_payload.size); + if (ret) { + goto end; + } + +end: + return ret; +} + +static +bool msgpack_str_is_equal(const struct msgpack_object *obj, const char *str) +{ + bool is_equal = true; + + assert(obj->type == MSGPACK_OBJECT_STR); + + if (obj->via.str.size != strlen(str)) { + is_equal = false; + goto end; + } + + if (strncmp(obj->via.str.ptr, str, obj->via.str.size) != 0) { + is_equal = false; + goto end; + } + +end: + return is_equal; +} + +static +const msgpack_object *get_msgpack_map_obj(const struct msgpack_object *map_obj, + const char *name) +{ + const msgpack_object *ret = NULL; + size_t i; + + assert(map_obj->type == MSGPACK_OBJECT_MAP); + + for (i = 0; i < map_obj->via.map.size; i++) { + const struct msgpack_object_kv *kv = &map_obj->via.map.ptr[i]; + + assert(kv->key.type == MSGPACK_OBJECT_STR); + + if (msgpack_str_is_equal(&kv->key, name)) { + ret = &kv->val; + goto end; + } + } + +end: + return ret; +} + +static +void lttng_evaluation_event_rule_destroy( + struct lttng_evaluation *evaluation) +{ + struct lttng_evaluation_event_rule *hit; + + hit = container_of(evaluation, struct lttng_evaluation_event_rule, + parent); + free(hit->name); + lttng_dynamic_buffer_reset(&hit->capture_payload); + if (hit->captured_values) { + lttng_event_field_value_destroy(hit->captured_values); + } + free(hit); +} + +static +int event_field_value_from_obj(const msgpack_object *obj, + struct lttng_event_field_value **field_val) +{ + assert(obj); + assert(field_val); + int ret = 0; + + switch (obj->type) { + case MSGPACK_OBJECT_NIL: + /* Unavailable */ + *field_val = NULL; + goto end; + case MSGPACK_OBJECT_POSITIVE_INTEGER: + *field_val = lttng_event_field_value_uint_create( + obj->via.u64); + break; + case MSGPACK_OBJECT_NEGATIVE_INTEGER: + *field_val = lttng_event_field_value_int_create( + obj->via.i64); + break; + case MSGPACK_OBJECT_FLOAT32: + case MSGPACK_OBJECT_FLOAT64: + *field_val = lttng_event_field_value_real_create( + obj->via.f64); + break; + case MSGPACK_OBJECT_STR: + *field_val = lttng_event_field_value_string_create_with_size( + obj->via.str.ptr, obj->via.str.size); + break; + case MSGPACK_OBJECT_ARRAY: + { + size_t i; + + *field_val = lttng_event_field_value_array_create(); + if (!*field_val) { + goto error; + } + + for (i = 0; i < obj->via.array.size; i++) { + const msgpack_object *elem_obj = &obj->via.array.ptr[i]; + struct lttng_event_field_value *elem_field_val; + + ret = event_field_value_from_obj(elem_obj, + &elem_field_val); + + if (ret) { + goto error; + } + + if (elem_field_val) { + ret = lttng_event_field_value_array_append( + *field_val, elem_field_val); + } else { + ret = lttng_event_field_value_array_append_unavailable( + *field_val); + } + + if (ret) { + lttng_event_field_value_destroy(elem_field_val); + goto error; + } + } + + break; + } + case MSGPACK_OBJECT_MAP: + { + /* + * As of this version, the only valid map object is + * for an enumeration value, for example: + * + * type: enum + * value: 177 + * labels: + * - Labatt 50 + * - Molson Dry + * - Carling Black Label + */ + const msgpack_object *inner_obj; + size_t label_i; + + inner_obj = get_msgpack_map_obj(obj, "type"); + if (!inner_obj) { + ERR("Missing `type` entry in map object."); + goto error; + } + + if (inner_obj->type != MSGPACK_OBJECT_STR) { + ERR("Map object's `type` entry is not a string (it's a %d).", + inner_obj->type); + goto error; + } + + if (!msgpack_str_is_equal(inner_obj, "enum")) { + ERR("Map object's `type` entry: expecting `enum`."); + goto error; + } + + inner_obj = get_msgpack_map_obj(obj, "value"); + if (!inner_obj) { + ERR("Missing `value` entry in map object."); + goto error; + } + + if (inner_obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER) { + *field_val = lttng_event_field_value_enum_uint_create( + inner_obj->via.u64); + } else if (inner_obj->type == MSGPACK_OBJECT_NEGATIVE_INTEGER) { + *field_val = lttng_event_field_value_enum_int_create( + inner_obj->via.i64); + } else { + ERR("Map object's `value` entry is not an integer (it's a %d).", + inner_obj->type); + goto error; + } + + if (!*field_val) { + goto error; + } + + inner_obj = get_msgpack_map_obj(obj, "labels"); + if (!inner_obj) { + /* No labels */ + goto end; + } + + if (inner_obj->type != MSGPACK_OBJECT_ARRAY) { + ERR("Map object's `labels` entry is not an array (it's a %d).", + inner_obj->type); + goto error; + } + + for (label_i = 0; label_i < inner_obj->via.array.size; + label_i++) { + int iret; + const msgpack_object *elem_obj = + &inner_obj->via.array.ptr[label_i]; + + if (elem_obj->type != MSGPACK_OBJECT_STR) { + ERR("Map object's `labels` entry's type is not a string (it's a %d).", + elem_obj->type); + goto error; + } + + iret = lttng_event_field_value_enum_append_label_with_size( + *field_val, elem_obj->via.str.ptr, + elem_obj->via.str.size); + if (iret) { + goto error; + } + } + + break; + } + default: + ERR("Unexpected object type %d.", obj->type); + goto error; + } + + if (!*field_val) { + goto error; + } + + goto end; + +error: + lttng_event_field_value_destroy(*field_val); + *field_val = NULL; + ret = -1; + +end: + return ret; +} + +static +struct lttng_event_field_value *event_field_value_from_capture_payload( + const struct lttng_condition_event_rule *condition, + const char *capture_payload, size_t capture_payload_size) +{ + struct lttng_event_field_value *ret = NULL; + msgpack_unpacked unpacked; + msgpack_unpack_return unpack_return; + const msgpack_object *root_obj; + const msgpack_object_array *root_array_obj; + size_t i; + size_t count; + + assert(condition); + assert(capture_payload); + + /* Initialize value */ + msgpack_unpacked_init(&unpacked); + + /* Decode */ + unpack_return = msgpack_unpack_next(&unpacked, capture_payload, + capture_payload_size, NULL); + if (unpack_return != MSGPACK_UNPACK_SUCCESS) { + ERR("msgpack_unpack_next() failed to decode the " + "MessagePack-encoded capture payload " + "(size %zu); returned %d.", + capture_payload_size, unpack_return); + goto error; + } + + /* Get root array */ + root_obj = &unpacked.data; + + if (root_obj->type != MSGPACK_OBJECT_ARRAY) { + ERR("Expecting an array as the root object; got type %d.", + root_obj->type); + goto error; + } + + root_array_obj = &root_obj->via.array; + + /* Create an empty root array event field value */ + ret = lttng_event_field_value_array_create(); + if (!ret) { + goto error; + } + + /* + * For each capture descriptor in the condition object: + * + * 1. Get its corresponding captured field value MessagePack + * object. + * + * 2. Create a corresponding event field value. + * + * 3. Append it to `ret` (the root array event field value). + */ + count = lttng_dynamic_pointer_array_get_count( + &condition->capture_descriptors); + assert(count > 0); + + for (i = 0; i < count; i++) { + const struct lttng_capture_descriptor *capture_descriptor = + lttng_condition_event_rule_get_internal_capture_descriptor_at_index( + &condition->parent, i); + const msgpack_object *elem_obj; + struct lttng_event_field_value *elem_field_val; + int iret; + + assert(capture_descriptor); + assert(capture_descriptor->capture_index >= 0); + + if (capture_descriptor->capture_index >= root_array_obj->size) { + ERR("Root array object of size %u does not have enough " + "elements for the capture index %u " + "(for capture descriptor #%zu).", + (unsigned int) root_array_obj->size, + (unsigned int) capture_descriptor->capture_index, + i); + goto error; + } + + elem_obj = &root_array_obj->ptr[(size_t) capture_descriptor->capture_index]; + iret = event_field_value_from_obj(elem_obj, + &elem_field_val); + if (iret) { + goto error; + } + + if (elem_field_val) { + iret = lttng_event_field_value_array_append(ret, + elem_field_val); + } else { + iret = lttng_event_field_value_array_append_unavailable( + ret); + } + + if (iret) { + lttng_event_field_value_destroy(elem_field_val); + goto error; + } + } + + goto end; + +error: + lttng_event_field_value_destroy(ret); + ret = NULL; + +end: + msgpack_unpacked_destroy(&unpacked); + return ret; +} + +LTTNG_HIDDEN +struct lttng_evaluation *lttng_evaluation_event_rule_create( + const struct lttng_condition_event_rule *condition, + const char *trigger_name, + const char *capture_payload, size_t capture_payload_size, + bool decode_capture_payload) +{ + struct lttng_evaluation_event_rule *hit; + + hit = zmalloc(sizeof(struct lttng_evaluation_event_rule)); + if (!hit) { + goto error; + } + + /* TODO errir handling */ + hit->name = strdup(trigger_name); + lttng_dynamic_buffer_init(&hit->capture_payload); + + if (capture_payload) { + lttng_dynamic_buffer_append(&hit->capture_payload, + capture_payload, capture_payload_size); + + if (decode_capture_payload) { + hit->captured_values = + event_field_value_from_capture_payload( + condition, + capture_payload, + capture_payload_size); + if (!hit->captured_values) { + ERR("Failed to decode the capture payload (size %zu).", + capture_payload_size); + goto error; + } + } + } + + hit->parent.type = LTTNG_CONDITION_TYPE_EVENT_RULE_HIT; + hit->parent.serialize = lttng_evaluation_event_rule_serialize; + hit->parent.destroy = lttng_evaluation_event_rule_destroy; + goto end; + +error: + lttng_evaluation_event_rule_destroy(&hit->parent); + hit = NULL; + +end: + return &hit->parent; +} + +enum lttng_evaluation_status lttng_evaluation_get_captured_values( + const struct lttng_evaluation *evaluation, + const struct lttng_event_field_value **field_val) +{ + struct lttng_evaluation_event_rule *hit; + enum lttng_evaluation_status status = LTTNG_EVALUATION_STATUS_OK; + + if (!evaluation || !is_event_rule_evaluation(evaluation) || + !field_val) { + status = LTTNG_EVALUATION_STATUS_INVALID; + goto end; + } + + hit = container_of(evaluation, struct lttng_evaluation_event_rule, + parent); + if (!hit->captured_values) { + status = LTTNG_EVALUATION_STATUS_INVALID; + goto end; + } + + *field_val = hit->captured_values; + +end: + return status; +} + +enum lttng_evaluation_status +lttng_evaluation_event_rule_get_trigger_name( + const struct lttng_evaluation *evaluation, + const char **name) +{ + struct lttng_evaluation_event_rule *hit; + enum lttng_evaluation_status status = LTTNG_EVALUATION_STATUS_OK; + + if (!evaluation || !is_event_rule_evaluation(evaluation) || !name) { + status = LTTNG_EVALUATION_STATUS_INVALID; + goto end; + } + + hit = container_of(evaluation, struct lttng_evaluation_event_rule, + parent); + *name = hit->name; +end: + return status; +} + +LTTNG_HIDDEN +enum lttng_error_code +lttng_condition_event_rule_generate_capture_descriptor_bytecode_set( + struct lttng_condition *condition, + struct lttng_dynamic_pointer_array *bytecode_set) +{ + enum lttng_error_code ret; + enum lttng_condition_status status; + unsigned int capture_count; + const struct lttng_condition_event_rule_capture_bytecode_element *set_element; + struct lttng_capture_descriptor *local_capture_desc; + ssize_t set_count; + struct lttng_condition_event_rule_capture_bytecode_element *set_element_to_append = + NULL; + struct lttng_bytecode *bytecode = NULL; + + if (!condition || !IS_EVENT_RULE_CONDITION(condition) || + !bytecode_set) { + ret = LTTNG_ERR_FATAL; + goto end; + } + + status = lttng_condition_event_rule_get_capture_descriptor_count( + condition, &capture_count); + if (status != LTTNG_CONDITION_STATUS_OK) { + ret = LTTNG_ERR_FATAL; + goto end; + } + + /* + * O(n^2), don't care. This code path is not hot. + * Before inserting into the set, validate that the expression is not + * already present in it. + */ + for (unsigned int i = 0; i < capture_count; i++) { + int found_in_set = false; + + set_count = lttng_dynamic_pointer_array_get_count(bytecode_set); + + local_capture_desc = + lttng_condition_event_rule_get_internal_capture_descriptor_at_index( + condition, i); + if (local_capture_desc == NULL) { + ret = LTTNG_ERR_FATAL; + goto end; + } + + /* + * Iterate over the set to check if already present in the set. + */ + for (ssize_t j = 0; j < set_count; j++) { + set_element = (struct lttng_condition_event_rule_capture_bytecode_element + *) + lttng_dynamic_pointer_array_get_pointer( + bytecode_set, j); + if (set_element == NULL) { + ret = LTTNG_ERR_FATAL; + goto end; + } + + if (!lttng_event_expr_is_equal( + local_capture_desc->event_expression, + set_element->expression)) { + /* Check against next set element */ + continue; + } + + /* + * Already present in the set, assign the + * capture index of the capture descriptor for + * future use. + */ + found_in_set = true; + local_capture_desc->capture_index = j; + /* Exit inner loop */ + break; + } + + if (found_in_set) { + /* Process next local capture descriptor */ + continue; + } + + /* + * Not found in the set. + * Insert the capture descriptor in the set. + */ + set_element_to_append = malloc(sizeof(*set_element_to_append)); + if (set_element_to_append == NULL) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + /* Generate the bytecode */ + status = lttng_event_expr_to_bytecode( + local_capture_desc->event_expression, + &bytecode); + if (status < 0 || bytecode == NULL) { + /* TODO: return pertinent capture related error code */ + ret = LTTNG_ERR_FILTER_INVAL; + goto end; + } + + set_element_to_append->bytecode = bytecode; + + /* + * Ensure the lifetime of the event expression. + * Our reference will be put on condition destroy. + */ + lttng_event_expr_get(local_capture_desc->event_expression); + set_element_to_append->expression = + local_capture_desc->event_expression; + + ret = lttng_dynamic_pointer_array_add_pointer( + bytecode_set, set_element_to_append); + if (ret < 0) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + /* Ownership tranfered to the bytecode set */ + set_element_to_append = NULL; + bytecode = NULL; + + /* Assign the capture descriptor for future use */ + local_capture_desc->capture_index = set_count; + } + + /* Everything went better than expected */ + ret = LTTNG_OK; + +end: + free(set_element_to_append); + free(bytecode); + return ret; +} diff --git a/src/common/session-consumed-size.c b/src/common/conditions/session-consumed-size.c similarity index 99% rename from src/common/session-consumed-size.c rename to src/common/conditions/session-consumed-size.c index dfb72632e..6e0fcb49e 100644 --- a/src/common/session-consumed-size.c +++ b/src/common/conditions/session-consumed-size.c @@ -65,7 +65,8 @@ end: static int lttng_condition_session_consumed_size_serialize( const struct lttng_condition *condition, - struct lttng_dynamic_buffer *buf) + struct lttng_dynamic_buffer *buf, + int *fd_to_send) { int ret; size_t session_name_len; @@ -102,6 +103,11 @@ int lttng_condition_session_consumed_size_serialize( if (ret) { goto end; } + + if (fd_to_send) { + /* No fd to send */ + *fd_to_send = -1; + } end: return ret; } diff --git a/src/common/session-rotation.c b/src/common/conditions/session-rotation.c similarity index 98% rename from src/common/session-rotation.c rename to src/common/conditions/session-rotation.c index f8d4439de..1c4124f64 100644 --- a/src/common/session-rotation.c +++ b/src/common/conditions/session-rotation.c @@ -19,7 +19,8 @@ bool lttng_condition_session_rotation_validate( static int lttng_condition_session_rotation_serialize( const struct lttng_condition *condition, - struct lttng_dynamic_buffer *buf); + struct lttng_dynamic_buffer *buf, + int *fd_to_send); static bool lttng_condition_session_rotation_is_equal(const struct lttng_condition *_a, const struct lttng_condition *_b); @@ -95,7 +96,8 @@ end: static int lttng_condition_session_rotation_serialize( const struct lttng_condition *condition, - struct lttng_dynamic_buffer *buf) + struct lttng_dynamic_buffer *buf, + int *fd_to_send) { int ret; size_t session_name_len; @@ -128,6 +130,11 @@ int lttng_condition_session_rotation_serialize( if (ret) { goto end; } + + if (fd_to_send) { + /* No fd to send */ + *fd_to_send = -1; + } end: return ret; } diff --git a/src/common/credentials.c b/src/common/credentials.c new file mode 100644 index 000000000..77447ca31 --- /dev/null +++ b/src/common/credentials.c @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 Jonathan Rajotte + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include "credentials.h" + +bool lttng_credentials_is_equal(const struct lttng_credentials *a, + const struct lttng_credentials *b) +{ + assert(a); + assert(b); + if ((a->uid != b->uid) || (a->gid != b->gid)) { + return false; + } + return true; +}; diff --git a/src/common/credentials.h b/src/common/credentials.h index a0d93308c..16b65575b 100644 --- a/src/common/credentials.h +++ b/src/common/credentials.h @@ -9,10 +9,15 @@ #define LTTNG_CREDENTIALS_H #include +#include +#include struct lttng_credentials { uid_t uid; gid_t gid; }; +bool lttng_credentials_is_equal(const struct lttng_credentials *a, + const struct lttng_credentials *b); + #endif /* LTTNG_CREDENTIALS_H */ diff --git a/src/common/domain.c b/src/common/domain.c new file mode 100644 index 000000000..5d6e7a078 --- /dev/null +++ b/src/common/domain.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 - EfficiOS, inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License, version 2 only, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "lttng/domain-internal.h" +#include "common/macros.h" + +LTTNG_HIDDEN +const char *lttng_domain_type_str(enum lttng_domain_type domain_type) +{ + switch (domain_type) { + case LTTNG_DOMAIN_NONE: + return "none"; + + case LTTNG_DOMAIN_KERNEL: + return "kernel"; + + case LTTNG_DOMAIN_UST: + return "ust"; + + case LTTNG_DOMAIN_JUL: + return "jul"; + + case LTTNG_DOMAIN_LOG4J: + return "log4j"; + + case LTTNG_DOMAIN_PYTHON: + return "python"; + + default: + return "???"; + } +} diff --git a/src/common/dynamic-array.h b/src/common/dynamic-array.h index 208dd1956..2b3ea6273 100644 --- a/src/common/dynamic-array.h +++ b/src/common/dynamic-array.h @@ -121,6 +121,23 @@ void *lttng_dynamic_pointer_array_get_pointer( return *element; } +/* + * Returns the pointer at index `index`, sets the array slot to NULL. Does not + * run the destructor. + */ + +static inline +void *lttng_dynamic_pointer_array_steal_pointer( + struct lttng_dynamic_pointer_array *array, size_t index) +{ + void **p_element = lttng_dynamic_array_get_element(&array->array, index); + void *element = *p_element; + + *p_element = NULL; + + return element; +} + /* * Add a pointer to the end of a dynamic pointer array. The array's element * count is increased by one and its underlying capacity is adjusted diff --git a/src/common/error.c b/src/common/error.c index 70063e504..82190b9fd 100644 --- a/src/common/error.c +++ b/src/common/error.c @@ -7,14 +7,15 @@ #define _LGPL_SOURCE #include +#include #include +#include #include #include -#include -#include #include #include +#include #include "error.h" @@ -28,6 +29,7 @@ static int lttng_opt_abort_on_error = -1; /* TLS variable that contains the time of one single log entry. */ DEFINE_URCU_TLS(struct log_time, error_log_time); +DEFINE_URCU_TLS(const char *, logger_thread_name); LTTNG_HIDDEN const char *log_add_time(void) @@ -66,6 +68,30 @@ error: return ""; } +LTTNG_HIDDEN +void logger_set_thread_name(const char *name, bool set_pthread_name) +{ + int ret; + + assert(name); + URCU_TLS(logger_thread_name) = name; + + if (set_pthread_name) { + char pthread_name[16]; + + /* + * Truncations are expected since pthread limits thread names to + * a generous 16 characters. + */ + strncpy(pthread_name, name, sizeof(pthread_name)); + pthread_name[sizeof(pthread_name) - 1] = '\0'; + ret = pthread_setname_np(pthread_self(), pthread_name); + if (ret) { + DBG("Failed to set pthread name attribute"); + } + } +} + /* * Human readable error message. */ diff --git a/src/common/error.h b/src/common/error.h index 33a299c4e..ea00ea5f9 100644 --- a/src/common/error.h +++ b/src/common/error.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #ifndef _GNU_SOURCE #error "lttng-tools error.h needs _GNU_SOURCE" @@ -42,6 +44,7 @@ struct log_time { char str[19]; }; extern DECLARE_URCU_TLS(struct log_time, error_log_time); +extern DECLARE_URCU_TLS(const char *, logger_thread_name); extern int lttng_opt_quiet; extern int lttng_opt_verbose; @@ -139,13 +142,44 @@ static inline void __lttng_print_check_abort(enum lttng_error_level type) } while (0) /* Three level of debug. Use -v, -vv or -vvv for the levels */ -#define _ERRMSG(msg, type, fmt, args...) __lttng_print(type, msg \ - " - %s [%ld/%ld]: " fmt " (in %s() at " __FILE__ ":" XSTR(__LINE__) ")\n", \ - log_add_time(), (long) getpid(), (long) lttng_gettid(), ## args, __func__) +#define _ERRMSG(msg, type, fmt, args...) \ + do { \ + if (caa_unlikely(__lttng_print_check_opt(type))) { \ + char generic_name[MAX_INT_DEC_LEN(long) + \ + MAX_INT_DEC_LEN(long)]; \ + \ + snprintf(generic_name, sizeof(generic_name), \ + "%ld/%ld", (long) getpid(), \ + (long) lttng_gettid()); \ + \ + __lttng_print(type, \ + msg " - %s [%s]: " fmt \ + " (in %s() at " __FILE__ \ + ":" XSTR(__LINE__) ")\n", \ + log_add_time(), \ + URCU_TLS(logger_thread_name) ?: \ + generic_name, \ + ##args, __func__); \ + } \ + } while (0) -#define _ERRMSG_NO_LOC(msg, type, fmt, args...) __lttng_print(type, msg \ - " - %s [%ld/%ld]: " fmt "\n", \ - log_add_time(), (long) getpid(), (long) lttng_gettid(), ## args) +#define _ERRMSG_NO_LOC(msg, type, fmt, args...) \ + do { \ + if (caa_unlikely(__lttng_print_check_opt(type))) { \ + char generic_name[MAX_INT_DEC_LEN(long) + \ + MAX_INT_DEC_LEN(long)]; \ + \ + snprintf(generic_name, sizeof(generic_name), \ + "%ld/%ld", (long) getpid(), \ + (long) lttng_gettid()); \ + \ + __lttng_print(type, msg " - %s [%s]: " fmt "\n", \ + log_add_time(), \ + URCU_TLS(logger_thread_name) ?: \ + generic_name, \ + ##args); \ + } \ + } while (0) #define MSG(fmt, args...) \ __lttng_print(PRINT_MSG, fmt "\n", ## args) @@ -158,10 +192,10 @@ static inline void __lttng_print_check_abort(enum lttng_error_level type) #define BUG(fmt, args...) _ERRMSG("BUG", PRINT_BUG, fmt, ## args) -#define DBG(fmt, args...) _ERRMSG("DEBUG1", PRINT_DBG, fmt, ## args) -#define DBG_NO_LOC(fmt, args...) _ERRMSG_NO_LOC("DEBUG1", PRINT_DBG, fmt, ## args) -#define DBG2(fmt, args...) _ERRMSG("DEBUG2", PRINT_DBG2, fmt, ## args) -#define DBG3(fmt, args...) _ERRMSG("DEBUG3", PRINT_DBG3, fmt, ## args) +#define DBG(fmt, args...) _ERRMSG("DBG1", PRINT_DBG, fmt, ## args) +#define DBG_NO_LOC(fmt, args...) _ERRMSG_NO_LOC("DBG1", PRINT_DBG, fmt, ## args) +#define DBG2(fmt, args...) _ERRMSG("DBG2", PRINT_DBG2, fmt, ## args) +#define DBG3(fmt, args...) _ERRMSG("DBG3", PRINT_DBG3, fmt, ## args) #define LOG(type, fmt, args...) \ do { \ switch (type) { \ @@ -226,4 +260,8 @@ const char *error_get_str(int32_t code); */ const char *log_add_time(void); +/* Name must be a statically-allocated string. */ +LTTNG_HIDDEN +void logger_set_thread_name(const char *name, bool set_pthread_name); + #endif /* _ERROR_H */ diff --git a/src/common/evaluation.c b/src/common/evaluation.c index efd212921..ad3915ab0 100644 --- a/src/common/evaluation.c +++ b/src/common/evaluation.c @@ -5,10 +5,12 @@ * */ +#include #include #include #include #include +#include #include #include #include @@ -48,6 +50,7 @@ end: LTTNG_HIDDEN ssize_t lttng_evaluation_create_from_buffer( + const struct lttng_condition *condition, const struct lttng_buffer_view *src_view, struct lttng_evaluation **evaluation) { @@ -106,6 +109,19 @@ ssize_t lttng_evaluation_create_from_buffer( } evaluation_size += ret; break; + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + assert(condition); + assert(condition->type == LTTNG_CONDITION_TYPE_EVENT_RULE_HIT); + ret = lttng_evaluation_event_rule_create_from_buffer( + container_of(condition, + struct lttng_condition_event_rule, + parent), + &evaluation_view, evaluation); + if (ret < 0) { + goto end; + } + evaluation_size += ret; + break; default: ERR("Attempted to create evaluation of unknown type (%i)", (int) evaluation_comm->type); diff --git a/src/common/event-expr-to-bytecode.c b/src/common/event-expr-to-bytecode.c new file mode 100644 index 000000000..05eccb7ea --- /dev/null +++ b/src/common/event-expr-to-bytecode.c @@ -0,0 +1,201 @@ +/* + * Copyright 2020 EfficiOS, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include "event-expr-to-bytecode.h" + +#include +#include +#include + + +static +int event_expr_to_bytecode_recursive(const struct lttng_event_expr *expr, + struct lttng_bytecode_alloc **bytecode, + struct lttng_bytecode_alloc **bytecode_reloc) +{ + enum lttng_event_expr_status event_expr_status; + int status; + + switch (lttng_event_expr_get_type(expr)) { + case LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD: + { + const char *name; + + status = bytecode_push_get_payload_root(bytecode); + if (status) { + goto end; + } + + name = lttng_event_expr_event_payload_field_get_name(expr); + if (!name) { + status = -1; + goto end; + } + + status = bytecode_push_get_symbol(bytecode, bytecode_reloc, name); + if (status) { + goto end; + } + + break; + } + + case LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD: + { + const char *name; + + status = bytecode_push_get_context_root(bytecode); + if (status) { + goto end; + } + + name = lttng_event_expr_channel_context_field_get_name(expr); + if (!name) { + status = -1; + goto end; + } + + status = bytecode_push_get_symbol(bytecode, bytecode_reloc, name); + if (status) { + goto end; + } + + break; + } + + case LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD: + { + const char *provider_name, *type_name; + char *name = NULL; + int ret; + + status = bytecode_push_get_app_context_root(bytecode); + if (status) { + goto end; + } + + provider_name = lttng_event_expr_app_specific_context_field_get_provider_name(expr); + if (!provider_name) { + status = -1; + goto end; + } + + type_name = lttng_event_expr_app_specific_context_field_get_type_name(expr); + if (!type_name) { + status = -1; + goto end; + } + + /* Reconstitute the app context field name from its two parts. */ + ret = asprintf(&name, "%s:%s", provider_name, type_name); + if (ret < 0) { + status = -1; + goto end; + } + + status = bytecode_push_get_symbol(bytecode, bytecode_reloc, name); + free(name); + if (status) { + goto end; + } + + break; + } + + case LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT: + { + const struct lttng_event_expr *parent; + unsigned int index; + + parent = lttng_event_expr_array_field_element_get_parent_expr(expr); + if (!parent) { + status = -1; + goto end; + } + + status = event_expr_to_bytecode_recursive(parent, bytecode, bytecode_reloc); + if (status) { + goto end; + } + + event_expr_status = lttng_event_expr_array_field_element_get_index( + expr, &index); + if (event_expr_status != LTTNG_EVENT_EXPR_STATUS_OK) { + status = -1; + goto end; + } + + status = bytecode_push_get_index_u64(bytecode, index); + if (status) { + goto end; + } + + break; + } + + default: + abort(); + } + + status = 0; +end: + return status; +} + +LTTNG_HIDDEN +int lttng_event_expr_to_bytecode(const struct lttng_event_expr *expr, + struct lttng_bytecode **bytecode_out) +{ + struct lttng_bytecode_alloc *bytecode = NULL; + struct lttng_bytecode_alloc *bytecode_reloc = NULL; + struct return_op ret_insn; + int status; + + status = bytecode_init(&bytecode); + if (status) { + goto end; + } + + status = bytecode_init(&bytecode_reloc); + if (status) { + goto end; + } + + status = event_expr_to_bytecode_recursive (expr, &bytecode, &bytecode_reloc); + if (status) { + goto end; + } + + ret_insn.op = BYTECODE_OP_RETURN; + bytecode_push(&bytecode, &ret_insn, 1, sizeof(ret_insn)); + + /* Append symbol table to bytecode. */ + bytecode->b.reloc_table_offset = bytecode_get_len(&bytecode->b); + status = bytecode_push(&bytecode, bytecode_reloc->b.data, + 1, bytecode_get_len(&bytecode_reloc->b)); + if (status) { + goto end; + } + + /* Copy the `lttng_bytecode` out of the `lttng_bytecode_alloc`. */ + *bytecode_out = bytecode_copy(&bytecode->b); + if (!*bytecode_out) { + status = -1; + goto end; + } + +end: + if (bytecode) { + free(bytecode); + } + + if (bytecode_reloc) { + free(bytecode_reloc); + } + + return status; +} diff --git a/src/common/event-expr-to-bytecode.h b/src/common/event-expr-to-bytecode.h new file mode 100644 index 000000000..9d7d57aea --- /dev/null +++ b/src/common/event-expr-to-bytecode.h @@ -0,0 +1,20 @@ +#ifndef SRC_COMMON_EVENT_EXPR_TO_BYTECODE_H +#define SRC_COMMON_EVENT_EXPR_TO_BYTECODE_H + +/* + * Copyright 2020 EfficiOS, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include + +struct lttng_bytecode; +struct lttng_event_expr; + +LTTNG_HIDDEN +int lttng_event_expr_to_bytecode (const struct lttng_event_expr *expr, + struct lttng_bytecode **bytecode_out); + +#endif /* SRC_COMMON_EVENT_EXPR_TO_BYTECODE_H */ diff --git a/src/common/event-expr.c b/src/common/event-expr.c new file mode 100644 index 000000000..10f109c46 --- /dev/null +++ b/src/common/event-expr.c @@ -0,0 +1,449 @@ +/* + * event-expr.c + * + * Linux Trace Toolkit Control Library + * + * Copyright (C) 2020 Philippe Proulx + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#define _LGPL_SOURCE +#include +#include + +#include +#include +#include + +enum lttng_event_expr_type lttng_event_expr_get_type( + const struct lttng_event_expr *expr) +{ + enum lttng_event_expr_type type; + + if (!expr) { + type = LTTNG_EVENT_EXPR_TYPE_INVALID; + goto end; + } + + type = expr->type; + +end: + return type; +} + +static +struct lttng_event_expr *create_empty_expr(enum lttng_event_expr_type type, + size_t size) +{ + struct lttng_event_expr *expr; + + expr = zmalloc(size); + if (!expr) { + goto end; + } + + urcu_ref_init(&expr->ref); + expr->type = type; + +end: + return expr; +} + +static +struct lttng_event_expr_field *create_field_event_expr( + enum lttng_event_expr_type type, + const char *name) +{ + struct lttng_event_expr_field *expr = + container_of( + create_empty_expr(type, sizeof(*expr)), + struct lttng_event_expr_field, parent); + + if (!expr) { + goto error; + } + + assert(name); + expr->name = strdup(name); + if (!expr->name) { + goto error; + } + + goto end; + +error: + lttng_event_expr_destroy(&expr->parent); + +end: + return expr; +} + +struct lttng_event_expr *lttng_event_expr_event_payload_field_create( + const char *field_name) +{ + struct lttng_event_expr *expr = NULL; + + if (!field_name) { + goto end; + } + + expr = &create_field_event_expr( + LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD, + field_name)->parent; + +end: + return expr; +} + +struct lttng_event_expr *lttng_event_expr_channel_context_field_create( + const char *field_name) +{ + struct lttng_event_expr *expr = NULL; + + if (!field_name) { + goto end; + } + + expr = &create_field_event_expr( + LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD, + field_name)->parent; + +end: + return expr; +} + +struct lttng_event_expr *lttng_event_expr_app_specific_context_field_create( + const char *provider_name, const char *type_name) +{ + struct lttng_event_expr_app_specific_context_field *expr = NULL; + + if (!type_name || !provider_name) { + goto error; + } + + expr = container_of(create_empty_expr( + LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD, + sizeof(*expr)), + struct lttng_event_expr_app_specific_context_field, + parent); + if (!expr) { + goto error; + } + + expr->provider_name = strdup(provider_name); + if (!expr->provider_name) { + goto error; + } + + expr->type_name = strdup(type_name); + if (!expr->type_name) { + goto error; + } + + goto end; + +error: + lttng_event_expr_destroy(&expr->parent); + +end: + return &expr->parent; +} + +struct lttng_event_expr *lttng_event_expr_array_field_element_create( + struct lttng_event_expr *array_field_expr, + unsigned int index) +{ + struct lttng_event_expr_array_field_element *expr = NULL; + + /* The parent array field expression must be an l-value */ + if (!array_field_expr || + !lttng_event_expr_is_lvalue(array_field_expr)) { + goto error; + } + + expr = container_of(create_empty_expr( + LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT, + sizeof(*expr)), + struct lttng_event_expr_array_field_element, + parent); + if (!expr) { + goto error; + } + + expr->array_field_expr = array_field_expr; + expr->index = index; + goto end; + +error: + lttng_event_expr_destroy(&expr->parent); + +end: + return &expr->parent; +} + +const char *lttng_event_expr_event_payload_field_get_name( + const struct lttng_event_expr *expr) +{ + const char *ret = NULL; + + if (!expr || expr->type != LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD) { + goto end; + } + + ret = container_of(expr, + const struct lttng_event_expr_field, parent)->name; + +end: + return ret; +} + +const char *lttng_event_expr_channel_context_field_get_name( + const struct lttng_event_expr *expr) +{ + const char *ret = NULL; + + if (!expr || expr->type != LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD) { + goto end; + } + + ret = container_of(expr, + const struct lttng_event_expr_field, parent)->name; + +end: + return ret; +} + +const char *lttng_event_expr_app_specific_context_field_get_provider_name( + const struct lttng_event_expr *expr) +{ + const char *ret = NULL; + + if (!expr || expr->type != LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD) { + goto end; + } + + ret = container_of(expr, + const struct lttng_event_expr_app_specific_context_field, + parent)->provider_name; + +end: + return ret; +} + +const char *lttng_event_expr_app_specific_context_field_get_type_name( + const struct lttng_event_expr *expr) +{ + const char *ret = NULL; + + if (!expr || expr->type != LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD) { + goto end; + } + + ret = container_of(expr, + const struct lttng_event_expr_app_specific_context_field, + parent)->type_name; + +end: + return ret; +} + +const struct lttng_event_expr * +lttng_event_expr_array_field_element_get_parent_expr( + const struct lttng_event_expr *expr) +{ + const struct lttng_event_expr *ret = NULL; + + if (!expr || expr->type != LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT) { + goto end; + } + + ret = container_of(expr, + const struct lttng_event_expr_array_field_element, + parent)->array_field_expr; + +end: + return ret; +} + +enum lttng_event_expr_status lttng_event_expr_array_field_element_get_index( + const struct lttng_event_expr *expr, unsigned int *index) +{ + enum lttng_event_expr_status ret = LTTNG_EVENT_EXPR_STATUS_OK; + + if (!expr || expr->type != LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT || + !index) { + ret = LTTNG_EVENT_EXPR_STATUS_INVALID; + goto end; + } + + *index = container_of(expr, + const struct lttng_event_expr_array_field_element, + parent)->index; + +end: + return ret; +} + +bool lttng_event_expr_is_equal(const struct lttng_event_expr *expr_a, + const struct lttng_event_expr *expr_b) +{ + bool is_equal = true; + + if (!expr_a && !expr_b) { + /* Both `NULL`: equal */ + goto end; + } + + if (!expr_a || !expr_b) { + /* Only one `NULL`: not equal */ + goto not_equal; + } + + if (expr_a->type != expr_b->type) { + /* Different types: not equal */ + goto not_equal; + } + + switch (expr_a->type) { + case LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD: + case LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD: + { + const struct lttng_event_expr_field *field_expr_a = + container_of(expr_a, + const struct lttng_event_expr_field, + parent); + const struct lttng_event_expr_field *field_expr_b = + container_of(expr_b, + const struct lttng_event_expr_field, + parent); + + if (strcmp(field_expr_a->name, field_expr_b->name) != 0) { + goto not_equal; + } + + break; + } + case LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD: + { + const struct lttng_event_expr_app_specific_context_field *field_expr_a = + container_of(expr_a, + const struct lttng_event_expr_app_specific_context_field, + parent); + const struct lttng_event_expr_app_specific_context_field *field_expr_b = + container_of(expr_b, + const struct lttng_event_expr_app_specific_context_field, + parent); + + if (strcmp(field_expr_a->provider_name, + field_expr_b->provider_name) != 0) { + goto not_equal; + } + + if (strcmp(field_expr_a->type_name, + field_expr_b->type_name) != 0) { + goto not_equal; + } + + break; + } + case LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT: + { + const struct lttng_event_expr_array_field_element *elem_expr_a = + container_of(expr_a, + const struct lttng_event_expr_array_field_element, + parent); + const struct lttng_event_expr_array_field_element *elem_expr_b = + container_of(expr_b, + const struct lttng_event_expr_array_field_element, + parent); + + if (!lttng_event_expr_is_equal(elem_expr_a->array_field_expr, + elem_expr_b->array_field_expr)) { + goto not_equal; + } + + if (elem_expr_a->index != elem_expr_b->index) { + goto not_equal; + } + + break; + } + default: + break; + } + + goto end; + +not_equal: + is_equal = false; + +end: + return is_equal; +} + +static +void event_expr_destroy_ref(struct urcu_ref *ref) +{ + struct lttng_event_expr *expr = + container_of(ref, struct lttng_event_expr, ref); + + switch (expr->type) { + case LTTNG_EVENT_EXPR_TYPE_EVENT_PAYLOAD_FIELD: + case LTTNG_EVENT_EXPR_TYPE_CHANNEL_CONTEXT_FIELD: + { + struct lttng_event_expr_field *field_expr = + container_of(expr, + struct lttng_event_expr_field, parent); + + free(field_expr->name); + break; + } + case LTTNG_EVENT_EXPR_TYPE_APP_SPECIFIC_CONTEXT_FIELD: + { + struct lttng_event_expr_app_specific_context_field *field_expr = + container_of(expr, + struct lttng_event_expr_app_specific_context_field, + parent); + + free(field_expr->provider_name); + free(field_expr->type_name); + break; + } + case LTTNG_EVENT_EXPR_TYPE_ARRAY_FIELD_ELEMENT: + { + struct lttng_event_expr_array_field_element *elem_expr = + container_of(expr, + struct lttng_event_expr_array_field_element, + parent); + + lttng_event_expr_destroy(elem_expr->array_field_expr); + break; + } + default: + break; + } + + free(expr); +} + +LTTNG_HIDDEN +void lttng_event_expr_get(struct lttng_event_expr *expr) +{ + urcu_ref_get(&expr->ref); +} + +LTTNG_HIDDEN +void lttng_event_expr_put(struct lttng_event_expr *expr) { + if(!expr) { + return; + } + urcu_ref_put(&expr->ref, event_expr_destroy_ref); +} + +void lttng_event_expr_destroy(struct lttng_event_expr *expr) +{ + lttng_event_expr_put(expr); + return; +} diff --git a/src/common/event-field-value.c b/src/common/event-field-value.c new file mode 100644 index 000000000..de524cc8c --- /dev/null +++ b/src/common/event-field-value.c @@ -0,0 +1,592 @@ +/* + * event-field-value.c + * + * Linux Trace Toolkit Control Library + * + * Copyright (C) 2020 Philippe Proulx + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include + +#include +#include +#include + +static +struct lttng_event_field_value *create_empty_field_val( + enum lttng_event_field_value_type type, size_t size) +{ + struct lttng_event_field_value *field_val; + + field_val = zmalloc(size); + if (!field_val) { + goto end; + } + + field_val->type = type; + +end: + return field_val; +} + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_uint_create( + uint64_t val) +{ + struct lttng_event_field_value_uint *field_val; + + field_val = container_of(create_empty_field_val( + LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT, + sizeof(*field_val)), + struct lttng_event_field_value_uint, parent); + if (!field_val) { + goto error; + } + + field_val->val = val; + goto end; + +error: + lttng_event_field_value_destroy(&field_val->parent); + +end: + return &field_val->parent; +} + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_int_create( + int64_t val) +{ + struct lttng_event_field_value_int *field_val; + + field_val = container_of(create_empty_field_val( + LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_INT, + sizeof(*field_val)), + struct lttng_event_field_value_int, parent); + if (!field_val) { + goto error; + } + + field_val->val = val; + goto end; + +error: + lttng_event_field_value_destroy(&field_val->parent); + +end: + return &field_val->parent; +} + +static +struct lttng_event_field_value_enum *create_enum_field_val( + enum lttng_event_field_value_type type, size_t size) +{ + struct lttng_event_field_value_enum *field_val; + + field_val = container_of(create_empty_field_val(type, size), + struct lttng_event_field_value_enum, parent); + if (!field_val) { + goto error; + } + + lttng_dynamic_pointer_array_init(&field_val->labels, free); + goto end; + +error: + lttng_event_field_value_destroy(&field_val->parent); + +end: + return field_val; +} + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_enum_uint_create( + uint64_t val) +{ + struct lttng_event_field_value_enum_uint *field_val; + + field_val = container_of(create_enum_field_val( + LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM, + sizeof(*field_val)), + struct lttng_event_field_value_enum_uint, parent); + if (!field_val) { + goto error; + } + + field_val->val = val; + goto end; + +error: + lttng_event_field_value_destroy(&field_val->parent.parent); + +end: + return &field_val->parent.parent; +} + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_enum_int_create( + int64_t val) +{ + struct lttng_event_field_value_enum_int *field_val; + + field_val = container_of(create_enum_field_val( + LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM, + sizeof(*field_val)), + struct lttng_event_field_value_enum_int, parent); + if (!field_val) { + goto error; + } + + field_val->val = val; + goto end; + +error: + lttng_event_field_value_destroy(&field_val->parent.parent); + +end: + return &field_val->parent.parent; +} + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_real_create(double val) +{ + struct lttng_event_field_value_real *field_val = container_of( + create_empty_field_val( + LTTNG_EVENT_FIELD_VALUE_TYPE_REAL, + sizeof(*field_val)), + struct lttng_event_field_value_real, parent); + + if (!field_val) { + goto error; + } + + field_val->val = val; + goto end; + +error: + lttng_event_field_value_destroy(&field_val->parent); + +end: + return &field_val->parent; +} + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_string_create_with_size( + const char *val, size_t size) +{ + struct lttng_event_field_value_string *field_val = container_of( + create_empty_field_val( + LTTNG_EVENT_FIELD_VALUE_TYPE_STRING, + sizeof(*field_val)), + struct lttng_event_field_value_string, parent); + + if (!field_val) { + goto error; + } + + assert(val); + field_val->val = strndup(val, size); + if (!field_val->val) { + goto error; + } + + goto end; + +error: + lttng_event_field_value_destroy(&field_val->parent); + +end: + return &field_val->parent; +} + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_string_create( + const char *val) +{ + assert(val); + return lttng_event_field_value_string_create_with_size(val, + strlen(val)); +} + +static +void destroy_field_val(void *field_val) +{ + lttng_event_field_value_destroy(field_val); +} + +LTTNG_HIDDEN +struct lttng_event_field_value *lttng_event_field_value_array_create(void) +{ + struct lttng_event_field_value_array *field_val = container_of( + create_empty_field_val( + LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY, + sizeof(*field_val)), + struct lttng_event_field_value_array, parent); + + if (!field_val) { + goto error; + } + + lttng_dynamic_pointer_array_init(&field_val->elems, destroy_field_val); + goto end; + +error: + lttng_event_field_value_destroy(&field_val->parent); + +end: + return &field_val->parent; +} + +LTTNG_HIDDEN +void lttng_event_field_value_destroy(struct lttng_event_field_value *field_val) +{ + if (!field_val) { + goto end; + } + + switch (field_val->type) { + case LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM: + case LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM: + { + struct lttng_event_field_value_enum *enum_field_val = + container_of(field_val, + struct lttng_event_field_value_enum, parent); + + lttng_dynamic_pointer_array_reset(&enum_field_val->labels); + break; + } + case LTTNG_EVENT_FIELD_VALUE_TYPE_STRING: + { + struct lttng_event_field_value_string *str_field_val = + container_of(field_val, + struct lttng_event_field_value_string, parent); + + free(str_field_val->val); + break; + } + case LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY: + { + struct lttng_event_field_value_array *array_field_expr = + container_of(field_val, + struct lttng_event_field_value_array, + parent); + + lttng_dynamic_pointer_array_reset(&array_field_expr->elems); + break; + } + default: + break; + } + + free(field_val); + +end: + return; +} + +LTTNG_HIDDEN +int lttng_event_field_value_enum_append_label_with_size( + struct lttng_event_field_value *field_val, + const char *label, size_t size) +{ + int ret; + char *mein_label; + + assert(field_val); + assert(label); + mein_label = strndup(label, size); + if (!mein_label) { + ret = -1; + goto end; + } + + ret = lttng_dynamic_pointer_array_add_pointer( + &container_of(field_val, + struct lttng_event_field_value_enum, parent)->labels, + mein_label); + if (ret == 0) { + mein_label = NULL; + } + +end: + free(mein_label); + return ret; +} + +LTTNG_HIDDEN +int lttng_event_field_value_enum_append_label( + struct lttng_event_field_value *field_val, + const char *label) +{ + assert(label); + return lttng_event_field_value_enum_append_label_with_size(field_val, + label, strlen(label)); +} + +LTTNG_HIDDEN +int lttng_event_field_value_array_append( + struct lttng_event_field_value *array_field_val, + struct lttng_event_field_value *field_val) +{ + assert(array_field_val); + assert(field_val); + return lttng_dynamic_pointer_array_add_pointer( + &container_of(array_field_val, + struct lttng_event_field_value_array, parent)->elems, + field_val); +} + +LTTNG_HIDDEN +int lttng_event_field_value_array_append_unavailable( + struct lttng_event_field_value *array_field_val) +{ + assert(array_field_val); + return lttng_dynamic_pointer_array_add_pointer( + &container_of(array_field_val, + struct lttng_event_field_value_array, parent)->elems, + NULL); +} + +enum lttng_event_field_value_type lttng_event_field_value_get_type( + const struct lttng_event_field_value *field_val) +{ + enum lttng_event_field_value_type type; + + if (!field_val) { + type = LTTNG_EVENT_FIELD_VALUE_TYPE_INVALID; + goto end; + } + + type = field_val->type; + +end: + return type; +} + +enum lttng_event_field_value_status +lttng_event_field_value_unsigned_int_get_value( + const struct lttng_event_field_value *field_val, uint64_t *val) +{ + enum lttng_event_field_value_status status; + + if (!field_val || !val) { + status = LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID; + goto end; + } + + switch (field_val->type) { + case LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT: + *val = container_of(field_val, + const struct lttng_event_field_value_uint, + parent)->val; + break; + case LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM: + *val = container_of( + container_of(field_val, + const struct lttng_event_field_value_enum, + parent), + const struct lttng_event_field_value_enum_uint, + parent)->val; + break; + default: + status = LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID; + goto end; + } + + status = LTTNG_EVENT_FIELD_VALUE_STATUS_OK; + +end: + return status; +} + +enum lttng_event_field_value_status +lttng_event_field_value_signed_int_get_value( + const struct lttng_event_field_value *field_val, int64_t *val) +{ + enum lttng_event_field_value_status status; + + if (!field_val || !val) { + status = LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID; + goto end; + } + + switch (field_val->type) { + case LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_INT: + *val = container_of(field_val, + const struct lttng_event_field_value_int, + parent)->val; + break; + case LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM: + *val = container_of( + container_of(field_val, + const struct lttng_event_field_value_enum, + parent), + const struct lttng_event_field_value_enum_int, + parent)->val; + break; + default: + status = LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID; + goto end; + } + + status = LTTNG_EVENT_FIELD_VALUE_STATUS_OK; + +end: + return status; +} + +enum lttng_event_field_value_status +lttng_event_field_value_real_get_value( + const struct lttng_event_field_value *field_val, double *val) +{ + enum lttng_event_field_value_status status; + + if (!field_val || field_val->type != LTTNG_EVENT_FIELD_VALUE_TYPE_REAL || + !val) { + status = LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID; + goto end; + } + + *val = container_of(field_val, + const struct lttng_event_field_value_real, parent)->val; + status = LTTNG_EVENT_FIELD_VALUE_STATUS_OK; + +end: + return status; +} + +static +bool ist_enum_field_val(const struct lttng_event_field_value *field_val) +{ + return field_val->type == LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM || + field_val->type == LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM; +} + +enum lttng_event_field_value_status +lttng_event_field_value_enum_get_label_count( + const struct lttng_event_field_value *field_val, + unsigned int *count) +{ + enum lttng_event_field_value_status status; + + if (!field_val || !ist_enum_field_val(field_val) || !count) { + status = LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID; + goto end; + } + + *count = (unsigned int) lttng_dynamic_pointer_array_get_count( + &container_of(field_val, + const struct lttng_event_field_value_enum, + parent)->labels); + status = LTTNG_EVENT_FIELD_VALUE_STATUS_OK; + +end: + return status; +} + +const char *lttng_event_field_value_enum_get_label_at_index( + const struct lttng_event_field_value *field_val, + unsigned int index) +{ + const char *ret; + const struct lttng_event_field_value_enum *enum_field_val; + + if (!field_val || !ist_enum_field_val(field_val)) { + ret = NULL; + goto end; + } + + enum_field_val = container_of(field_val, + const struct lttng_event_field_value_enum, parent); + + if (index >= lttng_dynamic_pointer_array_get_count(&enum_field_val->labels)) { + ret = NULL; + goto end; + } + + ret = lttng_dynamic_pointer_array_get_pointer(&enum_field_val->labels, + index); + +end: + return ret; +} + +const char *lttng_event_field_value_string_get_value( + const struct lttng_event_field_value *field_val) +{ + const char *ret; + + if (!field_val || field_val->type != LTTNG_EVENT_FIELD_VALUE_TYPE_STRING) { + ret = NULL; + goto end; + } + + ret = container_of(field_val, + const struct lttng_event_field_value_string, parent)->val; + +end: + return ret; +} + +enum lttng_event_field_value_status lttng_event_field_value_array_get_length( + const struct lttng_event_field_value *field_val, + unsigned int *length) +{ + enum lttng_event_field_value_status status; + + if (!field_val || field_val->type != LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY || + !length) { + status = LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID; + goto end; + } + + *length = (unsigned int) lttng_dynamic_pointer_array_get_count( + &container_of(field_val, + const struct lttng_event_field_value_array, + parent)->elems); + status = LTTNG_EVENT_FIELD_VALUE_STATUS_OK; + +end: + return status; +} + +enum lttng_event_field_value_status +lttng_event_field_value_array_get_element_at_index( + const struct lttng_event_field_value *field_val, + unsigned int index, + const struct lttng_event_field_value **elem_field_val) +{ + enum lttng_event_field_value_status status; + const struct lttng_event_field_value_array *array_field_val; + + if (!field_val || field_val->type != LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY || + !elem_field_val) { + status = LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID; + goto end; + } + + array_field_val = container_of(field_val, + const struct lttng_event_field_value_array, parent); + + if (index >= lttng_dynamic_pointer_array_get_count(&array_field_val->elems)) { + status = LTTNG_EVENT_FIELD_VALUE_STATUS_INVALID; + goto end; + } + + *elem_field_val = lttng_dynamic_pointer_array_get_pointer( + &array_field_val->elems, index); + if (*elem_field_val) { + status = LTTNG_EVENT_FIELD_VALUE_STATUS_OK; + } else { + status = LTTNG_EVENT_FIELD_VALUE_STATUS_UNAVAILABLE; + } + +end: + return status; +} diff --git a/src/common/event-rule/event-rule.c b/src/common/event-rule/event-rule.c new file mode 100644 index 000000000..1e4d83c3d --- /dev/null +++ b/src/common/event-rule/event-rule.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum lttng_event_rule_type lttng_event_rule_get_type( + const struct lttng_event_rule *event_rule) +{ + return event_rule ? event_rule->type : LTTNG_EVENT_RULE_TYPE_UNKNOWN; +} + +LTTNG_HIDDEN +enum lttng_domain_type lttng_event_rule_get_domain_type( + const struct lttng_event_rule *event_rule) +{ + enum lttng_domain_type domain_type; + + switch (lttng_event_rule_get_type(event_rule)) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + (void) lttng_event_rule_tracepoint_get_domain_type(event_rule, &domain_type); + break; + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + case LTTNG_EVENT_RULE_TYPE_KPROBE: + case LTTNG_EVENT_RULE_TYPE_KRETPROBE: + case LTTNG_EVENT_RULE_TYPE_UPROBE: + domain_type = LTTNG_DOMAIN_KERNEL; + break; + case LTTNG_EVENT_RULE_TYPE_UNKNOWN: + domain_type = LTTNG_DOMAIN_NONE; + break; + } + + return domain_type; +} + +static void lttng_event_rule_release(struct urcu_ref *ref) +{ + struct lttng_event_rule *event_rule = + container_of(ref, typeof(*event_rule), ref); + + assert(event_rule->destroy); + event_rule->destroy(event_rule); +} + +void lttng_event_rule_destroy(struct lttng_event_rule *event_rule) +{ + lttng_event_rule_put(event_rule); +} + +LTTNG_HIDDEN +bool lttng_event_rule_validate(const struct lttng_event_rule *event_rule) +{ + bool valid; + + if (!event_rule) { + valid = false; + goto end; + } + + if (!event_rule->validate) { + /* Sub-class guarantees that it can never be invalid. */ + valid = true; + goto end; + } + + valid = event_rule->validate(event_rule); +end: + return valid; +} + +LTTNG_HIDDEN +int lttng_event_rule_serialize(const struct lttng_event_rule *event_rule, + struct lttng_dynamic_buffer *buf, + int *fd_to_send) +{ + int ret; + struct lttng_event_rule_comm event_rule_comm = {0}; + + if (!event_rule) { + ret = -1; + goto end; + } + + event_rule_comm.event_rule_type = (int8_t) event_rule->type; + + ret = lttng_dynamic_buffer_append( + buf, &event_rule_comm, sizeof(event_rule_comm)); + if (ret) { + goto end; + } + + ret = event_rule->serialize(event_rule, buf, fd_to_send); + if (ret) { + goto end; + } +end: + return ret; +} + +LTTNG_HIDDEN +bool lttng_event_rule_is_equal(const struct lttng_event_rule *a, + const struct lttng_event_rule *b) +{ + bool is_equal = false; + + if (!a || !b) { + goto end; + } + + if (a->type != b->type) { + goto end; + } + + if (a == b) { + is_equal = true; + goto end; + } + + is_equal = a->equal ? a->equal(a, b) : true; +end: + return is_equal; +} + +LTTNG_HIDDEN +ssize_t lttng_event_rule_create_from_buffer( + const struct lttng_buffer_view *buffer, + struct lttng_event_rule **event_rule) +{ + ssize_t ret, event_rule_size = 0; + const struct lttng_event_rule_comm *event_rule_comm; + event_rule_create_from_buffer_cb create_from_buffer = NULL; + + if (!buffer || !event_rule) { + ret = -1; + goto end; + } + + DBG("Deserializing event_rule from buffer"); + event_rule_comm = (const struct lttng_event_rule_comm *) buffer->data; + event_rule_size += sizeof(*event_rule_comm); + + switch ((enum lttng_event_rule_type) event_rule_comm->event_rule_type) { + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + create_from_buffer = + lttng_event_rule_tracepoint_create_from_buffer; + break; + case LTTNG_EVENT_RULE_TYPE_KPROBE: + create_from_buffer = lttng_event_rule_kprobe_create_from_buffer; + break; + case LTTNG_EVENT_RULE_TYPE_KRETPROBE: + create_from_buffer = + lttng_event_rule_kretprobe_create_from_buffer; + break; + case LTTNG_EVENT_RULE_TYPE_UPROBE: + create_from_buffer = lttng_event_rule_uprobe_create_from_buffer; + break; + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + create_from_buffer = + lttng_event_rule_syscall_create_from_buffer; + break; + default: + ERR("Attempted to create event rule of unknown type (%i)", + (int) event_rule_comm->event_rule_type); + ret = -1; + goto end; + } + + if (create_from_buffer) { + const struct lttng_buffer_view view = + lttng_buffer_view_from_view(buffer, + sizeof(*event_rule_comm), -1); + + ret = create_from_buffer(&view, event_rule); + if (ret < 0) { + goto end; + } + event_rule_size += ret; + + } else { + abort(); + } + + ret = event_rule_size; +end: + return ret; +} + +LTTNG_HIDDEN +void lttng_event_rule_init(struct lttng_event_rule *event_rule, + enum lttng_event_rule_type type) +{ + urcu_ref_init(&event_rule->ref); + event_rule->type = type; +} + +LTTNG_HIDDEN +bool lttng_event_rule_get(struct lttng_event_rule *event_rule) +{ + return urcu_ref_get_unless_zero(&event_rule->ref); +} + +LTTNG_HIDDEN +void lttng_event_rule_put(struct lttng_event_rule *event_rule) +{ + if (!event_rule) { + return; + } + assert(event_rule->ref.refcount); + urcu_ref_put(&event_rule->ref, lttng_event_rule_release); +} + +LTTNG_HIDDEN +enum lttng_error_code lttng_event_rule_populate( + struct lttng_event_rule *rule, uid_t uid, gid_t gid) +{ + assert(rule->populate); + return rule->populate(rule, uid, gid); +} + +/* If not present return NULL + * Caller DO NOT own the returned object + */ +LTTNG_HIDDEN +const char *lttng_event_rule_get_filter(const struct lttng_event_rule *rule) +{ + assert(rule->get_filter); + return rule->get_filter(rule); +} + +/* If not present return NULL + * Caller DO NOT own the returned object + */ +LTTNG_HIDDEN +const struct lttng_bytecode *lttng_event_rule_get_filter_bytecode( + const struct lttng_event_rule *rule) +{ + assert(rule->get_filter_bytecode); + return rule->get_filter_bytecode(rule); +} + +/* + * If not present return NULL + * Caller OWN the returned object + * TODO: should this be done another way? + */ +LTTNG_HIDDEN +struct lttng_event_exclusion *lttng_event_rule_generate_exclusions( + struct lttng_event_rule *rule) +{ + assert(rule->generate_exclusions); + return rule->generate_exclusions(rule); +} + +LTTNG_HIDDEN +struct lttng_event *lttng_event_rule_generate_lttng_event( + const struct lttng_event_rule *rule) +{ + assert(rule->generate_lttng_event); + return rule->generate_lttng_event(rule); +} + +LTTNG_HIDDEN +bool lttng_event_rule_is_agent(const struct lttng_event_rule *rule) +{ + bool ret = false; + enum lttng_domain_type type = lttng_event_rule_get_domain_type(rule); + + switch (type) { + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + ret = true; + break; + case LTTNG_DOMAIN_UST: + case LTTNG_DOMAIN_KERNEL: + ret = false; + break; + default: + assert(0); + }; + + return ret; +} + +const char *lttng_event_rule_type_str(enum lttng_event_rule_type type) +{ + switch (type) { + case LTTNG_EVENT_RULE_TYPE_UNKNOWN: + return "unknown"; + + case LTTNG_EVENT_RULE_TYPE_TRACEPOINT: + return "tracepoint"; + + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + return "syscall"; + + case LTTNG_EVENT_RULE_TYPE_KPROBE: + return "probe"; + + case LTTNG_EVENT_RULE_TYPE_KRETPROBE: + return "function"; + + case LTTNG_EVENT_RULE_TYPE_UPROBE: + return "userspace-probe"; + + default: + abort(); + } +} diff --git a/src/common/event-rule/kprobe.c b/src/common/event-rule/kprobe.c new file mode 100644 index 000000000..8016424fd --- /dev/null +++ b/src/common/event-rule/kprobe.c @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IS_KPROBE_EVENT_RULE(rule) \ + (lttng_event_rule_get_type(rule) == LTTNG_EVENT_RULE_TYPE_KPROBE) + +#if (LTTNG_SYMBOL_NAME_LEN == 256) +#define LTTNG_SYMBOL_NAME_LEN_SCANF_IS_A_BROKEN_API "255" +#endif + +static void lttng_event_rule_kprobe_destroy(struct lttng_event_rule *rule) +{ + struct lttng_event_rule_kprobe *kprobe; + + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + + free(kprobe->name); + free(kprobe->probe.symbol_name); + free(kprobe); +} + +static bool lttng_event_rule_kprobe_validate( + const struct lttng_event_rule *rule) +{ + bool valid = false; + struct lttng_event_rule_kprobe *kprobe; + + if (!rule) { + goto end; + } + + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + + /* Required field */ + if (!kprobe->name) { + ERR("Invalid name event rule: a name must be set."); + goto end; + } + if (kprobe->probe.set == LTTNG_DOMAIN_NONE) { + ERR("Invalid kprobe event rule: a source must be set."); + goto end; + } + + valid = true; +end: + return valid; +} + +static int lttng_event_rule_kprobe_serialize( + const struct lttng_event_rule *rule, + struct lttng_dynamic_buffer *buf, + int *fd_to_send) +{ + int ret; + size_t name_len, probe_symbol_name_len; + struct lttng_event_rule_kprobe *kprobe; + struct lttng_event_rule_kprobe_comm kprobe_comm; + + if (!rule || !IS_KPROBE_EVENT_RULE(rule)) { + ret = -1; + goto end; + } + + DBG("Serializing kprobe event rule"); + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + + name_len = strlen(kprobe->name) + 1; + + if (kprobe->probe.symbol_name != NULL) { + probe_symbol_name_len = strlen(kprobe->probe.symbol_name) + 1; + } else { + probe_symbol_name_len = 0; + } + + kprobe_comm.name_len = name_len; + kprobe_comm.probe_symbol_name_len = probe_symbol_name_len; + kprobe_comm.probe_address = kprobe->probe.address; + kprobe_comm.probe_offset = kprobe->probe.offset; + + ret = lttng_dynamic_buffer_append( + buf, &kprobe_comm, sizeof(kprobe_comm)); + if (ret) { + goto end; + } + ret = lttng_dynamic_buffer_append(buf, kprobe->name, name_len); + if (ret) { + goto end; + } + ret = lttng_dynamic_buffer_append( + buf, kprobe->probe.symbol_name, probe_symbol_name_len); + if (ret) { + goto end; + } + + if (fd_to_send) { + /* Nothing to send */ + *fd_to_send = -1; + } +end: + return ret; +} + +static bool lttng_event_rule_kprobe_is_equal(const struct lttng_event_rule *_a, + const struct lttng_event_rule *_b) +{ + bool is_equal = false; + struct lttng_event_rule_kprobe *a, *b; + + a = container_of(_a, struct lttng_event_rule_kprobe, parent); + b = container_of(_b, struct lttng_event_rule_kprobe, parent); + + /* Quick checks */ + if (!!a->name != !!b->name) { + goto end; + } + + if (!!a->probe.symbol_name != !!b->probe.symbol_name) { + goto end; + } + + /* Long check */ + /* kprobe is invalid if this is not true */ + /* TODO: validate that a kprobe MUST have a name */ + assert(a->name); + assert(b->name); + if (strcmp(a->name, b->name)) { + goto end; + } + + if (a->probe.symbol_name) { + /* Both have symbol name due to previous checks */ + if (strcmp(a->probe.symbol_name, b->probe.symbol_name)) { + goto end; + } + } + + if (a->probe.offset != b->probe.offset) { + goto end; + } + + if (a->probe.address != b->probe.address) { + goto end; + } + + is_equal = true; +end: + return is_equal; +} + +static enum lttng_error_code lttng_event_rule_kprobe_populate( + struct lttng_event_rule *rule, uid_t uid, gid_t gid) +{ + /* Nothing to do */ + return LTTNG_OK; +} + +static const char *lttng_event_rule_kprobe_get_filter( + const struct lttng_event_rule *rule) +{ + /* Not supported */ + return NULL; +} + +static const struct lttng_bytecode * +lttng_event_rule_kprobe_get_filter_bytecode(const struct lttng_event_rule *rule) +{ + /* Not supported */ + return NULL; +} + +static struct lttng_event_exclusion * +lttng_event_rule_kprobe_generate_exclusions(struct lttng_event_rule *rule) +{ + /* Not supported */ + return NULL; +} + +struct lttng_event_rule *lttng_event_rule_kprobe_create() +{ + struct lttng_event_rule_kprobe *rule; + + rule = zmalloc(sizeof(struct lttng_event_rule_kprobe)); + if (!rule) { + return NULL; + } + + lttng_event_rule_init(&rule->parent, LTTNG_EVENT_RULE_TYPE_KPROBE); + rule->parent.validate = lttng_event_rule_kprobe_validate; + rule->parent.serialize = lttng_event_rule_kprobe_serialize; + rule->parent.equal = lttng_event_rule_kprobe_is_equal; + rule->parent.destroy = lttng_event_rule_kprobe_destroy; + rule->parent.populate = lttng_event_rule_kprobe_populate; + rule->parent.get_filter = lttng_event_rule_kprobe_get_filter; + rule->parent.get_filter_bytecode = + lttng_event_rule_kprobe_get_filter_bytecode; + rule->parent.generate_exclusions = + lttng_event_rule_kprobe_generate_exclusions; + return &rule->parent; +} + +LTTNG_HIDDEN +ssize_t lttng_event_rule_kprobe_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **_event_rule) +{ + ssize_t ret, offset = 0; + enum lttng_event_rule_status status; + const struct lttng_event_rule_kprobe_comm *kprobe_comm; + const char *name; + const char *probe_symbol_name = NULL; + struct lttng_buffer_view current_view; + struct lttng_event_rule *rule = NULL; + struct lttng_event_rule_kprobe *kprobe = NULL; + + if (!_event_rule) { + ret = -1; + goto end; + } + + if (view->size < sizeof(*kprobe_comm)) { + ERR("Failed to initialize from malformed event rule kprobe: buffer too short to contain header"); + ret = -1; + goto end; + } + + current_view = lttng_buffer_view_from_view( + view, offset, sizeof(*kprobe_comm)); + kprobe_comm = (typeof(kprobe_comm)) current_view.data; + if (!kprobe_comm) { + ret = -1; + goto end; + } + + rule = lttng_event_rule_kprobe_create(); + if (!rule) { + ERR("Failed to create event rule kprobe"); + ret = -1; + goto end; + } + + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + + /* Skip to payload */ + offset += current_view.size; + /* Map the name */ + current_view = lttng_buffer_view_from_view( + view, offset, kprobe_comm->name_len); + name = current_view.data; + if (!name) { + ret = -1; + goto end; + } + + if (kprobe_comm->name_len == 1 || + name[kprobe_comm->name_len - 1] != '\0' || + strlen(name) != kprobe_comm->name_len - 1) { + /* + * Check that the name is not NULL, is NULL-terminated, and + * does not contain a NULL before the last byte. + */ + ret = -1; + goto end; + } + + /* Skip after the name */ + offset += kprobe_comm->name_len; + if (!kprobe_comm->probe_symbol_name_len) { + goto skip_probe_symbol_name; + } + + /* Map the probe_symbol_name */ + current_view = lttng_buffer_view_from_view( + view, offset, kprobe_comm->probe_symbol_name_len); + probe_symbol_name = current_view.data; + if (!probe_symbol_name) { + ret = -1; + goto end; + } + + if (kprobe_comm->probe_symbol_name_len == 1 || + probe_symbol_name[kprobe_comm->probe_symbol_name_len - + 1] != '\0' || + strlen(probe_symbol_name) != + kprobe_comm->probe_symbol_name_len - + 1) { + /* + * Check that the filter expression is not NULL, is + * NULL-terminated, and does not contain a NULL before the last + * byte. + */ + ret = -1; + goto end; + } + + /* Skip after the pattern */ + offset += kprobe_comm->probe_symbol_name_len; + +skip_probe_symbol_name: + + status = lttng_event_rule_kprobe_set_name(rule, name); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set event rule kprobe name"); + ret = -1; + goto end; + } + kprobe->probe.offset = kprobe_comm->probe_offset; + kprobe->probe.address = kprobe_comm->probe_address; + if (probe_symbol_name) { + kprobe->probe.symbol_name = strdup(probe_symbol_name); + if (!kprobe->probe.symbol_name) { + ERR("Failed to set event rule kprobe probe symbol name"); + ret = -1; + goto end; + } + } + + *_event_rule = rule; + rule = NULL; + ret = offset; +end: + lttng_event_rule_destroy(rule); + return ret; +} + +enum lttng_event_rule_status lttng_event_rule_kprobe_set_source( + struct lttng_event_rule *rule, const char *source) +{ + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + int match; + char s_hex[19]; + char name[LTTNG_SYMBOL_NAME_LEN]; + struct lttng_event_rule_kprobe *kprobe; + + /* TODO: support multiple call for this, we must free the symbol name if + * that happens !!! + */ + + if (!source || !IS_KPROBE_EVENT_RULE(rule) || !rule) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + + /* Check for symbol+offset */ + match = sscanf(source, "%18s[^'+']+%18s", name, s_hex); + if (match == 2) { + /* TODO double validate termination handling of this */ + kprobe->probe.symbol_name = + strndup(name, LTTNG_SYMBOL_NAME_LEN); + if (!kprobe->probe.symbol_name) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + if (*s_hex == '\0') { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + kprobe->probe.offset = strtoul(s_hex, NULL, 0); + kprobe->probe.address = 0; + kprobe->probe.set = true; + goto end; + } + + /* Check for symbol */ + if (isalpha(name[0]) || name[0] == '_') { + match = sscanf(source, + "%" LTTNG_SYMBOL_NAME_LEN_SCANF_IS_A_BROKEN_API + "s", + name); + if (match == 1) { + /* TODO double validate termination handling of this */ + kprobe->probe.symbol_name = + strndup(name, LTTNG_SYMBOL_NAME_LEN); + if (!kprobe->probe.symbol_name) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + kprobe->probe.offset = 0; + kprobe->probe.address = 0; + kprobe->probe.set = true; + goto end; + } + } + + /* Check for address */ + match = sscanf(source, "%18s", s_hex); + if (match > 0) { + if (*s_hex == '\0') { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + kprobe->probe.address = strtoul(s_hex, NULL, 0); + kprobe->probe.offset = 0; + kprobe->probe.symbol_name = NULL; + kprobe->probe.set = true; + goto end; + } + + /* No match */ + status = LTTNG_EVENT_RULE_STATUS_INVALID; + +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_kprobe_set_name( + struct lttng_event_rule *rule, const char *name) +{ + char *name_copy = NULL; + struct lttng_event_rule_kprobe *kprobe; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_KPROBE_EVENT_RULE(rule) || !name || + strlen(name) == 0) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + name_copy = strdup(name); + if (!name_copy) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + + if (kprobe->name) { + free(kprobe->name); + } + + kprobe->name = name_copy; + name_copy = NULL; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_kprobe_get_name( + const struct lttng_event_rule *rule, const char **name) +{ + struct lttng_event_rule_kprobe *kprobe; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_KPROBE_EVENT_RULE(rule) || !name) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + if (!kprobe->name) { + status = LTTNG_EVENT_RULE_STATUS_UNSET; + goto end; + } + + *name = kprobe->name; +end: + return status; +} + +LTTNG_HIDDEN +uint64_t lttng_event_rule_kprobe_get_address( + const struct lttng_event_rule *rule) +{ + struct lttng_event_rule_kprobe *kprobe; + + assert(rule && IS_KPROBE_EVENT_RULE(rule)); + + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + + return kprobe->probe.address; +} + +LTTNG_HIDDEN +uint64_t lttng_event_rule_kprobe_get_offset(const struct lttng_event_rule *rule) +{ + struct lttng_event_rule_kprobe *kprobe; + + assert(rule && IS_KPROBE_EVENT_RULE(rule)); + + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + return kprobe->probe.offset; +} + +LTTNG_HIDDEN +const char *lttng_event_rule_kprobe_get_symbol_name( + const struct lttng_event_rule *rule) +{ + struct lttng_event_rule_kprobe *kprobe; + + assert(rule && IS_KPROBE_EVENT_RULE(rule)); + + kprobe = container_of(rule, struct lttng_event_rule_kprobe, parent); + return kprobe->probe.symbol_name; +} diff --git a/src/common/event-rule/kretprobe.c b/src/common/event-rule/kretprobe.c new file mode 100644 index 000000000..9a77a82c7 --- /dev/null +++ b/src/common/event-rule/kretprobe.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include + +#define IS_KRETPROBE_EVENT_RULE(rule) \ + (lttng_event_rule_get_type(rule) == LTTNG_EVENT_RULE_TYPE_KRETPROBE) + +static void lttng_event_rule_kretprobe_destroy(struct lttng_event_rule *rule) +{ + struct lttng_event_rule_kretprobe *kretprobe; + + kretprobe = container_of( + rule, struct lttng_event_rule_kretprobe, parent); + + /* + * TODO + */ + free(kretprobe); +} + +static bool lttng_event_rule_kretprobe_validate( + const struct lttng_event_rule *rule) +{ + /* TODO */ + return false; +} + +static int lttng_event_rule_kretprobe_serialize( + const struct lttng_event_rule *rule, + struct lttng_dynamic_buffer *buf, + int *fd_to_send) +{ + /* TODO */ + return -1; +} + +static bool lttng_event_rule_kretprobe_is_equal( + const struct lttng_event_rule *_a, + const struct lttng_event_rule *_b) +{ + /* TODO */ + return false; +} + +static enum lttng_error_code lttng_event_rule_kretprobe_populate( + struct lttng_event_rule *rule, uid_t uid, gid_t gid) +{ + /* Nothing to do */ + return LTTNG_OK; +} + +static const char *lttng_event_rule_kretprobe_get_filter( + const struct lttng_event_rule *rule) +{ + /* Not supported */ + return NULL; +} + +static const struct lttng_bytecode * +lttng_event_rule_kretprobe_get_filter_bytecode( + const struct lttng_event_rule *rule) +{ + /* Not supported */ + return NULL; +} + +static struct lttng_event_exclusion * +lttng_event_rule_kretprobe_generate_exclusions(struct lttng_event_rule *rule) +{ + /* Not supported */ + return NULL; +} + +struct lttng_event_rule *lttng_event_rule_kretprobe_create() +{ + struct lttng_event_rule_kretprobe *rule; + + rule = zmalloc(sizeof(struct lttng_event_rule_kretprobe)); + if (!rule) { + return NULL; + } + + lttng_event_rule_init(&rule->parent, LTTNG_EVENT_RULE_TYPE_KRETPROBE); + rule->parent.validate = lttng_event_rule_kretprobe_validate; + rule->parent.serialize = lttng_event_rule_kretprobe_serialize; + rule->parent.equal = lttng_event_rule_kretprobe_is_equal; + rule->parent.destroy = lttng_event_rule_kretprobe_destroy; + rule->parent.populate = lttng_event_rule_kretprobe_populate; + rule->parent.get_filter = lttng_event_rule_kretprobe_get_filter; + rule->parent.get_filter_bytecode = + lttng_event_rule_kretprobe_get_filter_bytecode; + rule->parent.generate_exclusions = + lttng_event_rule_kretprobe_generate_exclusions; + return &rule->parent; +} + +LTTNG_HIDDEN +ssize_t lttng_event_rule_kretprobe_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **_event_rule) +{ + /* TODO */ + return -1; +} + +enum lttng_event_rule_status lttng_event_rule_kretprobe_set_source( + struct lttng_event_rule *rule, const char *source) +{ + return LTTNG_EVENT_RULE_STATUS_UNSUPPORTED; +} + +enum lttng_event_rule_status lttng_event_rule_kretprobe_set_name( + struct lttng_event_rule *rule, const char *name) +{ + return LTTNG_EVENT_RULE_STATUS_UNSUPPORTED; +} + +enum lttng_event_rule_status lttng_event_rule_kretprobe_get_name( + const struct lttng_event_rule *rule, const char **name) +{ + return LTTNG_EVENT_RULE_STATUS_UNSUPPORTED; +} + +LTTNG_HIDDEN +uint64_t lttng_event_rule_kretprobe_get_address( + const struct lttng_event_rule *rule) +{ + assert("Not implemented" && 0); +} + +LTTNG_HIDDEN +uint64_t lttng_event_rule_kretprobe_get_offset( + const struct lttng_event_rule *rule) +{ + assert("Not implemented" && 0); +} + +LTTNG_HIDDEN +const char *lttng_event_rule_kretprobe_get_symbol_name( + const struct lttng_event_rule *rule) +{ + assert("Not implemented" && 0); +} diff --git a/src/common/event-rule/syscall.c b/src/common/event-rule/syscall.c new file mode 100644 index 000000000..caef09d12 --- /dev/null +++ b/src/common/event-rule/syscall.c @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include + +#define IS_SYSCALL_EVENT_RULE(rule) \ + (lttng_event_rule_get_type(rule) == LTTNG_EVENT_RULE_TYPE_SYSCALL) + +static void lttng_event_rule_syscall_destroy(struct lttng_event_rule *rule) +{ + struct lttng_event_rule_syscall *syscall; + + if (rule == NULL) { + return; + } + + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + + free(syscall->pattern); + free(syscall->filter_expression); + free(syscall->internal_filter.filter); + free(syscall->internal_filter.bytecode); + free(syscall); +} + +static bool lttng_event_rule_syscall_validate( + const struct lttng_event_rule *rule) +{ + bool valid = false; + struct lttng_event_rule_syscall *syscall; + + if (!rule) { + goto end; + } + + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + + /* Required field */ + if (!syscall->pattern) { + ERR("Invalid syscall event rule: a pattern must be set."); + goto end; + } + + valid = true; +end: + return valid; +} + +static int lttng_event_rule_syscall_serialize( + const struct lttng_event_rule *rule, + struct lttng_dynamic_buffer *buf, + int *fd_to_send) +{ + int ret; + size_t pattern_len, filter_expression_len; + struct lttng_event_rule_syscall *syscall; + struct lttng_event_rule_syscall_comm syscall_comm; + + if (!rule || !IS_SYSCALL_EVENT_RULE(rule)) { + ret = -1; + goto end; + } + + DBG("Serializing syscall event rule"); + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + + pattern_len = strlen(syscall->pattern) + 1; + + if (syscall->filter_expression != NULL) { + filter_expression_len = strlen(syscall->filter_expression) + 1; + } else { + filter_expression_len = 0; + } + + syscall_comm.pattern_len = pattern_len; + syscall_comm.filter_expression_len = filter_expression_len; + + ret = lttng_dynamic_buffer_append( + buf, &syscall_comm, sizeof(syscall_comm)); + if (ret) { + goto end; + } + ret = lttng_dynamic_buffer_append(buf, syscall->pattern, pattern_len); + if (ret) { + goto end; + } + ret = lttng_dynamic_buffer_append( + buf, syscall->filter_expression, filter_expression_len); + if (ret) { + goto end; + } + + if (fd_to_send) { + /* Nothing to send */ + *fd_to_send = -1; + } +end: + return ret; +} + +static bool lttng_event_rule_syscall_is_equal(const struct lttng_event_rule *_a, + const struct lttng_event_rule *_b) +{ + bool is_equal = false; + struct lttng_event_rule_syscall *a, *b; + + a = container_of(_a, struct lttng_event_rule_syscall, parent); + b = container_of(_b, struct lttng_event_rule_syscall, parent); + + if (!!a->filter_expression != !!b->filter_expression) { + goto end; + } + + /* Long check */ + /* syscall is invalid if this is not true */ + assert(a->pattern); + assert(b->pattern); + if (strcmp(a->pattern, b->pattern)) { + goto end; + } + + if (a->filter_expression && b->filter_expression) { + if (strcmp(a->filter_expression, b->filter_expression)) { + goto end; + } + } + + is_equal = true; +end: + return is_equal; +} + +static enum lttng_error_code lttng_event_rule_syscall_populate( + struct lttng_event_rule *rule, uid_t uid, gid_t gid) +{ + int ret; + enum lttng_error_code ret_code = LTTNG_OK; + struct lttng_event_rule_syscall *syscall; + enum lttng_event_rule_status status; + const char *filter; + struct lttng_bytecode *bytecode = NULL; + + assert(rule); + + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + + /* Generate the filter bytecode */ + status = lttng_event_rule_syscall_get_filter(rule, &filter); + if (status == LTTNG_EVENT_RULE_STATUS_UNSET) { + filter = NULL; + } else if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret_code = LTTNG_ERR_FILTER_INVAL; + goto end; + } + + if (filter && filter[0] == '\0') { + ret_code = LTTNG_ERR_FILTER_INVAL; + goto error; + } + + if (filter == NULL) { + /* Nothing to do */ + ret = LTTNG_OK; + goto end; + } + + syscall->internal_filter.filter = strdup(filter); + if (syscall->internal_filter.filter == NULL) { + ret_code = LTTNG_ERR_NOMEM; + goto end; + } + + ret = run_as_generate_filter_bytecode( + syscall->internal_filter.filter, uid, gid, &bytecode); + if (ret) { + ret_code = LTTNG_ERR_FILTER_INVAL; + } + + syscall->internal_filter.bytecode = bytecode; + bytecode = NULL; + +error: +end: + free(bytecode); + return ret_code; +} + +static const char *lttng_event_rule_syscall_get_internal_filter( + const struct lttng_event_rule *rule) +{ + struct lttng_event_rule_syscall *syscall; + assert(rule); + + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + return syscall->internal_filter.filter; +} + +static const struct lttng_bytecode * +lttng_event_rule_syscall_get_internal_filter_bytecode( + const struct lttng_event_rule *rule) +{ + struct lttng_event_rule_syscall *syscall; + assert(rule); + + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + return syscall->internal_filter.bytecode; +} + +static struct lttng_event_exclusion * +lttng_event_rule_syscall_generate_exclusions(struct lttng_event_rule *rule) +{ + /* Not supported */ + return NULL; +} + +struct lttng_event_rule *lttng_event_rule_syscall_create() +{ + struct lttng_event_rule_syscall *rule; + + rule = zmalloc(sizeof(struct lttng_event_rule_syscall)); + if (!rule) { + return NULL; + } + + lttng_event_rule_init(&rule->parent, LTTNG_EVENT_RULE_TYPE_SYSCALL); + rule->parent.validate = lttng_event_rule_syscall_validate; + rule->parent.serialize = lttng_event_rule_syscall_serialize; + rule->parent.equal = lttng_event_rule_syscall_is_equal; + rule->parent.destroy = lttng_event_rule_syscall_destroy; + rule->parent.populate = lttng_event_rule_syscall_populate; + rule->parent.get_filter = lttng_event_rule_syscall_get_internal_filter; + rule->parent.get_filter_bytecode = + lttng_event_rule_syscall_get_internal_filter_bytecode; + rule->parent.generate_exclusions = + lttng_event_rule_syscall_generate_exclusions; + return &rule->parent; +} + +LTTNG_HIDDEN +ssize_t lttng_event_rule_syscall_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **_event_rule) +{ + ssize_t ret, offset = 0; + enum lttng_event_rule_status status; + const struct lttng_event_rule_syscall_comm *syscall_comm; + const char *pattern; + const char *filter_expression = NULL; + struct lttng_buffer_view current_view; + struct lttng_event_rule *rule = NULL; + + if (!_event_rule) { + ret = -1; + goto end; + } + + if (view->size < sizeof(*syscall_comm)) { + ERR("Failed to initialize from malformed event rule syscall: buffer too short to contain header"); + ret = -1; + goto end; + } + + current_view = lttng_buffer_view_from_view( + view, offset, sizeof(*syscall_comm)); + syscall_comm = (typeof(syscall_comm)) current_view.data; + + if (!syscall_comm) { + ret = -1; + goto end; + } + + rule = lttng_event_rule_syscall_create(); + if (!rule) { + ERR("Failed to create event rule syscall"); + ret = -1; + goto end; + } + + /* Skip to payload */ + offset += current_view.size; + + /* Map the pattern */ + current_view = lttng_buffer_view_from_view( + view, offset, syscall_comm->pattern_len); + pattern = current_view.data; + if (!pattern) { + ret = -1; + goto end; + } + + if (syscall_comm->pattern_len == 1 || + pattern[syscall_comm->pattern_len - 1] != '\0' || + strlen(pattern) != syscall_comm->pattern_len - 1) { + /* + * Check that the pattern is not NULL, is NULL-terminated, and + * does not contain a NULL before the last byte. + */ + ret = -1; + goto end; + } + + /* Skip after the pattern */ + offset += syscall_comm->pattern_len; + + if (!syscall_comm->filter_expression_len) { + goto skip_filter_expression; + } + + /* Map the filter_expression */ + current_view = lttng_buffer_view_from_view( + view, offset, syscall_comm->filter_expression_len); + filter_expression = current_view.data; + if (!filter_expression) { + ret = -1; + goto end; + } + + if (syscall_comm->filter_expression_len == 1 || + filter_expression[syscall_comm->filter_expression_len - + 1] != '\0' || + strlen(filter_expression) != + syscall_comm->filter_expression_len - + 1) { + /* + * Check that the filter expression is not NULL, is + * NULL-terminated, and does not contain a NULL before the last + * byte. + */ + ret = -1; + goto end; + } + + /* Skip after the pattern */ + offset += syscall_comm->filter_expression_len; + +skip_filter_expression: + + status = lttng_event_rule_syscall_set_pattern(rule, pattern); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set event rule syscall pattern"); + ret = -1; + goto end; + } + + if (filter_expression) { + status = lttng_event_rule_syscall_set_filter( + rule, filter_expression); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set event rule syscall pattern"); + ret = -1; + goto end; + } + } + + *_event_rule = rule; + rule = NULL; + ret = offset; +end: + lttng_event_rule_destroy(rule); + return ret; +} + +enum lttng_event_rule_status lttng_event_rule_syscall_set_pattern( + struct lttng_event_rule *rule, const char *pattern) +{ + char *pattern_copy = NULL; + struct lttng_event_rule_syscall *syscall; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_SYSCALL_EVENT_RULE(rule) || !pattern || + strlen(pattern) == 0) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + pattern_copy = strdup(pattern); + if (!pattern_copy) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + + if (syscall->pattern) { + free(syscall->pattern); + } + + syscall->pattern = pattern_copy; + pattern_copy = NULL; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_syscall_get_pattern( + const struct lttng_event_rule *rule, const char **pattern) +{ + struct lttng_event_rule_syscall *syscall; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_SYSCALL_EVENT_RULE(rule) || !pattern) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + if (!syscall->pattern) { + status = LTTNG_EVENT_RULE_STATUS_UNSET; + goto end; + } + + *pattern = syscall->pattern; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_syscall_set_filter( + struct lttng_event_rule *rule, const char *expression) +{ + char *expression_copy = NULL; + struct lttng_event_rule_syscall *syscall; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + /* TODO: validate that the passed expression is valid */ + + if (!rule || !IS_SYSCALL_EVENT_RULE(rule) || !expression || + strlen(expression) == 0) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + expression_copy = strdup(expression); + if (!expression_copy) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + + if (syscall->filter_expression) { + free(syscall->filter_expression); + } + + syscall->filter_expression = expression_copy; + expression_copy = NULL; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_syscall_get_filter( + const struct lttng_event_rule *rule, const char **expression) +{ + struct lttng_event_rule_syscall *syscall; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_SYSCALL_EVENT_RULE(rule) || !expression) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + syscall = container_of(rule, struct lttng_event_rule_syscall, parent); + if (!syscall->filter_expression) { + status = LTTNG_EVENT_RULE_STATUS_UNSET; + goto end; + } + + *expression = syscall->filter_expression; +end: + return status; +} diff --git a/src/common/event-rule/tracepoint.c b/src/common/event-rule/tracepoint.c new file mode 100644 index 000000000..750ce0d23 --- /dev/null +++ b/src/common/event-rule/tracepoint.c @@ -0,0 +1,1133 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define IS_TRACEPOINT_EVENT_RULE(rule) \ + (lttng_event_rule_get_type(rule) == LTTNG_EVENT_RULE_TYPE_TRACEPOINT) + +static void lttng_event_rule_tracepoint_destroy(struct lttng_event_rule *rule) +{ + struct lttng_event_rule_tracepoint *tracepoint; + + if (rule == NULL) { + return; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + + free(tracepoint->pattern); + free(tracepoint->filter_expression); + for (int i = 0; i < tracepoint->exclusions.count; i++) { + free(tracepoint->exclusions.values[i]); + } + free(tracepoint->exclusions.values); + free(tracepoint->internal_filter.filter); + free(tracepoint->internal_filter.bytecode); + free(tracepoint); +} + +static bool lttng_event_rule_tracepoint_validate( + const struct lttng_event_rule *rule) +{ + bool valid = false; + struct lttng_event_rule_tracepoint *tracepoint; + + if (!rule) { + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + + /* Required field */ + if (!tracepoint->pattern) { + ERR("Invalid tracepoint event rule: a pattern must be set."); + goto end; + } + if (tracepoint->domain == LTTNG_DOMAIN_NONE) { + ERR("Invalid tracepoint event rule: a domain must be set."); + goto end; + } + + /* QUESTION: do we validate inside state on validate or during set of + * each component */ + + valid = true; +end: + return valid; +} + +static int lttng_event_rule_tracepoint_serialize( + const struct lttng_event_rule *rule, + struct lttng_dynamic_buffer *buf, + int *fd_to_send) +{ + int ret; + size_t pattern_len, filter_expression_len, exclusions_len; + struct lttng_event_rule_tracepoint *tracepoint; + struct lttng_event_rule_tracepoint_comm tracepoint_comm; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule)) { + ret = -1; + goto end; + } + + DBG("Serializing tracepoint event rule"); + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + + pattern_len = strlen(tracepoint->pattern) + 1; + + if (tracepoint->filter_expression != NULL) { + filter_expression_len = + strlen(tracepoint->filter_expression) + 1; + } else { + filter_expression_len = 0; + } + + exclusions_len = 0; + for (int i = 0; i < tracepoint->exclusions.count; i++) { + /* Payload */ + exclusions_len += strlen(tracepoint->exclusions.values[i]) + 1; + /* Bound check */ + exclusions_len += sizeof(uint32_t); + } + + tracepoint_comm.domain_type = (int8_t) tracepoint->domain; + tracepoint_comm.loglevel_type = (int8_t) tracepoint->loglevel.type; + tracepoint_comm.loglevel_value = tracepoint->loglevel.value; + tracepoint_comm.pattern_len = pattern_len; + tracepoint_comm.filter_expression_len = filter_expression_len; + tracepoint_comm.exclusions_count = tracepoint->exclusions.count; + tracepoint_comm.exclusions_len = exclusions_len; + + ret = lttng_dynamic_buffer_append( + buf, &tracepoint_comm, sizeof(tracepoint_comm)); + if (ret) { + goto end; + } + ret = lttng_dynamic_buffer_append( + buf, tracepoint->pattern, pattern_len); + if (ret) { + goto end; + } + ret = lttng_dynamic_buffer_append(buf, tracepoint->filter_expression, + filter_expression_len); + if (ret) { + goto end; + } + + size_t exclusions_appended = 0; + for (int i = 0; i < tracepoint->exclusions.count; i++) { + size_t len; + len = strlen(tracepoint->exclusions.values[i]) + 1; + /* Append bound check, does not include the '\0' */ + ret = lttng_dynamic_buffer_append(buf, &len, sizeof(uint32_t)); + if (ret) { + goto end; + } + exclusions_appended += sizeof(uint32_t); + + /* Include the '\0' in the payload */ + ret = lttng_dynamic_buffer_append( + buf, tracepoint->exclusions.values[i], len); + if (ret) { + goto end; + } + exclusions_appended += len; + } + + assert(exclusions_len == exclusions_appended); + + if (fd_to_send) { + /* No fd to send */ + *fd_to_send = -1; + } + +end: + return ret; +} + +static bool lttng_event_rule_tracepoint_is_equal( + const struct lttng_event_rule *_a, + const struct lttng_event_rule *_b) +{ + bool is_equal = false; + struct lttng_event_rule_tracepoint *a, *b; + + a = container_of(_a, struct lttng_event_rule_tracepoint, parent); + b = container_of(_b, struct lttng_event_rule_tracepoint, parent); + + /* Quick checks */ + if (a->domain != b->domain) { + goto end; + } + + if (a->exclusions.count != b->exclusions.count) { + goto end; + } + + if (!!a->filter_expression != !!b->filter_expression) { + goto end; + } + + /* Long check */ + /* Tracepoint is invalid if this is not true */ + assert(a->pattern); + assert(b->pattern); + if (strcmp(a->pattern, b->pattern)) { + goto end; + } + + if (a->filter_expression && b->filter_expression) { + if (strcmp(a->filter_expression, b->filter_expression)) { + goto end; + } + } + + if (a->loglevel.type != b->loglevel.type) { + goto end; + } + + if (a->loglevel.value != b->loglevel.value) { + goto end; + } + + for (int i = 0; i < a->exclusions.count; i++) { + if (strcmp(a->exclusions.values[i], b->exclusions.values[i])) { + goto end; + } + } + + is_equal = true; +end: + return is_equal; +} + +/* + * On success ret is 0; + * + * On error ret is negative. + * + * An event with NO loglevel and the name is * will return NULL. + */ +static int generate_agent_filter( + const struct lttng_event_rule *rule, char **_agent_filter) +{ + int err; + int ret = 0; + char *agent_filter = NULL; + const char *pattern; + const char *filter; + enum lttng_loglevel_type loglevel_type; + enum lttng_event_rule_status status; + + assert(rule); + assert(_agent_filter); + + status = lttng_event_rule_tracepoint_get_pattern(rule, &pattern); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret = -1; + goto end; + } + + status = lttng_event_rule_tracepoint_get_filter(rule, &filter); + if (status == LTTNG_EVENT_RULE_STATUS_UNSET) { + filter = NULL; + } else if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret = -1; + goto end; + } + + status = lttng_event_rule_tracepoint_get_loglevel_type( + rule, &loglevel_type); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret = -1; + goto end; + } + + /* Don't add filter for the '*' event. */ + if (strcmp(pattern, "*") != 0) { + if (filter) { + err = asprintf(&agent_filter, + "(%s) && (logger_name == \"%s\")", + filter, pattern); + } else { + err = asprintf(&agent_filter, "logger_name == \"%s\"", + pattern); + } + if (err < 0) { + PERROR("asprintf"); + ret = -1; + goto end; + } + } + + if (loglevel_type != LTTNG_EVENT_LOGLEVEL_ALL) { + const char *op; + int loglevel_value; + + status = lttng_event_rule_tracepoint_get_loglevel( + rule, &loglevel_value); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret = -1; + goto end; + } + + if (loglevel_type == LTTNG_EVENT_LOGLEVEL_RANGE) { + op = ">="; + } else { + op = "=="; + } + + if (filter || agent_filter) { + char *new_filter; + + err = asprintf(&new_filter, + "(%s) && (int_loglevel %s %d)", + agent_filter ? agent_filter : filter, + op, loglevel_value); + if (agent_filter) { + free(agent_filter); + } + agent_filter = new_filter; + } else { + err = asprintf(&agent_filter, "int_loglevel %s %d", op, + loglevel_value); + } + if (err < 0) { + PERROR("asprintf"); + ret = -1; + goto end; + } + } + + *_agent_filter = agent_filter; + agent_filter = NULL; + +end: + free(agent_filter); + return ret; +} + +static enum lttng_error_code lttng_event_rule_tracepoint_populate( + struct lttng_event_rule *rule, uid_t uid, gid_t gid) +{ + int ret; + enum lttng_error_code ret_code; + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_domain_type domain_type; + enum lttng_event_rule_status status; + const char *filter; + struct lttng_bytecode *bytecode = NULL; + + assert(rule); + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + + status = lttng_event_rule_tracepoint_get_filter(rule, &filter); + if (status == LTTNG_EVENT_RULE_STATUS_UNSET) { + filter = NULL; + } else if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret_code = LTTNG_ERR_FILTER_INVAL; + goto end; + } + + if (filter && filter[0] == '\0') { + ret_code = LTTNG_ERR_FILTER_INVAL; + goto error; + } + + status = lttng_event_rule_tracepoint_get_domain_type( + rule, &domain_type); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret_code = LTTNG_ERR_UNK; + goto error; + } + + switch (domain_type) { + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_PYTHON: + { + char *agent_filter; + ret = generate_agent_filter(rule, &agent_filter); + if (ret) { + ret_code = LTTNG_ERR_FILTER_INVAL; + goto error; + } + tracepoint->internal_filter.filter = agent_filter; + break; + } + default: + { + if (filter) { + tracepoint->internal_filter.filter = strdup(filter); + if (tracepoint->internal_filter.filter == NULL) { + ret_code = LTTNG_ERR_NOMEM; + goto error; + } + } else { + tracepoint->internal_filter.filter = NULL; + } + break; + } + } + + if (tracepoint->internal_filter.filter == NULL) { + ret_code = LTTNG_OK; + goto end; + } + + ret = run_as_generate_filter_bytecode( + tracepoint->internal_filter.filter, uid, gid, + &bytecode); + if (ret) { + ret_code = LTTNG_ERR_FILTER_INVAL; + goto end; + } + + tracepoint->internal_filter.bytecode = bytecode; + bytecode = NULL; + ret_code = LTTNG_OK; + +error: +end: + free(bytecode); + return ret_code; +} + +static const char *lttng_event_rule_tracepoint_get_internal_filter( + const struct lttng_event_rule *rule) +{ + struct lttng_event_rule_tracepoint *tracepoint; + assert(rule); + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + return tracepoint->internal_filter.filter; +} + +static const struct lttng_bytecode * +lttng_event_rule_tracepoint_get_internal_filter_bytecode( + const struct lttng_event_rule *rule) +{ + struct lttng_event_rule_tracepoint *tracepoint; + assert(rule); + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + return tracepoint->internal_filter.bytecode; +} + +/* TODO: review error handling, the function should be able to + * return error information. + */ +static struct lttng_event_exclusion * +lttng_event_rule_tracepoint_generate_exclusions(struct lttng_event_rule *rule) +{ + enum lttng_domain_type domain_type = LTTNG_DOMAIN_NONE; + struct lttng_event_exclusion *local_exclusions = NULL; + struct lttng_event_exclusion *ret_exclusions = NULL; + unsigned int nb_exclusions = 0; + + (void) lttng_event_rule_tracepoint_get_domain_type(rule, &domain_type); + + switch (domain_type) { + case LTTNG_DOMAIN_KERNEL: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + /* Not supported */ + ret_exclusions = NULL; + goto end; + case LTTNG_DOMAIN_UST: + /* Exclusions supported */ + break; + default: + assert(0); + } + + (void) lttng_event_rule_tracepoint_get_exclusions_count( + rule, &nb_exclusions); + if (nb_exclusions == 0) { + /* Nothing to do */ + ret_exclusions = NULL; + goto end; + } + + local_exclusions = zmalloc(sizeof(struct lttng_event_exclusion) + + (LTTNG_SYMBOL_NAME_LEN * nb_exclusions)); + if (!local_exclusions) { + ERR("local exclusion allocation"); + ret_exclusions = NULL; + goto end; + } + + local_exclusions->count = nb_exclusions; + for (unsigned int i = 0; i < nb_exclusions; i++) { + /* TODO: check for truncation. + * Part of this should be validated on set exclusion + */ + const char *tmp; + (void) lttng_event_rule_tracepoint_get_exclusion_at_index( + rule, i, &tmp); + strncpy(local_exclusions->names[i], tmp, LTTNG_SYMBOL_NAME_LEN); + local_exclusions->names[i][LTTNG_SYMBOL_NAME_LEN - 1] = '\0'; + } + + /* Pass ownership */ + ret_exclusions = local_exclusions; + local_exclusions = NULL; +end: + free(local_exclusions); + /* Not supported */ + return ret_exclusions; +} + +static struct lttng_event *lttng_event_rule_tracepoint_generate_lttng_event( + const struct lttng_event_rule *rule) +{ + const struct lttng_event_rule_tracepoint *tracepoint; + struct lttng_event *local_event = NULL; + struct lttng_event *event = NULL; + + tracepoint = container_of( + rule, const struct lttng_event_rule_tracepoint, parent); + + local_event = zmalloc(sizeof(*local_event)); + if (!local_event) { + goto error; + } + + local_event->type = LTTNG_EVENT_TRACEPOINT; + (void) strncpy(local_event->name, tracepoint->pattern, + sizeof(local_event->name) - 1); + local_event->name[sizeof(local_event->name) - 1] = '\0'; + local_event->loglevel_type = tracepoint->loglevel.type; + local_event->loglevel = tracepoint->loglevel.value; + + event = local_event; + local_event = NULL; +error: + free(local_event); + return event; +} + +struct lttng_event_rule *lttng_event_rule_tracepoint_create( + enum lttng_domain_type domain_type) +{ + struct lttng_event_rule_tracepoint *rule; + + if (domain_type == LTTNG_DOMAIN_NONE) { + return NULL; + } + + rule = zmalloc(sizeof(struct lttng_event_rule_tracepoint)); + if (!rule) { + return NULL; + } + + lttng_event_rule_init(&rule->parent, LTTNG_EVENT_RULE_TYPE_TRACEPOINT); + rule->parent.validate = lttng_event_rule_tracepoint_validate; + rule->parent.serialize = lttng_event_rule_tracepoint_serialize; + rule->parent.equal = lttng_event_rule_tracepoint_is_equal; + rule->parent.destroy = lttng_event_rule_tracepoint_destroy; + rule->parent.populate = lttng_event_rule_tracepoint_populate; + rule->parent.get_filter = + lttng_event_rule_tracepoint_get_internal_filter; + rule->parent.get_filter_bytecode = + lttng_event_rule_tracepoint_get_internal_filter_bytecode; + rule->parent.generate_exclusions = + lttng_event_rule_tracepoint_generate_exclusions; + rule->parent.generate_lttng_event = + lttng_event_rule_tracepoint_generate_lttng_event; + + rule->domain = domain_type; + rule->loglevel.type = LTTNG_EVENT_LOGLEVEL_ALL; + + return &rule->parent; +} + +LTTNG_HIDDEN +ssize_t lttng_event_rule_tracepoint_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **_event_rule) +{ + ssize_t ret, offset = 0; + enum lttng_event_rule_status status; + enum lttng_domain_type domain_type; + enum lttng_loglevel_type loglevel_type; + const struct lttng_event_rule_tracepoint_comm *tracepoint_comm; + const char *pattern; + const char *filter_expression = NULL; + const char **exclusions = NULL; + const uint32_t *exclusion_len; + const char *exclusion; + struct lttng_buffer_view current_view; + struct lttng_event_rule *rule = NULL; + + if (!_event_rule) { + ret = -1; + goto end; + } + + if (view->size < sizeof(*tracepoint_comm)) { + ERR("Failed to initialize from malformed event rule tracepoint: buffer too short to contain header"); + ret = -1; + goto end; + } + + current_view = lttng_buffer_view_from_view( + view, offset, sizeof(*tracepoint_comm)); + tracepoint_comm = (typeof(tracepoint_comm)) current_view.data; + + if (!tracepoint_comm) { + ret = -1; + goto end; + } + + if (tracepoint_comm->domain_type <= LTTNG_DOMAIN_NONE || + tracepoint_comm->domain_type > LTTNG_DOMAIN_PYTHON) { + /* Invalid domain value. */ + ERR("Invalid domain type value (%i) found in tracepoint_comm buffer", + (int) tracepoint_comm->domain_type); + ret = -1; + goto end; + } + + domain_type = (enum lttng_domain_type) tracepoint_comm->domain_type; + rule = lttng_event_rule_tracepoint_create(domain_type); + if (!rule) { + ERR("Failed to create event rule tracepoint"); + ret = -1; + goto end; + } + + loglevel_type = (enum lttng_loglevel_type) + tracepoint_comm->loglevel_type; + switch (loglevel_type) { + case LTTNG_EVENT_LOGLEVEL_ALL: + status = lttng_event_rule_tracepoint_set_loglevel_all(rule); + break; + case LTTNG_EVENT_LOGLEVEL_RANGE: + status = lttng_event_rule_tracepoint_set_loglevel_range( + rule, (enum lttng_loglevel_type) tracepoint_comm + ->loglevel_value); + break; + case LTTNG_EVENT_LOGLEVEL_SINGLE: + status = lttng_event_rule_tracepoint_set_loglevel( + rule, (enum lttng_loglevel_type) tracepoint_comm + ->loglevel_value); + break; + default: + ERR("Failed to set event rule tracepoint loglevel: unknown loglevel type"); + ret = -1; + goto end; + } + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set event rule tracepoint loglevel"); + } + + /* Skip to payload */ + offset += current_view.size; + + /* Map the pattern */ + current_view = lttng_buffer_view_from_view( + view, offset, tracepoint_comm->pattern_len); + pattern = current_view.data; + if (!pattern) { + ret = -1; + goto end; + } + + if (tracepoint_comm->pattern_len == 1 || + pattern[tracepoint_comm->pattern_len - 1] != '\0' || + strlen(pattern) != tracepoint_comm->pattern_len - 1) { + /* + * Check that the pattern is not NULL, is NULL-terminated, and + * does not contain a NULL before the last byte. + */ + ret = -1; + goto end; + } + + /* Skip after the pattern */ + offset += tracepoint_comm->pattern_len; + + if (!tracepoint_comm->filter_expression_len) { + goto skip_filter_expression; + } + + /* Map the filter_expression */ + current_view = lttng_buffer_view_from_view( + view, offset, tracepoint_comm->filter_expression_len); + filter_expression = current_view.data; + if (!filter_expression) { + ret = -1; + goto end; + } + + if (tracepoint_comm->filter_expression_len == 1 || + filter_expression[tracepoint_comm->filter_expression_len - + 1] != '\0' || + strlen(filter_expression) != + tracepoint_comm->filter_expression_len - + 1) { + /* + * Check that the filter expression is not NULL, is + * NULL-terminated, and does not contain a NULL before the last + * byte. + */ + ret = -1; + goto end; + } + + /* Skip after the pattern */ + offset += tracepoint_comm->filter_expression_len; + +skip_filter_expression: + + if (!tracepoint_comm->exclusions_count) { + goto skip_exclusions; + } + + exclusions = zmalloc(sizeof(*exclusions) * + tracepoint_comm->exclusions_count); + if (!exclusions) { + ret = -1; + goto end; + } + + for (int i = 0; i < tracepoint_comm->exclusions_count; i++) { + current_view = lttng_buffer_view_from_view( + view, offset, sizeof(*exclusion_len)); + exclusion_len = (typeof(exclusion_len)) current_view.data; + if (!exclusion_len) { + ret = -1; + goto end; + } + + offset += sizeof(*exclusion_len); + current_view = lttng_buffer_view_from_view( + view, offset, *exclusion_len); + exclusion = current_view.data; + if (*exclusion_len == 1 || + exclusion[*exclusion_len - 1] != '\0' || + strlen(exclusion) != *exclusion_len - 1) { + /* + * Check that the exclusion is not NULL, is + * NULL-terminated, and does not contain a NULL before + * the last byte. + */ + ret = -1; + goto end; + } + exclusions[i] = exclusion; + /* Skip to next exclusion */ + offset += *exclusion_len; + } + +skip_exclusions: + status = lttng_event_rule_tracepoint_set_pattern(rule, pattern); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set event rule tracepoint pattern"); + ret = -1; + goto end; + } + + if (filter_expression) { + status = lttng_event_rule_tracepoint_set_filter( + rule, filter_expression); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set event rule tracepoint pattern"); + ret = -1; + goto end; + } + } + + if (exclusions) { + status = lttng_event_rule_tracepoint_set_exclusions(rule, + tracepoint_comm->exclusions_count, exclusions); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ERR("Failed to set event rule tracepoint exclusions"); + ret = -1; + goto end; + } + } + + *_event_rule = rule; + rule = NULL; + ret = offset; +end: + free(exclusions); + lttng_event_rule_destroy(rule); + return ret; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_set_pattern( + struct lttng_event_rule *rule, const char *pattern) +{ + char *pattern_copy = NULL; + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || !pattern || + strlen(pattern) == 0) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + pattern_copy = strdup(pattern); + if (!pattern_copy) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + + if (tracepoint->pattern) { + free(tracepoint->pattern); + } + + tracepoint->pattern = pattern_copy; + pattern_copy = NULL; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_get_pattern( + const struct lttng_event_rule *rule, const char **pattern) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || !pattern) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + if (!tracepoint->pattern) { + status = LTTNG_EVENT_RULE_STATUS_UNSET; + goto end; + } + + *pattern = tracepoint->pattern; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_get_domain_type( + const struct lttng_event_rule *rule, + enum lttng_domain_type *type) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || !type) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + *type = tracepoint->domain; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_set_filter( + struct lttng_event_rule *rule, const char *expression) +{ + char *expression_copy = NULL; + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + /* TODO: validate that the passed expression is valid */ + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || !expression || + strlen(expression) == 0) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + expression_copy = strdup(expression); + if (!expression_copy) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + + if (tracepoint->filter_expression) { + free(tracepoint->filter_expression); + } + + tracepoint->filter_expression = expression_copy; + expression_copy = NULL; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_get_filter( + const struct lttng_event_rule *rule, const char **expression) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || !expression) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + if (!tracepoint->filter_expression) { + status = LTTNG_EVENT_RULE_STATUS_UNSET; + goto end; + } + + *expression = tracepoint->filter_expression; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_set_loglevel( + struct lttng_event_rule *rule, int level) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + /* + * TODO/QUESTION: do we validate the passed level based on the domain? + * What if no domain is set yet? Should we move the domain to the + * "create" api call to enforce the domain type? + */ + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule)) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + tracepoint->loglevel.value = level; + tracepoint->loglevel.type = LTTNG_EVENT_LOGLEVEL_SINGLE; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_set_loglevel_range( + struct lttng_event_rule *rule, int level) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + /* + * TODO/QUESTION: do we validate the passed level based on the domain? + * What if no domain is set yet? Should we move the domain to the + * "create" api call to enforce the domain type? + */ + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule)) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + tracepoint->loglevel.value = level; + tracepoint->loglevel.type = LTTNG_EVENT_LOGLEVEL_RANGE; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_set_loglevel_all( + struct lttng_event_rule *rule) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule)) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + tracepoint->loglevel.type = LTTNG_EVENT_LOGLEVEL_ALL; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_get_loglevel_type( + const struct lttng_event_rule *rule, + enum lttng_loglevel_type *type) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || !type) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + *type = tracepoint->loglevel.type; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_get_loglevel( + const struct lttng_event_rule *rule, int *level) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || !level) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + if (tracepoint->loglevel.type == LTTNG_EVENT_LOGLEVEL_ALL) { + status = LTTNG_EVENT_RULE_STATUS_UNSET; + goto end; + } + *level = tracepoint->loglevel.value; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_set_exclusions( + struct lttng_event_rule *rule, + unsigned int count, + const char **exclusions) +{ + char **exclusions_copy = NULL; + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + enum lttng_domain_type domain_type; + + /* TODO: validate that the passed exclusions are valid? */ + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || count == 0 || + !exclusions) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + + status = lttng_event_rule_tracepoint_get_domain_type( + rule, &domain_type); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + goto end; + } + + switch (domain_type) { + case LTTNG_DOMAIN_KERNEL: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + status = LTTNG_EVENT_RULE_STATUS_UNSUPPORTED; + goto end; + case LTTNG_DOMAIN_UST: + /* Exclusions supported */ + break; + default: + assert(0); + } + + exclusions_copy = zmalloc(sizeof(char *) * count); + if (!exclusions_copy) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + + /* Perform the copy locally */ + for (int i = 0; i < count; i++) { + exclusions_copy[i] = strdup(exclusions[i]); + if (!exclusions_copy[i]) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + } + + if (tracepoint->exclusions.count != 0) { + for (int i = 0; i < tracepoint->exclusions.count; i++) { + free(tracepoint->exclusions.values[i]); + } + free(tracepoint->exclusions.values); + } + + tracepoint->exclusions.values = exclusions_copy; + tracepoint->exclusions.count = count; + exclusions_copy = NULL; +end: + if (exclusions_copy) { + for (int i = 0; i < count; i++) { + free(exclusions_copy[i]); + } + free(exclusions_copy); + } + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_get_exclusions_count( + const struct lttng_event_rule *rule, unsigned int *count) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || !count) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + *count = tracepoint->exclusions.count; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_tracepoint_get_exclusion_at_index( + const struct lttng_event_rule *rule, + unsigned int index, + const char **exclusion) +{ + struct lttng_event_rule_tracepoint *tracepoint; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_TRACEPOINT_EVENT_RULE(rule) || !exclusion) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + tracepoint = container_of( + rule, struct lttng_event_rule_tracepoint, parent); + if (index >= tracepoint->exclusions.count) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + *exclusion = tracepoint->exclusions.values[index]; +end: + return status; +} diff --git a/src/common/event-rule/uprobe.c b/src/common/event-rule/uprobe.c new file mode 100644 index 000000000..ebdbc9fc6 --- /dev/null +++ b/src/common/event-rule/uprobe.c @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define IS_UPROBE_EVENT_RULE(rule) \ + (lttng_event_rule_get_type(rule) == LTTNG_EVENT_RULE_TYPE_UPROBE) + +static void lttng_event_rule_uprobe_destroy(struct lttng_event_rule *rule) +{ + struct lttng_event_rule_uprobe *uprobe; + + uprobe = container_of(rule, struct lttng_event_rule_uprobe, parent); + + lttng_userspace_probe_location_destroy(uprobe->location); + free(uprobe->name); + free(uprobe); +} + +static bool lttng_event_rule_uprobe_validate( + const struct lttng_event_rule *rule) +{ + bool valid = false; + struct lttng_event_rule_uprobe *uprobe; + + if (!rule) { + goto end; + } + + uprobe = container_of(rule, struct lttng_event_rule_uprobe, parent); + + /* Required field */ + if (!uprobe->name) { + ERR("Invalid uprobe event rule: a pattern must be set."); + goto end; + } + + if (!uprobe->location) { + ERR("Invalid uprobe event rule: a location must be set."); + goto end; + } + + /* TODO should we validate the probe location? */ + + valid = true; +end: + return valid; +} + +static int lttng_event_rule_uprobe_serialize( + const struct lttng_event_rule *rule, + struct lttng_dynamic_buffer *buf, + int *fd_to_send) +{ + int ret; + size_t name_len, header_offset, size_before_probe; + struct lttng_event_rule_uprobe *uprobe; + struct lttng_event_rule_uprobe_comm uprobe_comm = {0}; + struct lttng_event_rule_uprobe_comm *header; + + if (!rule || !IS_UPROBE_EVENT_RULE(rule)) { + ret = -1; + goto end; + } + + header_offset = buf->size; + + DBG("Serializing uprobe event rule"); + uprobe = container_of(rule, struct lttng_event_rule_uprobe, parent); + + name_len = strlen(uprobe->name) + 1; + + uprobe_comm.name_len = name_len; + + ret = lttng_dynamic_buffer_append( + buf, &uprobe_comm, sizeof(uprobe_comm)); + if (ret) { + goto end; + } + ret = lttng_dynamic_buffer_append(buf, uprobe->name, name_len); + if (ret) { + goto end; + } + + size_before_probe = buf->size; + + /* This serialize return the size taken in the buffer */ + /* TODO: should all serialize standardise on this? */ + ret = lttng_userspace_probe_location_serialize( + uprobe->location, buf, fd_to_send); + if (ret < 0) { + goto end; + } + + /* Update the header regarding the probe size */ + header = (struct lttng_event_rule_uprobe_comm *) ((char *) buf->data + + header_offset); + header->location_len = buf->size - size_before_probe; + + ret = 0; + +end: + return ret; +} + +static bool lttng_event_rule_uprobe_is_equal(const struct lttng_event_rule *_a, + const struct lttng_event_rule *_b) +{ + bool is_equal = false; + struct lttng_event_rule_uprobe *a, *b; + + a = container_of(_a, struct lttng_event_rule_uprobe, parent); + b = container_of(_b, struct lttng_event_rule_uprobe, parent); + + /* uprobe is invalid if this is not true */ + assert(a->name); + assert(b->name); + if (strcmp(a->name, b->name)) { + goto end; + } + + assert(a->location); + assert(b->location); + is_equal = lttng_userspace_probe_location_is_equal( + a->location, b->location); +end: + return is_equal; +} + +static enum lttng_error_code lttng_event_rule_uprobe_populate( + struct lttng_event_rule *rule, uid_t uid, gid_t gid) +{ + /* Nothing to do */ + return LTTNG_OK; +} + +static const char *lttng_event_rule_uprobe_get_filter( + const struct lttng_event_rule *rule) +{ + /* Unsupported */ + return NULL; +} + +static const struct lttng_bytecode * +lttng_event_rule_uprobe_get_filter_bytecode(const struct lttng_event_rule *rule) +{ + /* Unsupported */ + return NULL; +} + +static struct lttng_event_exclusion * +lttng_event_rule_uprobe_generate_exclusions(struct lttng_event_rule *rule) +{ + /* Unsupported */ + return NULL; +} + +struct lttng_event_rule *lttng_event_rule_uprobe_create() +{ + struct lttng_event_rule_uprobe *rule; + + rule = zmalloc(sizeof(struct lttng_event_rule_uprobe)); + if (!rule) { + return NULL; + } + + lttng_event_rule_init(&rule->parent, LTTNG_EVENT_RULE_TYPE_UPROBE); + rule->parent.validate = lttng_event_rule_uprobe_validate; + rule->parent.serialize = lttng_event_rule_uprobe_serialize; + rule->parent.equal = lttng_event_rule_uprobe_is_equal; + rule->parent.destroy = lttng_event_rule_uprobe_destroy; + rule->parent.populate = lttng_event_rule_uprobe_populate; + rule->parent.get_filter = lttng_event_rule_uprobe_get_filter; + rule->parent.get_filter_bytecode = + lttng_event_rule_uprobe_get_filter_bytecode; + rule->parent.generate_exclusions = + lttng_event_rule_uprobe_generate_exclusions; + return &rule->parent; +} + +LTTNG_HIDDEN +ssize_t lttng_event_rule_uprobe_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_event_rule **_event_rule) +{ + ssize_t ret, offset = 0; + const struct lttng_event_rule_uprobe_comm *uprobe_comm; + const char *name; + struct lttng_buffer_view current_view; + struct lttng_event_rule *rule = NULL; + struct lttng_userspace_probe_location *location; + struct lttng_event_rule_uprobe *uprobe; + + if (!_event_rule) { + ret = -1; + goto end; + } + + if (view->size < sizeof(*uprobe_comm)) { + ERR("Failed to initialize from malformed event rule uprobe: buffer too short to contain header"); + ret = -1; + goto end; + } + + current_view = lttng_buffer_view_from_view( + view, offset, sizeof(*uprobe_comm)); + uprobe_comm = (typeof(uprobe_comm)) current_view.data; + + if (!uprobe_comm) { + ret = -1; + goto end; + } + + rule = lttng_event_rule_uprobe_create(); + if (!rule) { + ERR("Failed to create event rule uprobe"); + ret = -1; + goto end; + } + + /* Skip to payload */ + offset += current_view.size; + + /* Map the name */ + current_view = lttng_buffer_view_from_view( + view, offset, uprobe_comm->name_len); + name = current_view.data; + if (!name) { + ret = -1; + goto end; + } + + if (uprobe_comm->name_len == 1 || + name[uprobe_comm->name_len - 1] != '\0' || + strlen(name) != uprobe_comm->name_len - 1) { + /* + * Check that the name is not NULL, is NULL-terminated, and + * does not contain a NULL before the last byte. + */ + ret = -1; + goto end; + } + + /* Skip after the name */ + offset += uprobe_comm->name_len; + + /* Map the location */ + current_view = lttng_buffer_view_from_view( + view, offset, uprobe_comm->location_len); + ret = lttng_userspace_probe_location_create_from_buffer( + ¤t_view, &location); + if (ret < 0) { + ret = -1; + goto end; + } + + assert(ret == uprobe_comm->location_len); + + /* Skip after the location */ + offset += uprobe_comm->location_len; + + uprobe = container_of(rule, struct lttng_event_rule_uprobe, parent); + uprobe->location = location; + + (void) lttng_event_rule_uprobe_set_name(rule, name); + + if (!lttng_event_rule_uprobe_validate(rule)) { + ret = -1; + goto end; + } + + *_event_rule = rule; + rule = NULL; + ret = offset; +end: + lttng_event_rule_destroy(rule); + return ret; +} + +enum lttng_event_rule_status lttng_event_rule_uprobe_set_location( + struct lttng_event_rule *rule, + const struct lttng_userspace_probe_location *location) +{ + struct lttng_userspace_probe_location *location_copy = NULL; + struct lttng_event_rule_uprobe *uprobe; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_UPROBE_EVENT_RULE(rule) || !location) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + uprobe = container_of(rule, struct lttng_event_rule_uprobe, parent); + location_copy = lttng_userspace_probe_location_copy(location); + if (!location_copy) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + + if (uprobe->location) { + lttng_userspace_probe_location_destroy(uprobe->location); + } + + uprobe->location = location_copy; + location_copy = NULL; +end: + lttng_userspace_probe_location_destroy(location_copy); + return status; +} + +enum lttng_event_rule_status lttng_event_rule_uprobe_get_location( + const struct lttng_event_rule *rule, + const struct lttng_userspace_probe_location **location) +{ + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_UPROBE_EVENT_RULE(rule) || !location) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + *location = lttng_event_rule_uprobe_get_location_no_const(rule); + if (!*location) { + status = LTTNG_EVENT_RULE_STATUS_UNSET; + goto end; + } + +end: + return status; +} + +LTTNG_HIDDEN +struct lttng_userspace_probe_location * +lttng_event_rule_uprobe_get_location_no_const( + const struct lttng_event_rule *rule) +{ + struct lttng_event_rule_uprobe *uprobe; + assert(rule); + uprobe = container_of(rule, struct lttng_event_rule_uprobe, parent); + + return uprobe->location; +} + +enum lttng_event_rule_status lttng_event_rule_uprobe_set_name( + struct lttng_event_rule *rule, const char *name) +{ + char *name_copy = NULL; + struct lttng_event_rule_uprobe *uprobe; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_UPROBE_EVENT_RULE(rule) || !name || + strlen(name) == 0) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + uprobe = container_of(rule, struct lttng_event_rule_uprobe, parent); + name_copy = strdup(name); + if (!name_copy) { + status = LTTNG_EVENT_RULE_STATUS_ERROR; + goto end; + } + + if (uprobe->name) { + free(uprobe->name); + } + + uprobe->name = name_copy; + name_copy = NULL; +end: + return status; +} + +enum lttng_event_rule_status lttng_event_rule_uprobe_get_name( + const struct lttng_event_rule *rule, const char **name) +{ + struct lttng_event_rule_uprobe *uprobe; + enum lttng_event_rule_status status = LTTNG_EVENT_RULE_STATUS_OK; + + if (!rule || !IS_UPROBE_EVENT_RULE(rule) || !name) { + status = LTTNG_EVENT_RULE_STATUS_INVALID; + goto end; + } + + uprobe = container_of(rule, struct lttng_event_rule_uprobe, parent); + if (!uprobe->name) { + status = LTTNG_EVENT_RULE_STATUS_UNSET; + goto end; + } + + *name = uprobe->name; +end: + return status; +} diff --git a/src/common/filter.c b/src/common/filter.c index fc16f1d52..5d333375d 100644 --- a/src/common/filter.c +++ b/src/common/filter.c @@ -16,7 +16,7 @@ struct bytecode_symbol_iterator { LTTNG_HIDDEN struct bytecode_symbol_iterator *bytecode_symbol_iterator_create( - struct lttng_filter_bytecode *bytecode) + struct lttng_bytecode *bytecode) { struct bytecode_symbol_iterator *it = NULL; diff --git a/src/common/filter.h b/src/common/filter.h index 2cbdd14a9..a03b1d9fe 100644 --- a/src/common/filter.h +++ b/src/common/filter.h @@ -18,7 +18,7 @@ struct bytecode_symbol_iterator; */ LTTNG_HIDDEN struct bytecode_symbol_iterator *bytecode_symbol_iterator_create( - struct lttng_filter_bytecode *bytecode); + struct lttng_bytecode *bytecode); /* * Advance iterator of one element. diff --git a/src/lib/lttng-ctl/filter/Makefile.am b/src/common/filter/Makefile.am similarity index 96% rename from src/lib/lttng-ctl/filter/Makefile.am rename to src/common/filter/Makefile.am index cac4cb175..261acc086 100644 --- a/src/lib/lttng-ctl/filter/Makefile.am +++ b/src/common/filter/Makefile.am @@ -19,7 +19,6 @@ libfilter_la_SOURCES = \ filter-visitor-ir-normalize-glob-patterns.c \ filter-visitor-generate-bytecode.c \ filter-ast.h \ - filter-bytecode.h \ filter-ir.h \ memstream.h libfilter_la_CFLAGS = -include filter-symbols.h $(AM_CFLAGS) @@ -63,4 +62,6 @@ all-local: filter-lexer.c endif # HAVE_FLEX filter_grammar_test_SOURCES = filter-grammar-test.c -filter_grammar_test_LDADD = libfilter.la +filter_grammar_test_LDADD = \ + libfilter.la \ + ../bytecode/libbytecode.la diff --git a/src/lib/lttng-ctl/filter/filter-ast.h b/src/common/filter/filter-ast.h similarity index 94% rename from src/lib/lttng-ctl/filter/filter-ast.h rename to src/common/filter/filter-ast.h index 926d8dd64..93f9b9b25 100644 --- a/src/lib/lttng-ctl/filter/filter-ast.h +++ b/src/common/filter/filter-ast.h @@ -17,6 +17,7 @@ */ #include +#include #include #define printf_debug(fmt, args...) \ @@ -157,13 +158,15 @@ struct filter_parser_ctx { struct filter_ast *ast; struct cds_list_head allocated_strings; struct ir_op *ir_root; - struct lttng_filter_bytecode_alloc *bytecode; - struct lttng_filter_bytecode_alloc *bytecode_reloc; + struct lttng_bytecode_alloc *bytecode; + struct lttng_bytecode_alloc *bytecode_reloc; }; struct filter_parser_ctx *filter_parser_ctx_alloc(FILE *input); void filter_parser_ctx_free(struct filter_parser_ctx *parser_ctx); int filter_parser_ctx_append_ast(struct filter_parser_ctx *parser_ctx); +int filter_parser_ctx_create_from_filter_expression( + const char *filter_expression, struct filter_parser_ctx **ctxp); static inline struct filter_ast *filter_parser_get_ast(struct filter_parser_ctx *parser_ctx) diff --git a/src/lib/lttng-ctl/filter/filter-grammar-test.c b/src/common/filter/filter-grammar-test.c similarity index 98% rename from src/lib/lttng-ctl/filter/filter-grammar-test.c rename to src/common/filter/filter-grammar-test.c index 70656e1a8..9029fa65e 100644 --- a/src/lib/lttng-ctl/filter/filter-grammar-test.c +++ b/src/common/filter/filter-grammar-test.c @@ -16,9 +16,9 @@ #include #include #include +#include "common/bytecode/bytecode.h" #include "filter-ast.h" #include "filter-parser.h" -#include "filter-bytecode.h" int main(int argc, char **argv) { diff --git a/src/lib/lttng-ctl/filter/filter-ir.h b/src/common/filter/filter-ir.h similarity index 60% rename from src/lib/lttng-ctl/filter/filter-ir.h rename to src/common/filter/filter-ir.h index d62c0ee0c..5775e8004 100644 --- a/src/lib/lttng-ctl/filter/filter-ir.h +++ b/src/common/filter/filter-ir.h @@ -31,6 +31,29 @@ enum ir_data_type { IR_DATA_EXPRESSION, }; +static inline +const char *ir_data_type_str(enum ir_data_type type) +{ + switch (type) { + case IR_DATA_UNKNOWN: + return "IR_DATA_UNKNOWN"; + case IR_DATA_STRING: + return "IR_DATA_STRING"; + case IR_DATA_NUMERIC: + return "IR_DATA_NUMERIC"; + case IR_DATA_FLOAT: + return "IR_DATA_FLOAT"; + case IR_DATA_FIELD_REF: + return "IR_DATA_FIELD_REF"; + case IR_DATA_GET_CONTEXT_REF: + return "IR_DATA_GET_CONTEXT_REF"; + case IR_DATA_EXPRESSION: + return "IR_DATA_EXPRESSION"; + default: + abort(); + } +} + enum ir_op_type { IR_OP_UNKNOWN = 0, IR_OP_ROOT, @@ -40,6 +63,27 @@ enum ir_op_type { IR_OP_LOGICAL, }; +static inline +const char *ir_op_type_str(enum ir_op_type type) +{ + switch (type) { + case IR_OP_UNKNOWN: + return "IR_OP_UNKNOWN"; + case IR_OP_ROOT: + return "IR_OP_ROOT"; + case IR_OP_LOAD: + return "IR_OP_LOAD"; + case IR_OP_UNARY: + return "IR_OP_UNARY"; + case IR_OP_BINARY: + return "IR_OP_BINARY"; + case IR_OP_LOGICAL: + return "IR_OP_LOGICAL"; + default: + abort(); + } +} + /* left or right child */ enum ir_side { IR_SIDE_UNKNOWN = 0, @@ -71,6 +115,27 @@ enum ir_load_expression_type { IR_LOAD_EXPRESSION_LOAD_FIELD, }; +static inline +const char *ir_load_expression_type_str(enum ir_load_expression_type type) +{ + switch (type) { + case IR_LOAD_EXPRESSION_GET_CONTEXT_ROOT: + return "IR_LOAD_EXPRESSION_GET_CONTEXT_ROOT"; + case IR_LOAD_EXPRESSION_GET_APP_CONTEXT_ROOT: + return "IR_LOAD_EXPRESSION_GET_APP_CONTEXT_ROOT"; + case IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT: + return "IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT"; + case IR_LOAD_EXPRESSION_GET_SYMBOL: + return "IR_LOAD_EXPRESSION_GET_SYMBOL"; + case IR_LOAD_EXPRESSION_GET_INDEX: + return "IR_LOAD_EXPRESSION_GET_INDEX"; + case IR_LOAD_EXPRESSION_LOAD_FIELD: + return "IR_LOAD_EXPRESSION_LOAD_FIELD"; + default: + abort(); + } +} + struct ir_load_expression_op { struct ir_load_expression_op *next; enum ir_load_expression_type type; diff --git a/src/lib/lttng-ctl/filter/filter-lexer.l b/src/common/filter/filter-lexer.l similarity index 100% rename from src/lib/lttng-ctl/filter/filter-lexer.l rename to src/common/filter/filter-lexer.l diff --git a/src/lib/lttng-ctl/filter/filter-parser.y b/src/common/filter/filter-parser.y similarity index 83% rename from src/lib/lttng-ctl/filter/filter-parser.y rename to src/common/filter/filter-parser.y index ba2d3007b..dc94a9813 100644 --- a/src/lib/lttng-ctl/filter/filter-parser.y +++ b/src/common/filter/filter-parser.y @@ -18,8 +18,10 @@ #include #include #include +#include "common/bytecode/bytecode.h" #include "filter-ast.h" #include "filter-parser.h" +#include "memstream.h" #include @@ -28,6 +30,20 @@ #define WIDTH_x64_SCANF_IS_A_BROKEN_API "17" #define WIDTH_lg_SCANF_IS_A_BROKEN_API "4096" /* Hugely optimistic approximation */ +#ifdef DEBUG +static const int print_xml = 1; +#define dbg_printf(fmt, args...) \ + printf("[debug filter_parser] " fmt, ## args) +#else +static const int print_xml = 0; +#define dbg_printf(fmt, args...) \ +do { \ + /* do nothing but check printf format */ \ + if (0) \ + printf("[debug filter_parser] " fmt, ## args); \ +} while (0) +#endif + LTTNG_HIDDEN int yydebug; LTTNG_HIDDEN @@ -288,6 +304,123 @@ void filter_parser_ctx_free(struct filter_parser_ctx *parser_ctx) free(parser_ctx); } +LTTNG_HIDDEN +int filter_parser_ctx_create_from_filter_expression( + const char *filter_expression, struct filter_parser_ctx **ctxp) +{ + int ret; + struct filter_parser_ctx *ctx = NULL; + FILE *fmem = NULL; + + assert(filter_expression); + assert(ctxp); + + /* + * Casting const to non-const, as the underlying function will use it in + * read-only mode. + */ + fmem = lttng_fmemopen((void *) filter_expression, + strlen(filter_expression), "r"); + if (!fmem) { + fprintf(stderr, "Error opening memory as stream\n"); + ret = -LTTNG_ERR_FILTER_NOMEM; + goto error; + } + ctx = filter_parser_ctx_alloc(fmem); + if (!ctx) { + fprintf(stderr, "Error allocating parser\n"); + ret = -LTTNG_ERR_FILTER_NOMEM; + goto filter_alloc_error; + } + ret = filter_parser_ctx_append_ast(ctx); + if (ret) { + fprintf(stderr, "Parse error\n"); + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + if (print_xml) { + ret = filter_visitor_print_xml(ctx, stdout, 0); + if (ret) { + fflush(stdout); + fprintf(stderr, "XML print error\n"); + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + } + + dbg_printf("Generating IR... "); + fflush(stdout); + ret = filter_visitor_ir_generate(ctx); + if (ret) { + fprintf(stderr, "Generate IR error\n"); + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + dbg_printf("done\n"); + + dbg_printf("Validating IR... "); + fflush(stdout); + ret = filter_visitor_ir_check_binary_op_nesting(ctx); + if (ret) { + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + + /* Normalize globbing patterns in the expression. */ + ret = filter_visitor_ir_normalize_glob_patterns(ctx); + if (ret) { + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + + /* Validate strings used as literals in the expression. */ + ret = filter_visitor_ir_validate_string(ctx); + if (ret) { + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + + /* Validate globbing patterns in the expression. */ + ret = filter_visitor_ir_validate_globbing(ctx); + if (ret) { + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + + dbg_printf("done\n"); + + dbg_printf("Generating bytecode... "); + fflush(stdout); + ret = filter_visitor_bytecode_generate(ctx); + if (ret) { + fprintf(stderr, "Generate bytecode error\n"); + ret = -LTTNG_ERR_FILTER_INVAL; + goto parse_error; + } + dbg_printf("done\n"); + dbg_printf("Size of bytecode generated: %u bytes.\n", + bytecode_get_len(&ctx->bytecode->b)); + + /* No need to keep the memory stream. */ + if (fclose(fmem) != 0) { + fprintf(stderr, "fclose (%d) \n", errno); + ret = -LTTNG_ERR_FILTER_INVAL; + } + + *ctxp = ctx; + return 0; + +parse_error: + filter_ir_free(ctx); + filter_parser_ctx_free(ctx); +filter_alloc_error: + if (fclose(fmem) != 0) { + fprintf(stderr, "fclose (%d) \n", errno); + } +error: + return ret; +} + %} %code provides diff --git a/src/lib/lttng-ctl/filter/filter-symbols.h b/src/common/filter/filter-symbols.h similarity index 100% rename from src/lib/lttng-ctl/filter/filter-symbols.h rename to src/common/filter/filter-symbols.h diff --git a/src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c b/src/common/filter/filter-visitor-generate-bytecode.c similarity index 68% rename from src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c rename to src/common/filter/filter-visitor-generate-bytecode.c index 699273c3d..458b9d042 100644 --- a/src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c +++ b/src/common/filter/filter-visitor-generate-bytecode.c @@ -12,114 +12,24 @@ #include #include #include -#include -#include -#include "filter-bytecode.h" -#include "filter-ir.h" +#include "common/align.h" +#include "common/bytecode/bytecode.h" +#include "common/compat/string.h" +#include "common/macros.h" #include "filter-ast.h" - -#include +#include "filter-ir.h" #ifndef max_t #define max_t(type, a, b) ((type) ((a) > (b) ? (a) : (b))) #endif -#define INIT_ALLOC_SIZE 4 - static int recursive_visit_gen_bytecode(struct filter_parser_ctx *ctx, struct ir_op *node); -static inline int get_count_order(unsigned int count) -{ - int order; - - order = lttng_fls(count) - 1; - if (count & (count - 1)) - order++; - return order; -} - -static -int bytecode_init(struct lttng_filter_bytecode_alloc **fb) -{ - uint32_t alloc_len; - - alloc_len = sizeof(struct lttng_filter_bytecode_alloc) + INIT_ALLOC_SIZE; - *fb = calloc(alloc_len, 1); - if (!*fb) { - return -ENOMEM; - } else { - (*fb)->alloc_len = alloc_len; - return 0; - } -} - static -int32_t bytecode_reserve(struct lttng_filter_bytecode_alloc **fb, uint32_t align, uint32_t len) -{ - int32_t ret; - uint32_t padding = offset_align((*fb)->b.len, align); - uint32_t new_len = (*fb)->b.len + padding + len; - uint32_t new_alloc_len = sizeof(struct lttng_filter_bytecode_alloc) + new_len; - uint32_t old_alloc_len = (*fb)->alloc_len; - - if (new_len > LTTNG_FILTER_MAX_LEN) - return -EINVAL; - - if (new_alloc_len > old_alloc_len) { - struct lttng_filter_bytecode_alloc *newptr; - - new_alloc_len = - max_t(uint32_t, 1U << get_count_order(new_alloc_len), old_alloc_len << 1); - newptr = realloc(*fb, new_alloc_len); - if (!newptr) - return -ENOMEM; - *fb = newptr; - /* We zero directly the memory from start of allocation. */ - memset(&((char *) *fb)[old_alloc_len], 0, new_alloc_len - old_alloc_len); - (*fb)->alloc_len = new_alloc_len; - } - (*fb)->b.len += padding; - ret = (*fb)->b.len; - (*fb)->b.len += len; - return ret; -} - -static -int bytecode_push(struct lttng_filter_bytecode_alloc **fb, const void *data, - uint32_t align, uint32_t len) -{ - int32_t offset; - - offset = bytecode_reserve(fb, align, len); - if (offset < 0) - return offset; - memcpy(&(*fb)->b.data[offset], data, len); - return 0; -} - -static -int bytecode_push_logical(struct lttng_filter_bytecode_alloc **fb, - struct logical_op *data, - uint32_t align, uint32_t len, - uint16_t *skip_offset) -{ - int32_t offset; - - offset = bytecode_reserve(fb, align, len); - if (offset < 0) - return offset; - memcpy(&(*fb)->b.data[offset], data, len); - *skip_offset = - (void *) &((struct logical_op *) &(*fb)->b.data[offset])->skip_offset - - (void *) &(*fb)->b.data[0]; - return 0; -} - -static -int bytecode_patch(struct lttng_filter_bytecode_alloc **fb, +int bytecode_patch(struct lttng_bytecode_alloc **fb, const void *data, uint16_t offset, uint32_t len) @@ -143,7 +53,7 @@ int visit_node_root(struct filter_parser_ctx *ctx, struct ir_op *node) return ret; /* Generate end of bytecode instruction */ - insn.op = FILTER_OP_RETURN; + insn.op = BYTECODE_OP_RETURN; return bytecode_push(&ctx->bytecode, &insn, 1, sizeof(insn)); } @@ -175,7 +85,7 @@ int append_str(char **s, const char *append) */ static int load_expression_legacy_match(const struct ir_load_expression *exp, - enum filter_op *op_type, + enum bytecode_op *op_type, char **symbol) { const struct ir_load_expression_op *op; @@ -184,21 +94,21 @@ int load_expression_legacy_match(const struct ir_load_expression *exp, op = exp->child; switch (op->type) { case IR_LOAD_EXPRESSION_GET_CONTEXT_ROOT: - *op_type = FILTER_OP_GET_CONTEXT_REF; + *op_type = BYTECODE_OP_GET_CONTEXT_REF; if (append_str(symbol, "$ctx.")) { return -ENOMEM; } need_dot = false; break; case IR_LOAD_EXPRESSION_GET_APP_CONTEXT_ROOT: - *op_type = FILTER_OP_GET_CONTEXT_REF; + *op_type = BYTECODE_OP_GET_CONTEXT_REF; if (append_str(symbol, "$app.")) { return -ENOMEM; } need_dot = false; break; case IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT: - *op_type = FILTER_OP_LOAD_FIELD_REF; + *op_type = BYTECODE_OP_LOAD_FIELD_REF; need_dot = false; break; @@ -250,7 +160,7 @@ int visit_node_load_expression_legacy(struct filter_parser_ctx *ctx, struct field_ref ref_offset; uint32_t reloc_offset_u32; uint16_t reloc_offset; - enum filter_op op_type; + enum bytecode_op op_type; char *symbol = NULL; int ret; @@ -328,100 +238,37 @@ int visit_node_load_expression(struct filter_parser_ctx *ctx, switch (op->type) { case IR_LOAD_EXPRESSION_GET_CONTEXT_ROOT: { - struct load_op *insn; - uint32_t insn_len = sizeof(struct load_op); - int ret; - - insn = calloc(insn_len, 1); - if (!insn) - return -ENOMEM; - insn->op = FILTER_OP_GET_CONTEXT_ROOT; - ret = bytecode_push(&ctx->bytecode, insn, 1, insn_len); - free(insn); + int ret = bytecode_push_get_context_root(&ctx->bytecode); if (ret) { return ret; } + break; } case IR_LOAD_EXPRESSION_GET_APP_CONTEXT_ROOT: { - struct load_op *insn; - uint32_t insn_len = sizeof(struct load_op); - int ret; - - insn = calloc(insn_len, 1); - if (!insn) - return -ENOMEM; - insn->op = FILTER_OP_GET_APP_CONTEXT_ROOT; - ret = bytecode_push(&ctx->bytecode, insn, 1, insn_len); - free(insn); + int ret = bytecode_push_get_app_context_root(&ctx->bytecode); if (ret) { return ret; } + break; } case IR_LOAD_EXPRESSION_GET_PAYLOAD_ROOT: { - struct load_op *insn; - uint32_t insn_len = sizeof(struct load_op); - int ret; - - insn = calloc(insn_len, 1); - if (!insn) - return -ENOMEM; - insn->op = FILTER_OP_GET_PAYLOAD_ROOT; - ret = bytecode_push(&ctx->bytecode, insn, 1, insn_len); - free(insn); + int ret = bytecode_push_get_payload_root(&ctx->bytecode); if (ret) { return ret; } + break; } case IR_LOAD_EXPRESSION_GET_SYMBOL: { - struct load_op *insn; - uint32_t insn_len = sizeof(struct load_op) - + sizeof(struct get_symbol); - struct get_symbol symbol_offset; - uint32_t reloc_offset_u32; - uint16_t reloc_offset; - uint32_t bytecode_reloc_offset_u32; - int ret; - - insn = calloc(insn_len, 1); - if (!insn) - return -ENOMEM; - insn->op = FILTER_OP_GET_SYMBOL; - bytecode_reloc_offset_u32 = - bytecode_get_len(&ctx->bytecode_reloc->b) - + sizeof(reloc_offset); - symbol_offset.offset = - (uint16_t) bytecode_reloc_offset_u32; - memcpy(insn->data, &symbol_offset, - sizeof(symbol_offset)); - /* reloc_offset points to struct load_op */ - reloc_offset_u32 = bytecode_get_len(&ctx->bytecode->b); - if (reloc_offset_u32 > LTTNG_FILTER_MAX_LEN - 1) { - free(insn); - return -EINVAL; - } - reloc_offset = (uint16_t) reloc_offset_u32; - ret = bytecode_push(&ctx->bytecode, insn, 1, insn_len); - if (ret) { - free(insn); - return ret; - } - /* append reloc */ - ret = bytecode_push(&ctx->bytecode_reloc, &reloc_offset, - 1, sizeof(reloc_offset)); - if (ret) { - free(insn); - return ret; - } - ret = bytecode_push(&ctx->bytecode_reloc, - op->u.symbol, - 1, strlen(op->u.symbol) + 1); - free(insn); + int ret = bytecode_push_get_symbol( + &ctx->bytecode, + &ctx->bytecode_reloc, + op->u.symbol); if (ret) { return ret; } @@ -429,20 +276,7 @@ int visit_node_load_expression(struct filter_parser_ctx *ctx, } case IR_LOAD_EXPRESSION_GET_INDEX: { - struct load_op *insn; - uint32_t insn_len = sizeof(struct load_op) - + sizeof(struct get_index_u64); - struct get_index_u64 index; - int ret; - - insn = calloc(insn_len, 1); - if (!insn) - return -ENOMEM; - insn->op = FILTER_OP_GET_INDEX_U64; - index.index = op->u.index; - memcpy(insn->data, &index, sizeof(index)); - ret = bytecode_push(&ctx->bytecode, insn, 1, insn_len); - free(insn); + int ret = bytecode_push_get_index_u64(&ctx->bytecode, op->u.index); if (ret) { return ret; } @@ -457,7 +291,7 @@ int visit_node_load_expression(struct filter_parser_ctx *ctx, insn = calloc(insn_len, 1); if (!insn) return -ENOMEM; - insn->op = FILTER_OP_LOAD_FIELD; + insn->op = BYTECODE_OP_LOAD_FIELD; ret = bytecode_push(&ctx->bytecode, insn, 1, insn_len); free(insn); if (ret) { @@ -500,7 +334,7 @@ int visit_node_load(struct filter_parser_ctx *ctx, struct ir_op *node) * that the appropriate matching function can be * called. Also, see comment below. */ - insn->op = FILTER_OP_LOAD_STAR_GLOB_STRING; + insn->op = BYTECODE_OP_LOAD_STAR_GLOB_STRING; break; default: /* @@ -513,7 +347,7 @@ int visit_node_load(struct filter_parser_ctx *ctx, struct ir_op *node) * can be anywhere in the string) is a special * case. */ - insn->op = FILTER_OP_LOAD_STRING; + insn->op = BYTECODE_OP_LOAD_STRING; break; } @@ -531,7 +365,7 @@ int visit_node_load(struct filter_parser_ctx *ctx, struct ir_op *node) insn = calloc(insn_len, 1); if (!insn) return -ENOMEM; - insn->op = FILTER_OP_LOAD_S64; + insn->op = BYTECODE_OP_LOAD_S64; memcpy(insn->data, &node->u.load.u.num, sizeof(int64_t)); ret = bytecode_push(&ctx->bytecode, insn, 1, insn_len); free(insn); @@ -546,7 +380,7 @@ int visit_node_load(struct filter_parser_ctx *ctx, struct ir_op *node) insn = calloc(insn_len, 1); if (!insn) return -ENOMEM; - insn->op = FILTER_OP_LOAD_DOUBLE; + insn->op = BYTECODE_OP_LOAD_DOUBLE; memcpy(insn->data, &node->u.load.u.flt, sizeof(double)); ret = bytecode_push(&ctx->bytecode, insn, 1, insn_len); free(insn); @@ -579,13 +413,13 @@ int visit_node_unary(struct filter_parser_ctx *ctx, struct ir_op *node) /* Nothing to do. */ return 0; case AST_UNARY_MINUS: - insn.op = FILTER_OP_UNARY_MINUS; + insn.op = BYTECODE_OP_UNARY_MINUS; return bytecode_push(&ctx->bytecode, &insn, 1, sizeof(insn)); case AST_UNARY_NOT: - insn.op = FILTER_OP_UNARY_NOT; + insn.op = BYTECODE_OP_UNARY_NOT; return bytecode_push(&ctx->bytecode, &insn, 1, sizeof(insn)); case AST_UNARY_BIT_NOT: - insn.op = FILTER_OP_UNARY_BIT_NOT; + insn.op = BYTECODE_OP_UNARY_BIT_NOT; return bytecode_push(&ctx->bytecode, &insn, 1, sizeof(insn)); } } @@ -622,53 +456,53 @@ int visit_node_binary(struct filter_parser_ctx *ctx, struct ir_op *node) return -EINVAL; case AST_OP_MUL: - insn.op = FILTER_OP_MUL; + insn.op = BYTECODE_OP_MUL; break; case AST_OP_DIV: - insn.op = FILTER_OP_DIV; + insn.op = BYTECODE_OP_DIV; break; case AST_OP_MOD: - insn.op = FILTER_OP_MOD; + insn.op = BYTECODE_OP_MOD; break; case AST_OP_PLUS: - insn.op = FILTER_OP_PLUS; + insn.op = BYTECODE_OP_PLUS; break; case AST_OP_MINUS: - insn.op = FILTER_OP_MINUS; + insn.op = BYTECODE_OP_MINUS; break; case AST_OP_BIT_RSHIFT: - insn.op = FILTER_OP_BIT_RSHIFT; + insn.op = BYTECODE_OP_BIT_RSHIFT; break; case AST_OP_BIT_LSHIFT: - insn.op = FILTER_OP_BIT_LSHIFT; + insn.op = BYTECODE_OP_BIT_LSHIFT; break; case AST_OP_BIT_AND: - insn.op = FILTER_OP_BIT_AND; + insn.op = BYTECODE_OP_BIT_AND; break; case AST_OP_BIT_OR: - insn.op = FILTER_OP_BIT_OR; + insn.op = BYTECODE_OP_BIT_OR; break; case AST_OP_BIT_XOR: - insn.op = FILTER_OP_BIT_XOR; + insn.op = BYTECODE_OP_BIT_XOR; break; case AST_OP_EQ: - insn.op = FILTER_OP_EQ; + insn.op = BYTECODE_OP_EQ; break; case AST_OP_NE: - insn.op = FILTER_OP_NE; + insn.op = BYTECODE_OP_NE; break; case AST_OP_GT: - insn.op = FILTER_OP_GT; + insn.op = BYTECODE_OP_GT; break; case AST_OP_LT: - insn.op = FILTER_OP_LT; + insn.op = BYTECODE_OP_LT; break; case AST_OP_GE: - insn.op = FILTER_OP_GE; + insn.op = BYTECODE_OP_GE; break; case AST_OP_LE: - insn.op = FILTER_OP_LE; + insn.op = BYTECODE_OP_LE; break; } return bytecode_push(&ctx->bytecode, &insn, 1, sizeof(insn)); @@ -699,9 +533,9 @@ int visit_node_logical(struct filter_parser_ctx *ctx, struct ir_op *node) if (node->u.binary.left->data_type == IR_DATA_FIELD_REF || node->u.binary.left->data_type == IR_DATA_GET_CONTEXT_REF || node->u.binary.left->data_type == IR_DATA_EXPRESSION) { - cast_insn.op = FILTER_OP_CAST_TO_S64; + cast_insn.op = BYTECODE_OP_CAST_TO_S64; } else { - cast_insn.op = FILTER_OP_CAST_DOUBLE_TO_S64; + cast_insn.op = BYTECODE_OP_CAST_DOUBLE_TO_S64; } ret = bytecode_push(&ctx->bytecode, &cast_insn, 1, sizeof(cast_insn)); @@ -715,10 +549,10 @@ int visit_node_logical(struct filter_parser_ctx *ctx, struct ir_op *node) return -EINVAL; case AST_OP_AND: - insn.op = FILTER_OP_AND; + insn.op = BYTECODE_OP_AND; break; case AST_OP_OR: - insn.op = FILTER_OP_OR; + insn.op = BYTECODE_OP_OR; break; } insn.skip_offset = (uint16_t) -1UL; /* Temporary */ @@ -740,9 +574,9 @@ int visit_node_logical(struct filter_parser_ctx *ctx, struct ir_op *node) if (node->u.binary.right->data_type == IR_DATA_FIELD_REF || node->u.binary.right->data_type == IR_DATA_GET_CONTEXT_REF || node->u.binary.right->data_type == IR_DATA_EXPRESSION) { - cast_insn.op = FILTER_OP_CAST_TO_S64; + cast_insn.op = BYTECODE_OP_CAST_TO_S64; } else { - cast_insn.op = FILTER_OP_CAST_DOUBLE_TO_S64; + cast_insn.op = BYTECODE_OP_CAST_DOUBLE_TO_S64; } ret = bytecode_push(&ctx->bytecode, &cast_insn, 1, sizeof(cast_insn)); diff --git a/src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c b/src/common/filter/filter-visitor-generate-ir.c similarity index 100% rename from src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c rename to src/common/filter/filter-visitor-generate-ir.c diff --git a/src/lib/lttng-ctl/filter/filter-visitor-ir-check-binary-comparator.c b/src/common/filter/filter-visitor-ir-check-binary-comparator.c similarity index 100% rename from src/lib/lttng-ctl/filter/filter-visitor-ir-check-binary-comparator.c rename to src/common/filter/filter-visitor-ir-check-binary-comparator.c diff --git a/src/lib/lttng-ctl/filter/filter-visitor-ir-check-binary-op-nesting.c b/src/common/filter/filter-visitor-ir-check-binary-op-nesting.c similarity index 100% rename from src/lib/lttng-ctl/filter/filter-visitor-ir-check-binary-op-nesting.c rename to src/common/filter/filter-visitor-ir-check-binary-op-nesting.c diff --git a/src/lib/lttng-ctl/filter/filter-visitor-ir-normalize-glob-patterns.c b/src/common/filter/filter-visitor-ir-normalize-glob-patterns.c similarity index 100% rename from src/lib/lttng-ctl/filter/filter-visitor-ir-normalize-glob-patterns.c rename to src/common/filter/filter-visitor-ir-normalize-glob-patterns.c diff --git a/src/lib/lttng-ctl/filter/filter-visitor-ir-validate-globbing.c b/src/common/filter/filter-visitor-ir-validate-globbing.c similarity index 100% rename from src/lib/lttng-ctl/filter/filter-visitor-ir-validate-globbing.c rename to src/common/filter/filter-visitor-ir-validate-globbing.c diff --git a/src/lib/lttng-ctl/filter/filter-visitor-ir-validate-string.c b/src/common/filter/filter-visitor-ir-validate-string.c similarity index 100% rename from src/lib/lttng-ctl/filter/filter-visitor-ir-validate-string.c rename to src/common/filter/filter-visitor-ir-validate-string.c diff --git a/src/lib/lttng-ctl/filter/filter-visitor-xml.c b/src/common/filter/filter-visitor-xml.c similarity index 100% rename from src/lib/lttng-ctl/filter/filter-visitor-xml.c rename to src/common/filter/filter-visitor-xml.c diff --git a/src/lib/lttng-ctl/filter/memstream.h b/src/common/filter/memstream.h similarity index 100% rename from src/lib/lttng-ctl/filter/memstream.h rename to src/common/filter/memstream.h diff --git a/src/common/kernel-ctl/kernel-ctl.c b/src/common/kernel-ctl/kernel-ctl.c index 10e281d69..272d9ed1f 100644 --- a/src/common/kernel-ctl/kernel-ctl.c +++ b/src/common/kernel-ctl/kernel-ctl.c @@ -418,7 +418,41 @@ int kernctl_stop_session(int fd) LTTNG_KERNEL_SESSION_STOP); } -int kernctl_filter(int fd, struct lttng_filter_bytecode *filter) +int kernctl_create_trigger_group(int fd) +{ + return LTTNG_IOCTL_NO_CHECK(fd, LTTNG_KERNEL_TRIGGER_GROUP_CREATE); +} + +int kernctl_create_trigger_group_notification_fd(int group_fd) +{ + return LTTNG_IOCTL_NO_CHECK(group_fd, LTTNG_KERNEL_TRIGGER_GROUP_NOTIFICATION_FD); +} + +int kernctl_create_trigger(int group_fd, struct lttng_kernel_trigger *trigger) +{ + return LTTNG_IOCTL_NO_CHECK(group_fd, LTTNG_KERNEL_TRIGGER_CREATE, trigger); +} + +int kernctl_capture(int fd, const struct lttng_bytecode *capture) +{ + struct lttng_kernel_capture_bytecode *kb; + uint32_t len; + int ret; + + /* Translate bytecode to kernel bytecode */ + kb = zmalloc(sizeof(*kb) + capture->len); + if (!kb) + return -ENOMEM; + kb->len = len = capture->len; + kb->reloc_offset = capture->reloc_table_offset; + kb->seqnum = capture->seqnum; + memcpy(kb->data, capture->data, len); + ret = LTTNG_IOCTL_CHECK(fd, LTTNG_KERNEL_CAPTURE, kb); + free(kb); + return ret; +} + +int kernctl_filter(int fd, const struct lttng_bytecode *filter) { struct lttng_kernel_filter_bytecode *kb; uint32_t len; diff --git a/src/common/kernel-ctl/kernel-ctl.h b/src/common/kernel-ctl/kernel-ctl.h index 49925ea6c..3d052f57c 100644 --- a/src/common/kernel-ctl/kernel-ctl.h +++ b/src/common/kernel-ctl/kernel-ctl.h @@ -28,9 +28,16 @@ int kernctl_disable(int fd); int kernctl_start_session(int fd); int kernctl_stop_session(int fd); +int kernctl_create_trigger_group(int fd); + +/* Apply on trigger_group FD*/ +int kernctl_create_trigger_group_notification_fd(int fd); +int kernctl_create_trigger(int fd, struct lttng_kernel_trigger *trigger); + /* Apply on event FD */ -int kernctl_filter(int fd, struct lttng_filter_bytecode *filter); +int kernctl_filter(int fd, const struct lttng_bytecode *filter); int kernctl_add_callsite(int fd, struct lttng_kernel_event_callsite *callsite); +int kernctl_capture(int fd, const struct lttng_bytecode *capture); int kernctl_tracepoint_list(int fd); int kernctl_syscall_list(int fd); diff --git a/src/common/kernel-ctl/kernel-ioctl.h b/src/common/kernel-ctl/kernel-ioctl.h index ed6555e10..8b3ee3414 100644 --- a/src/common/kernel-ctl/kernel-ioctl.h +++ b/src/common/kernel-ctl/kernel-ioctl.h @@ -118,6 +118,14 @@ #define LTTNG_KERNEL_TRACER_ABI_VERSION \ _IOR(0xF6, 0x4B, struct lttng_kernel_tracer_abi_version) +#define LTTNG_KERNEL_TRIGGER_GROUP_CREATE _IO(0xF6, 0x4C) + +/* Trigger group file descriptor ioctl */ +#define LTTNG_KERNEL_TRIGGER_GROUP_NOTIFICATION_FD _IO(0xF6, 0x30) +#define LTTNG_KERNEL_TRIGGER_CREATE \ + _IOW(0xF6, 0x31, struct lttng_kernel_trigger) +#define LTTNG_KERNEL_CAPTURE _IO(0xF6, 0x32) + /* Session FD ioctl */ #define LTTNG_KERNEL_METADATA \ _IOW(0xF6, 0x54, struct lttng_kernel_channel) diff --git a/src/common/lttng-kernel.h b/src/common/lttng-kernel.h index d5904e79f..6ea76e4d9 100644 --- a/src/common/lttng-kernel.h +++ b/src/common/lttng-kernel.h @@ -150,6 +150,39 @@ struct lttng_kernel_event { } u; } LTTNG_PACKED; +#define LTTNG_KERNEL_TRIGGER_PADDING1 16 +#define LTTNG_KERNEL_TRIGGER_PADDING2 LTTNG_KERNEL_SYM_NAME_LEN + 32 +struct lttng_kernel_trigger { + uint64_t id; + char name[LTTNG_KERNEL_SYM_NAME_LEN]; /* event name */ + enum lttng_kernel_instrumentation instrumentation; + char padding[LTTNG_KERNEL_TRIGGER_PADDING1]; + + /* Per instrumentation type configuration */ + union { + struct lttng_kernel_kretprobe kretprobe; + struct lttng_kernel_kprobe kprobe; + struct lttng_kernel_uprobe uprobe; + struct lttng_kernel_function ftrace; + char padding[LTTNG_KERNEL_TRIGGER_PADDING2]; + } u; +} LTTNG_PACKED; + +#define LTTNG_KERNEL_TRIGGER_NOTIFICATION_PADDING 32 +struct lttng_kernel_trigger_notification { + uint64_t id; + uint16_t capture_buf_size; + char padding[LTTNG_KERNEL_TRIGGER_NOTIFICATION_PADDING]; +} LTTNG_PACKED; + +#define LTTNG_KERNEL_CAPTURE_BYTECODE_MAX_LEN 65536 +struct lttng_kernel_capture_bytecode { + uint32_t len; + uint32_t reloc_offset; + uint64_t seqnum; + char data[0]; +} LTTNG_PACKED; + struct lttng_kernel_tracer_version { uint32_t major; uint32_t minor; diff --git a/src/common/notification.c b/src/common/notification.c index 6b32bd9ac..5a50e1584 100644 --- a/src/common/notification.c +++ b/src/common/notification.c @@ -51,8 +51,7 @@ int lttng_notification_serialize(const struct lttng_notification *notification, } size_before_payload = buf->size; - ret = lttng_condition_serialize(notification->condition, - buf); + ret = lttng_condition_serialize(notification->condition, buf, NULL); if (ret) { goto end; } @@ -105,8 +104,8 @@ ssize_t lttng_notification_create_from_buffer( /* struct lttng_evaluation */ evaluation_view = lttng_buffer_view_from_view(&condition_view, condition_size, -1); - evaluation_size = lttng_evaluation_create_from_buffer(&evaluation_view, - &evaluation); + evaluation_size = lttng_evaluation_create_from_buffer(condition, + &evaluation_view, &evaluation); if (evaluation_size < 0) { ret = evaluation_size; goto end; diff --git a/src/common/runas.c b/src/common/runas.c index 5d63f640e..51919405e 100644 --- a/src/common/runas.c +++ b/src/common/runas.c @@ -23,17 +23,22 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include #include +#include +#include + #include "runas.h" struct run_as_data; @@ -57,6 +62,7 @@ enum run_as_cmd { RUN_AS_RENAMEAT, RUN_AS_EXTRACT_ELF_SYMBOL_OFFSET, RUN_AS_EXTRACT_SDT_PROBE_OFFSETS, + RUN_AS_GENERATE_FILTER_BYTECODE, }; struct run_as_mkdir_data { @@ -94,6 +100,10 @@ struct run_as_extract_sdt_probe_offsets_data { char provider_name[LTTNG_SYMBOL_NAME_LEN]; } LTTNG_PACKED; +struct run_as_generate_filter_bytecode_data { + char filter_expression[LTTNG_FILTER_MAX_LEN]; +} LTTNG_PATCKED; + struct run_as_rename_data { /* * [0] = old_dirfd @@ -117,6 +127,11 @@ struct run_as_extract_sdt_probe_offsets_ret { uint64_t offsets[LTTNG_KERNEL_MAX_UPROBE_NUM]; } LTTNG_PACKED; +struct run_as_generate_filter_bytecode_ret { + /* A lttng_bytecode_filter strcut with "dynamic" payload */ + char bytecode[LTTNG_FILTER_MAX_LEN]; +} LTTNG_PACKED; + struct run_as_data { enum run_as_cmd cmd; union { @@ -127,6 +142,7 @@ struct run_as_data { struct run_as_rename_data rename; struct run_as_extract_elf_symbol_offset_data extract_elf_symbol_offset; struct run_as_extract_sdt_probe_offsets_data extract_sdt_probe_offsets; + struct run_as_generate_filter_bytecode_data generate_filter_bytecode; } u; uid_t uid; gid_t gid; @@ -153,6 +169,7 @@ struct run_as_ret { struct run_as_open_ret open; struct run_as_extract_elf_symbol_offset_ret extract_elf_symbol_offset; struct run_as_extract_sdt_probe_offsets_ret extract_sdt_probe_offsets; + struct run_as_generate_filter_bytecode_ret generate_filter_bytecode; } u; int _errno; bool _error; @@ -306,6 +323,13 @@ static const struct run_as_command_properties command_properties[] = { .out_fd_count = 0, .use_cwd_fd = false, }, + [RUN_AS_GENERATE_FILTER_BYTECODE] = { + .in_fds_offset = -1, + .in_fd_count = 0, + .out_fds_offset = -1, + .out_fd_count = 0, + .use_cwd_fd = false, + }, }; struct run_as_worker { @@ -619,6 +643,47 @@ int _extract_sdt_probe_offsets(struct run_as_data *data, } #endif +static +int _generate_filter_bytecode(struct run_as_data *data, + struct run_as_ret *ret_value) { + int ret = 0; + const char *filter_expression = NULL; + struct filter_parser_ctx *ctx = NULL; + + ret_value->_error = false; + + filter_expression = data->u.generate_filter_bytecode.filter_expression; + + if (lttng_strnlen(filter_expression, LTTNG_FILTER_MAX_LEN - 1) == LTTNG_FILTER_MAX_LEN - 1) { + ret_value->_error = true; + ret = -1; + goto end; + } + + ret = filter_parser_ctx_create_from_filter_expression(filter_expression, &ctx); + if (ret < 0) { + ret_value->_error = true; + ret = -1; + goto end; + } + + DBG("Size of bytecode generated: %u bytes.", + bytecode_get_len(&ctx->bytecode->b)); + + /* Copy the lttng_bytecode_filter object to the return structure */ + memcpy(ret_value->u.generate_filter_bytecode.bytecode, + &ctx->bytecode->b, + sizeof(ctx->bytecode->b) + + bytecode_get_len(&ctx->bytecode->b)); + +end: + if (ctx) { + filter_bytecode_free(ctx); + filter_ir_free(ctx); + filter_parser_ctx_free(ctx); + } + return ret; +} static run_as_fct run_as_enum_to_fct(enum run_as_cmd cmd) { @@ -648,6 +713,8 @@ run_as_fct run_as_enum_to_fct(enum run_as_cmd cmd) return _extract_elf_symbol_offset; case RUN_AS_EXTRACT_SDT_PROBE_OFFSETS: return _extract_sdt_probe_offsets; + case RUN_AS_GENERATE_FILTER_BYTECODE: + return _generate_filter_bytecode; default: ERR("Unknown command %d", (int) cmd); return NULL; @@ -660,6 +727,11 @@ int do_send_fds(int sock, const int *fds, unsigned int fd_count) ssize_t len; unsigned int i; + if (fd_count == 0) { + /* Nothing to send */ + return 0; + } + for (i = 0; i < fd_count; i++) { if (fds[i] < 0) { ERR("Attempt to send invalid file descriptor to master (fd = %i)", @@ -680,6 +752,11 @@ int do_recv_fds(int sock, int *fds, unsigned int fd_count) unsigned int i; ssize_t len; + if (fd_count == 0) { + /* Nothing to receive */ + goto end; + } + len = lttcomm_recv_fds_unix_sock(sock, fds, fd_count); if (len == 0) { ret = -1; @@ -1726,6 +1803,50 @@ error: return ret; } +LTTNG_HIDDEN +int run_as_generate_filter_bytecode(const char *filter_expression, + uid_t uid, + gid_t gid, + struct lttng_bytecode **bytecode) +{ + int ret; + struct run_as_data data = {}; + struct run_as_ret run_as_ret = {}; + const struct lttng_bytecode *view_bytecode = NULL; + struct lttng_bytecode *local_bytecode = NULL; + + DBG3("generate_filter_bytecode() from expression=\"%s\" for uid %d and gid %d", + filter_expression, (int) uid, (int) gid); + + ret = lttng_strncpy(data.u.generate_filter_bytecode.filter_expression, filter_expression, + sizeof(data.u.generate_filter_bytecode.filter_expression)); + if (ret) { + goto error; + } + + run_as(RUN_AS_GENERATE_FILTER_BYTECODE, &data, &run_as_ret, uid, gid); + errno = run_as_ret._errno; + if (run_as_ret._error) { + ret = -1; + goto error; + } + + view_bytecode = (const struct lttng_bytecode *) run_as_ret.u.generate_filter_bytecode.bytecode; + + local_bytecode = zmalloc(sizeof(*local_bytecode) + view_bytecode->len); + if (!local_bytecode) { + ret = -ENOMEM; + goto error; + } + + memcpy(local_bytecode, run_as_ret.u.generate_filter_bytecode.bytecode, + sizeof(*local_bytecode) + view_bytecode->len); + *bytecode = local_bytecode; +error: + return ret; + +} + LTTNG_HIDDEN int run_as_create_worker(const char *procname, post_fork_cleanup_cb clean_up_func, diff --git a/src/common/runas.h b/src/common/runas.h index 619d3d0b3..06e41cd01 100644 --- a/src/common/runas.h +++ b/src/common/runas.h @@ -14,6 +14,7 @@ #include #include +#include /* * The run-as process is launched by forking without an exec*() call. This means @@ -70,6 +71,11 @@ int run_as_extract_sdt_probe_offsets(int fd, const char *provider_name, const char* probe_name, uid_t uid, gid_t gid, uint64_t **offsets, uint32_t *num_offset); LTTNG_HIDDEN +int run_as_generate_filter_bytecode(const char *filter_expression, + uid_t uid, + gid_t gid, + struct lttng_bytecode **bytecode); +LTTNG_HIDDEN int run_as_create_worker(const char *procname, post_fork_cleanup_cb clean_up_func, void *clean_up_user_data); LTTNG_HIDDEN diff --git a/src/common/sessiond-comm/sessiond-comm.h b/src/common/sessiond-comm/sessiond-comm.h index 0b4c05fb7..e03265e2d 100644 --- a/src/common/sessiond-comm/sessiond-comm.h +++ b/src/common/sessiond-comm/sessiond-comm.h @@ -101,6 +101,7 @@ enum lttcomm_sessiond_command { LTTNG_SESSION_LIST_ROTATION_SCHEDULES = 48, LTTNG_CREATE_SESSION_EXT = 49, LTTNG_CLEAR_SESSION = 50, + LTTNG_LIST_TRIGGERS = 51, }; enum lttcomm_relayd_command { @@ -421,7 +422,7 @@ struct lttcomm_session_msg { * starts at reloc_table_offset. */ #define LTTNG_FILTER_PADDING 32 -struct lttng_filter_bytecode { +struct lttng_bytecode { uint32_t len; /* len of data */ uint32_t reloc_table_offset; uint64_t seqnum; diff --git a/src/common/snapshot.c b/src/common/snapshot.c new file mode 100644 index 000000000..2879f0f90 --- /dev/null +++ b/src/common/snapshot.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2020 - EfficiOS, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "buffer-view.h" +#include "dynamic-buffer.h" +#include "lttng/snapshot.h" +#include "lttng/snapshot-internal.h" +#include "snapshot.h" + +#include +#include + +LTTNG_HIDDEN +bool lttng_snapshot_output_validate(const struct lttng_snapshot_output *output) +{ + bool valid = false; + size_t len; + + /* + * It is mandatory to have a ctrl_url. If there is only one output + * URL (in the net://, net6:// or file:// form), it will be in this + * field. + */ + len = lttng_strnlen(output->ctrl_url, sizeof(output->ctrl_url)); + if (len == 0 || len >= sizeof(output->ctrl_url)) { + goto end; + } + + len = lttng_strnlen(output->data_url, sizeof(output->data_url)); + if (len >= sizeof(output->data_url)) { + goto end; + } + + len = lttng_strnlen(output->name, sizeof(output->name)); + if (len >= sizeof(output->name)) { + goto end; + } + + valid = true; + +end: + return valid; +} + +LTTNG_HIDDEN +bool lttng_snapshot_output_is_equal( + const struct lttng_snapshot_output *a, + const struct lttng_snapshot_output *b) +{ + bool equal = false; + + assert(a); + assert(b); + + if (a->max_size != b->max_size) { + goto end; + } + + if (strcmp(a->name, b->name) != 0) { + goto end; + } + + if (strcmp(a->ctrl_url, b->ctrl_url) != 0) { + goto end; + } + + if (strcmp(a->data_url, b->data_url) != 0) { + goto end; + } + + equal = true; + +end: + return equal; +} + +/* + * This is essentially the same as `struct lttng_snapshot_output`, but packed. + */ +struct LTTNG_PACKED lttng_snapshot_output_comm { + uint32_t id; + uint64_t max_size; + char name[LTTNG_NAME_MAX]; + char ctrl_url[PATH_MAX]; + char data_url[PATH_MAX]; +}; + +LTTNG_HIDDEN +int lttng_snapshot_output_serialize( + const struct lttng_snapshot_output *output, + struct lttng_dynamic_buffer *buf) +{ + struct lttng_snapshot_output_comm comm; + int ret; + + comm.id = output->id; + comm.max_size = output->max_size; + + ret = lttng_strncpy(comm.name, output->name, sizeof(comm.name)); + if (ret) { + goto end; + } + + ret = lttng_strncpy(comm.ctrl_url, output->ctrl_url, sizeof(comm.ctrl_url)); + if (ret) { + goto end; + } + + ret = lttng_strncpy(comm.data_url, output->data_url, sizeof(comm.data_url)); + if (ret) { + goto end; + } + + ret = lttng_dynamic_buffer_append(buf, &comm, sizeof(comm)); + if (ret) { + goto end; + } + +end: + return ret; +} + +LTTNG_HIDDEN +ssize_t lttng_snapshot_output_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_snapshot_output **output_p) +{ + const struct lttng_snapshot_output_comm *comm; + struct lttng_snapshot_output *output = NULL; + int ret; + + if (view->size != sizeof(*comm)) { + ret = -1; + goto end; + } + + output = lttng_snapshot_output_create(); + if (!output) { + ret = -1; + goto end; + } + + comm = (struct lttng_snapshot_output_comm *) view->data; + + output->id = comm->id; + output->max_size = comm->max_size; + + ret = lttng_strncpy(output->name, comm->name, sizeof(output->name)); + if (ret) { + goto end; + } + + ret = lttng_strncpy(output->ctrl_url, comm->ctrl_url, sizeof(output->ctrl_url)); + if (ret) { + goto end; + } + + ret = lttng_strncpy(output->data_url, comm->data_url, sizeof(output->data_url)); + if (ret) { + goto end; + } + + *output_p = output; + output = NULL; + ret = sizeof(*comm); + +end: + lttng_snapshot_output_destroy(output); + return ret; +} diff --git a/src/common/snapshot.h b/src/common/snapshot.h new file mode 100644 index 000000000..2f27d5c79 --- /dev/null +++ b/src/common/snapshot.h @@ -0,0 +1,47 @@ +#ifndef COMMON_SNAPSHOT_H +#define COMMON_SNAPSHOT_H + +/* + * Copyright (C) 2020 - EfficiOS, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "common/macros.h" + +#include + +struct lttng_buffer_view; +struct lttng_dynamic_buffer; +struct lttng_snapshot_output; + +LTTNG_HIDDEN +bool lttng_snapshot_output_validate(const struct lttng_snapshot_output *output); + +LTTNG_HIDDEN +bool lttng_snapshot_output_is_equal( + const struct lttng_snapshot_output *a, + const struct lttng_snapshot_output *b); + +LTTNG_HIDDEN +int lttng_snapshot_output_serialize( + const struct lttng_snapshot_output *output, + struct lttng_dynamic_buffer *buf); + +LTTNG_HIDDEN +ssize_t lttng_snapshot_output_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_snapshot_output **output_p); + +#endif /* COMMON_SNAPSHOT_H */ diff --git a/src/common/trigger.c b/src/common/trigger.c index dd161eb38..feeb0175a 100644 --- a/src/common/trigger.c +++ b/src/common/trigger.c @@ -7,12 +7,47 @@ #include #include +#include +#include +#include +#include +#include #include +#include #include +#include #include +#include + +static void lttng_trigger_set_internal_object_ownership( + struct lttng_trigger *trigger) +{ + /* + * This is necessary to faciliate the object destroy phase. A trigger + * created by a client does not OWN the internal objects (condition and + * action) but when a trigger object is created on the sessiond side or + * for listing triggers (mostly via create_from_buffer) the object is + * the owner of the internal objects. Hence, we set the ownership bool + * to true, in such case, to facilitate object lifetime management and + * internal ownership. + * + * TODO: I'm open to any other solution + */ + assert(trigger); + trigger->owns_internal_objects = true; +} + +static void destroy_lttng_condition_event_rule_capture_bytecode_element(void *ptr) +{ + struct lttng_condition_event_rule_capture_bytecode_element *element = + ptr; + lttng_event_expr_destroy(element->expression); + free(element->bytecode); + free(element); +} LTTNG_HIDDEN -bool lttng_trigger_validate(struct lttng_trigger *trigger) +bool lttng_trigger_validate(const struct lttng_trigger *trigger) { bool valid; @@ -42,8 +77,18 @@ struct lttng_trigger *lttng_trigger_create( goto end; } + urcu_ref_init(&trigger->ref); + trigger->owns_internal_objects = false; + trigger->firing_policy.type = LTTNG_TRIGGER_FIRE_EVERY_N; + trigger->firing_policy.threshold = 1; trigger->condition = condition; trigger->action = action; + + trigger->creds.set = false; + + lttng_dynamic_pointer_array_init(&trigger->capture_bytecode_set, + destroy_lttng_condition_event_rule_capture_bytecode_element); + end: return trigger; } @@ -54,11 +99,10 @@ struct lttng_condition *lttng_trigger_get_condition( return trigger ? trigger->condition : NULL; } -LTTNG_HIDDEN const struct lttng_condition *lttng_trigger_get_const_condition( const struct lttng_trigger *trigger) { - return trigger->condition; + return trigger ? trigger->condition : NULL; } struct lttng_action *lttng_trigger_get_action( @@ -67,33 +111,55 @@ struct lttng_action *lttng_trigger_get_action( return trigger ? trigger->action : NULL; } -LTTNG_HIDDEN const struct lttng_action *lttng_trigger_get_const_action( const struct lttng_trigger *trigger) { - return trigger->action; + return trigger ? trigger->action : NULL; } -void lttng_trigger_destroy(struct lttng_trigger *trigger) +static void trigger_destroy_ref(struct urcu_ref *ref) { - if (!trigger) { - return; + struct lttng_trigger *trigger = + container_of(ref, struct lttng_trigger, ref); + + lttng_dynamic_pointer_array_reset(&trigger->capture_bytecode_set); + + if (trigger->owns_internal_objects) { + struct lttng_action *action = lttng_trigger_get_action(trigger); + struct lttng_condition *condition = + lttng_trigger_get_condition(trigger); + + assert(action); + assert(condition); + lttng_action_destroy(action); + lttng_condition_destroy(condition); } + free(trigger->name); free(trigger); } +void lttng_trigger_destroy(struct lttng_trigger *trigger) +{ + lttng_trigger_put(trigger); +} + LTTNG_HIDDEN ssize_t lttng_trigger_create_from_buffer( const struct lttng_buffer_view *src_view, struct lttng_trigger **trigger) { - ssize_t ret, offset = 0, condition_size, action_size; + ssize_t ret, offset = 0, condition_size, action_size, name_size = 0; + enum lttng_trigger_status status; struct lttng_condition *condition = NULL; struct lttng_action *action = NULL; const struct lttng_trigger_comm *trigger_comm; struct lttng_buffer_view condition_view; struct lttng_buffer_view action_view; + struct lttng_buffer_view name_view; + const char *name = NULL; + unsigned long long firing_threshold; + enum lttng_trigger_firing_policy_type firing_policy; if (!src_view || !trigger) { ret = -1; @@ -104,6 +170,22 @@ ssize_t lttng_trigger_create_from_buffer( trigger_comm = (const struct lttng_trigger_comm *) src_view->data; offset += sizeof(*trigger_comm); + firing_policy = trigger_comm->policy_type; + firing_threshold = trigger_comm->policy_threshold; + if (trigger_comm->name_length != 0) { + name_view = lttng_buffer_view_from_view( + src_view, offset, trigger_comm->name_length); + name = name_view.data; + if (trigger_comm->name_length == 1 || + name[trigger_comm->name_length - 1] != '\0' || + strlen(name) != trigger_comm->name_length - 1) { + ret = -1; + goto end; + } + offset += trigger_comm->name_length; + name_size = trigger_comm->name_length; + } + condition_view = lttng_buffer_view_from_view(src_view, offset, -1); /* struct lttng_condition */ @@ -125,7 +207,7 @@ ssize_t lttng_trigger_create_from_buffer( offset += action_size; /* Unexpected size of inner-elements; the buffer is corrupted. */ - if ((ssize_t) trigger_comm->length != condition_size + action_size) { + if ((ssize_t) trigger_comm->length != condition_size + action_size + name_size) { ret = -1; goto error; } @@ -135,6 +217,26 @@ ssize_t lttng_trigger_create_from_buffer( ret = -1; goto error; } + + /* Take ownership of the internal object from there */ + lttng_trigger_set_internal_object_ownership(*trigger); + condition = NULL; + action = NULL; + + if (name) { + status = lttng_trigger_set_name(*trigger, name); + if (status != LTTNG_TRIGGER_STATUS_OK) { + ret = -1; + goto end; + } + } + + status = lttng_trigger_set_firing_policy(*trigger, firing_policy, firing_threshold); + if (status != LTTNG_TRIGGER_STATUS_OK) { + ret = -1; + goto end; + } + ret = offset; end: return ret; @@ -149,15 +251,27 @@ error: * for the detailed format. */ LTTNG_HIDDEN -int lttng_trigger_serialize(struct lttng_trigger *trigger, - struct lttng_dynamic_buffer *buf) +int lttng_trigger_serialize(const struct lttng_trigger *trigger, + struct lttng_dynamic_buffer *buf, + int *fd_to_send) { int ret; - size_t header_offset, size_before_payload; + size_t header_offset, size_before_payload, size_name; struct lttng_trigger_comm trigger_comm = { 0 }; struct lttng_trigger_comm *header; header_offset = buf->size; + + if (trigger->name != NULL) { + size_name = strlen(trigger->name) + 1; + } else { + size_name = 0; + } + + trigger_comm.name_length = size_name; + trigger_comm.policy_type = (uint8_t) trigger->firing_policy.type; + trigger_comm.policy_threshold = (uint64_t) trigger->firing_policy.threshold; + ret = lttng_dynamic_buffer_append(buf, &trigger_comm, sizeof(trigger_comm)); if (ret) { @@ -165,7 +279,14 @@ int lttng_trigger_serialize(struct lttng_trigger *trigger, } size_before_payload = buf->size; - ret = lttng_condition_serialize(trigger->condition, buf); + + /* Trigger name */ + ret = lttng_dynamic_buffer_append(buf, trigger->name, size_name); + if (ret) { + goto end; + } + + ret = lttng_condition_serialize(trigger->condition, buf, fd_to_send); if (ret) { goto end; } @@ -181,3 +302,513 @@ int lttng_trigger_serialize(struct lttng_trigger *trigger, end: return ret; } + +enum lttng_trigger_status lttng_trigger_set_name(struct lttng_trigger *trigger, const char* name) +{ + char *name_copy = NULL; + enum lttng_trigger_status status = LTTNG_TRIGGER_STATUS_OK; + + if (!trigger || !name || + strlen(name) == 0) { + status = LTTNG_TRIGGER_STATUS_INVALID; + goto end; + } + + name_copy = strdup(name); + if (!name_copy) { + status = LTTNG_TRIGGER_STATUS_ERROR; + goto end; + } + + if (trigger->name) { + free(trigger->name); + } + + trigger->name = name_copy; + name_copy = NULL; +end: + return status; +} + +enum lttng_trigger_status lttng_trigger_get_name(const struct lttng_trigger *trigger, const char **name) +{ + enum lttng_trigger_status status = LTTNG_TRIGGER_STATUS_OK; + + if (!trigger || !name) { + status = LTTNG_TRIGGER_STATUS_INVALID; + goto end; + } + + if (!trigger->name) { + status = LTTNG_TRIGGER_STATUS_UNSET; + } + + *name = trigger->name; +end: + return status; +} + +LTTNG_HIDDEN +int lttng_trigger_assign(struct lttng_trigger *dst, + const struct lttng_trigger *src) +{ + int ret = 0; + enum lttng_trigger_status status; + /* todo some validation */ + + status = lttng_trigger_set_name(dst, src->name); + if (status != LTTNG_TRIGGER_STATUS_OK) { + ret = -1; + ERR("Failed to set name for trigger"); + goto end; + } +end: + return ret; +} + +LTTNG_HIDDEN +void lttng_trigger_set_key(struct lttng_trigger *trigger, uint64_t key) +{ + assert(trigger); + trigger->key.value = key; + trigger->key.set = true; +} + +LTTNG_HIDDEN +uint64_t lttng_trigger_get_key(const struct lttng_trigger *trigger) +{ + assert(trigger); + + assert(trigger->key.set == true); + return trigger->key.value; +} + +LTTNG_HIDDEN +int lttng_trigger_generate_name(struct lttng_trigger *trigger, uint64_t offset) +{ + int ret = 0; + char *generated_name = NULL; + assert(trigger->key.set); + + ret = asprintf(&generated_name, "T%" PRIu64 "", trigger->key.value + offset); + if (ret < 0) { + ERR("Failed to generate trigger name"); + ret = -1; + goto end; + } + + if (trigger->name) { + free(trigger->name); + } + trigger->name = generated_name; +end: + return ret; +} + +LTTNG_HIDDEN +void lttng_trigger_get(struct lttng_trigger *trigger) +{ + urcu_ref_get(&trigger->ref); +} + +LTTNG_HIDDEN +void lttng_trigger_put(struct lttng_trigger *trigger) +{ + if (!trigger) { + return; + } + + urcu_ref_put(&trigger->ref , trigger_destroy_ref); +} + +static void delete_trigger_array_element(void *ptr) +{ + struct lttng_trigger *trigger = ptr; + lttng_trigger_destroy(trigger); +} + +LTTNG_HIDDEN +bool lttng_trigger_is_equal( + const struct lttng_trigger *a, const struct lttng_trigger *b) +{ + /* TODO: Optimization: for now a trigger with a firing policy that is + * not the same even if the conditions and actions is the same is + * treated as a "completely" different trigger. In a perfect world we + * would simply add a supplemental counter internally (sessiond side) to + * remove overhead on the tracer side. + */ + if (a->firing_policy.type != b->firing_policy.type) { + return false; + } + + if (a->firing_policy.threshold != b->firing_policy.threshold) { + return false; + } + + /* + * Name is not taken into account since it is cosmetic only + */ + if (!lttng_condition_is_equal(a->condition, b->condition)) { + return false; + } + if (!lttng_action_is_equal(a->action, b->action)) { + return false; + } + + return true; +} + +LTTNG_HIDDEN +struct lttng_triggers *lttng_triggers_create(void) +{ + struct lttng_triggers *triggers = NULL; + + triggers = zmalloc(sizeof(*triggers)); + if (!triggers) { + goto error; + } + + lttng_dynamic_pointer_array_init(&triggers->array, delete_trigger_array_element); + + return triggers; +error: + free(triggers); + return NULL; +} + +LTTNG_HIDDEN +struct lttng_trigger *lttng_triggers_get_pointer_of_index( + const struct lttng_triggers *triggers, unsigned int index) +{ + assert(triggers); + if (index >= lttng_dynamic_pointer_array_get_count(&triggers->array)) { + return NULL; + } + return lttng_dynamic_pointer_array_get_pointer(&triggers->array, index); +} + +LTTNG_HIDDEN +int lttng_triggers_add( + struct lttng_triggers *triggers, struct lttng_trigger *trigger) +{ + assert(triggers); + assert(trigger); + + return lttng_dynamic_pointer_array_add_pointer(&triggers->array, trigger); +} + +const struct lttng_trigger *lttng_triggers_get_at_index( + const struct lttng_triggers *triggers, unsigned int index) +{ + assert(triggers); + return lttng_triggers_get_pointer_of_index(triggers, index); +} + +enum lttng_trigger_status lttng_triggers_get_count(const struct lttng_triggers *triggers, unsigned int *count) +{ + enum lttng_trigger_status status = LTTNG_TRIGGER_STATUS_OK; + + if (!triggers || !count) { + status = LTTNG_TRIGGER_STATUS_INVALID; + goto end; + } + + *count = lttng_dynamic_pointer_array_get_count(&triggers->array); +end: + return status; +} + +void lttng_triggers_destroy(struct lttng_triggers *triggers) +{ + if (!triggers) { + return; + } + + lttng_dynamic_pointer_array_reset(&triggers->array); + free(triggers); +} + +int lttng_triggers_serialize(const struct lttng_triggers *triggers, + struct lttng_dynamic_buffer *buffer) +{ + int ret; + unsigned int count; + size_t header_offset, size_before_payload; + struct lttng_triggers_comm triggers_comm = { 0 }; + struct lttng_triggers_comm *header; + struct lttng_trigger *trigger; + enum lttng_trigger_status status; + + header_offset = buffer->size; + + status = lttng_triggers_get_count(triggers, &count); + if (status != LTTNG_TRIGGER_STATUS_OK) { + ret = LTTNG_ERR_INVALID; + goto end; + } + + triggers_comm.count = count; + + ret = lttng_dynamic_buffer_append(buffer, &triggers_comm, + sizeof(triggers_comm)); + if (ret) { + goto end; + } + + size_before_payload = buffer->size; + + for (int i = 0; i < count; i++) { + trigger = lttng_triggers_get_pointer_of_index(triggers, i); + if (!trigger) { + assert(0); + } + + ret = lttng_trigger_serialize(trigger, buffer, NULL); + if (ret) { + goto end; + } + } + + /* Update payload size. */ + header = (struct lttng_triggers_comm *) ((char *) buffer->data + header_offset); + header->length = buffer->size - size_before_payload; +end: + return ret; +} + +LTTNG_HIDDEN +ssize_t lttng_triggers_create_from_buffer( + const struct lttng_buffer_view *src_view, + struct lttng_triggers **triggers) +{ + ssize_t ret, offset = 0, trigger_size, triggers_size = 0; + const struct lttng_triggers_comm *triggers_comm; + struct lttng_buffer_view trigger_view; + struct lttng_triggers *local_triggers = NULL; + + if (!src_view || !triggers) { + ret = -1; + goto error; + } + + /* lttng_trigger_comms header */ + triggers_comm = (const struct lttng_triggers_comm *) src_view->data; + offset += sizeof(*triggers_comm); + + local_triggers = lttng_triggers_create(); + if (!local_triggers) { + ret = -1; + goto error; + } + + for (int i = 0; i < triggers_comm->count; i++) { + struct lttng_trigger *trigger = NULL; + trigger_view = lttng_buffer_view_from_view(src_view, offset, -1); + trigger_size = lttng_trigger_create_from_buffer(&trigger_view, + &trigger); + if (trigger_size < 0) { + ret = trigger_size; + goto error; + } + + /* Pass ownership of the trigger to the collection */ + ret = lttng_triggers_add(local_triggers, trigger); + if (ret < 0) { + assert(0); + } + trigger = NULL; + + offset += trigger_size; + triggers_size += trigger_size; + } + + /* Unexpected size of inner-elements; the buffer is corrupted. */ + if ((ssize_t) triggers_comm->length != triggers_size) { + ret = -1; + goto error; + } + + /* Pass ownership to caller */ + *triggers = local_triggers; + local_triggers = NULL; + + ret = offset; +error: + + lttng_triggers_destroy(local_triggers); + return ret; +} + +LTTNG_HIDDEN +const struct lttng_credentials *lttng_trigger_get_credentials( + const struct lttng_trigger *trigger) +{ + assert(trigger->creds.set); + return &(trigger->creds.credentials); +} + +LTTNG_HIDDEN +void lttng_trigger_set_credentials( + struct lttng_trigger *trigger, uid_t uid, gid_t gid) +{ + trigger->creds.credentials.uid = uid; + trigger->creds.credentials.gid = gid; + trigger->creds.set = true; +} + +enum lttng_trigger_status lttng_trigger_set_firing_policy( + struct lttng_trigger *trigger, + enum lttng_trigger_firing_policy_type policy_type, + unsigned long long threshold) +{ + enum lttng_trigger_status ret = LTTNG_TRIGGER_STATUS_OK; + assert(trigger); + + if (threshold < 1) { + ret = LTTNG_TRIGGER_STATUS_INVALID; + goto end; + } + + trigger->firing_policy.type = policy_type; + trigger->firing_policy.threshold = threshold; + +end: + return ret; +} + +enum lttng_trigger_status lttng_trigger_get_firing_policy( + const struct lttng_trigger *trigger, + enum lttng_trigger_firing_policy_type *policy_type, + unsigned long long *threshold) +{ + enum lttng_trigger_status status = LTTNG_TRIGGER_STATUS_OK; + + if (!trigger || !policy_type || !threshold) { + status = LTTNG_TRIGGER_STATUS_INVALID; + goto end; + } + + *policy_type = trigger->firing_policy.type; + *threshold = trigger->firing_policy.threshold; + +end: + return status; +} + +LTTNG_HIDDEN +bool lttng_trigger_is_ready_to_fire(struct lttng_trigger *trigger) +{ + assert(trigger); + bool ready_to_fire = false; + + trigger->firing_policy.current_count++; + + switch (trigger->firing_policy.type) { + case LTTNG_TRIGGER_FIRE_EVERY_N: + if (trigger->firing_policy.current_count == trigger->firing_policy.threshold) { + trigger->firing_policy.current_count = 0; + ready_to_fire = true; + } + break; + case LTTNG_TRIGGER_FIRE_ONCE_AFTER_N: + if (trigger->firing_policy.current_count == trigger->firing_policy.threshold) { + /* TODO: remove the trigger of at least deactivate it on + * the tracers side to remove any work overhead on the + * traced application or kernel since the trigger will + * never fire again. + * Still this branch should be left here since event + * could still be in the pipe. These will be discarded. + */ + ready_to_fire = true; + } + break; + default: + assert(0); + }; + + return ready_to_fire; +} + +LTTNG_HIDDEN +enum lttng_domain_type lttng_trigger_get_underlying_domain_type_restriction( + const struct lttng_trigger *trigger) +{ + enum lttng_domain_type type = LTTNG_DOMAIN_NONE; + const struct lttng_event_rule *event_rule; + enum lttng_condition_status c_status; + enum lttng_condition_type c_type; + + assert(trigger); + assert(trigger->condition); + c_type = lttng_condition_get_type(trigger->condition); + if (c_type == LTTNG_CONDITION_TYPE_UNKNOWN) { + assert(0); + } + + switch (c_type) { + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: + type = LTTNG_DOMAIN_NONE; + break; + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + c_status = lttng_condition_event_rule_get_rule( + trigger->condition, &event_rule); + if (c_status != LTTNG_CONDITION_STATUS_OK) { + /* The condition object is invalid */ + assert(0); + } + + type = lttng_event_rule_get_domain_type(event_rule); + break; + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + c_status = lttng_condition_buffer_usage_get_domain_type( + trigger->condition, &type); + if (c_status != LTTNG_CONDITION_STATUS_OK) { + /* The condition object is invalid */ + assert(0); + } + break; + default: + type = LTTNG_DOMAIN_NONE; + break; + } + + return type; +} + +LTTNG_HIDDEN +unsigned int lttng_trigger_get_capture_bytecode_count( + const struct lttng_trigger *trigger) +{ + unsigned int count = 0; + if (!trigger) { + goto end; + } + + count = lttng_dynamic_pointer_array_get_count( + &trigger->capture_bytecode_set); + +end: + return count; +} + +LTTNG_HIDDEN +const struct lttng_bytecode * +lttng_trigger_get_capture_bytecode_at_index( + const struct lttng_trigger *trigger, unsigned int index) +{ + struct lttng_condition_event_rule_capture_bytecode_element *element = NULL; + struct lttng_bytecode *bytecode = NULL; + + element = lttng_dynamic_pointer_array_get_pointer( + &trigger->capture_bytecode_set, index); + + if (element == NULL) { + goto end; + } + bytecode = element->bytecode; +end: + return bytecode; +} diff --git a/src/common/unix.c b/src/common/unix.c index 222b4a3d9..26eda52d9 100644 --- a/src/common/unix.c +++ b/src/common/unix.c @@ -232,12 +232,9 @@ retry: if (errno == EINTR) { goto retry; } else { - /* - * Only warn about EPIPE when quiet mode is - * deactivated. - * We consider EPIPE as expected. - */ - if (errno != EPIPE || !lttng_opt_quiet) { + /* We consider EPIPE and EAGAIN as expected. */ + if (!lttng_opt_quiet && + (errno != EPIPE && errno != EAGAIN)) { PERROR("recvmsg"); } goto end; @@ -320,12 +317,9 @@ retry: if (errno == EINTR) { goto retry; } else { - /* - * Only warn about EPIPE when quiet mode is - * deactivated. - * We consider EPIPE as expected. - */ - if (errno != EPIPE || !lttng_opt_quiet) { + /* We consider EPIPE and EAGAIN as expected. */ + if (!lttng_opt_quiet && + (errno != EPIPE && errno != EAGAIN)) { PERROR("sendmsg"); } goto end; diff --git a/src/common/userspace-probe.c b/src/common/userspace-probe.c index 508c9e8d4..168b35015 100644 --- a/src/common/userspace-probe.c +++ b/src/common/userspace-probe.c @@ -6,12 +6,15 @@ */ #include +#include #include #include -#include #include #include #include +#include +#include +#include enum lttng_userspace_probe_location_lookup_method_type lttng_userspace_probe_location_lookup_method_get_type( @@ -144,6 +147,88 @@ void lttng_userspace_probe_location_destroy( } } +/* + * Compare two FDs. + * For now only the Inode number is compared. + */ +static bool fd_is_equal(int a, int b) +{ + int ret; + bool is_equal = false; + struct stat a_stat, b_stat; + + if (a < 0 && b >= 0) { + goto end; + } + + if (b < 0 && a >= 0) { + goto end; + } + + if (a < 0 && b < 0) { + if (a == -1 && b == -1) { + is_equal = true; + goto end; + } + /* Invalid state, assert */ + assert(0); + goto end; + } + + /* Both are valid FD numbers */ + ret = fstat(a, &a_stat); + if (ret) { + PERROR("fstat on fd a: %d", a); + goto end; + } + + ret = fstat(b, &b_stat); + if (ret) { + PERROR("fstat on fd b: %d", b); + goto end; + } + + is_equal = (a_stat.st_ino == b_stat.st_ino); + +end: + return is_equal; +} + +static bool lttng_userspace_probe_location_function_is_equal( + const struct lttng_userspace_probe_location *_a, + const struct lttng_userspace_probe_location *_b) +{ + bool is_equal = false; + struct lttng_userspace_probe_location_function *a, *b; + + a = container_of(_a, struct lttng_userspace_probe_location_function, + parent); + b = container_of(_b, struct lttng_userspace_probe_location_function, + parent); + + if (a->instrumentation_type != b->instrumentation_type) { + goto end; + } + + assert(a->function_name); + assert(b->function_name); + if (strcmp(a->function_name, b->function_name)) { + goto end; + } + + assert(a->binary_path); + assert(b->binary_path); + if (strcmp(a->binary_path, b->binary_path)) { + goto end; + } + + // TODO: see gerrit comment: https://review.lttng.org/c/lttng-tools/+/2924/27/src/common/userspace-probe.c#255 + //is_equal = fd_is_equal(a->binary_fd, b->binary_fd); + is_equal = true; +end: + return is_equal; +} + static struct lttng_userspace_probe_location * lttng_userspace_probe_location_function_create_no_check(const char *binary_path, const char *function_name, @@ -192,6 +277,7 @@ lttng_userspace_probe_location_function_create_no_check(const char *binary_path, ret = &location->parent; ret->lookup_method = lookup_method; ret->type = LTTNG_USERSPACE_PROBE_LOCATION_TYPE_FUNCTION; + ret->equal = lttng_userspace_probe_location_function_is_equal; goto end; error: @@ -206,6 +292,42 @@ end: return ret; } +static bool lttng_userspace_probe_location_tracepoint_is_equal( + const struct lttng_userspace_probe_location *_a, + const struct lttng_userspace_probe_location *_b) +{ + bool is_equal = false; + struct lttng_userspace_probe_location_tracepoint *a, *b; + + a = container_of(_a, struct lttng_userspace_probe_location_tracepoint, + parent); + b = container_of(_b, struct lttng_userspace_probe_location_tracepoint, + parent); + + assert(a->probe_name); + assert(b->probe_name); + if (strcmp(a->probe_name, b->probe_name)) { + goto end; + } + + assert(a->provider_name); + assert(b->provider_name); + if (strcmp(a->provider_name, b->provider_name)) { + goto end; + } + + assert(a->binary_path); + assert(b->binary_path); + if (strcmp(a->binary_path, b->binary_path)) { + goto end; + } + + is_equal = fd_is_equal(a->binary_fd, b->binary_fd); + +end: + return is_equal; +} + static struct lttng_userspace_probe_location * lttng_userspace_probe_location_tracepoint_create_no_check(const char *binary_path, const char *provider_name, const char *probe_name, @@ -261,6 +383,7 @@ lttng_userspace_probe_location_tracepoint_create_no_check(const char *binary_pat ret = &location->parent; ret->lookup_method = lookup_method; ret->type = LTTNG_USERSPACE_PROBE_LOCATION_TYPE_TRACEPOINT; + ret->equal = lttng_userspace_probe_location_tracepoint_is_equal; goto end; error: @@ -1711,3 +1834,104 @@ struct lttng_userspace_probe_location *lttng_userspace_probe_location_copy( err: return new_location; } + +LTTNG_HIDDEN +bool lttng_userspace_probe_location_lookup_method_is_equal( + const struct lttng_userspace_probe_location_lookup_method *a, + const struct lttng_userspace_probe_location_lookup_method *b) +{ + bool is_equal = false; + + if (!a || !b) { + goto end; + } + + if (a == b) { + is_equal = true; + goto end; + } + + /* Should we use the getter here? */ + if (a->type != b->type) { + goto end; + } + + /* Not much else to compare except the type for now */ + is_equal = true; +end: + return is_equal; +} + +LTTNG_HIDDEN +bool lttng_userspace_probe_location_is_equal( + const struct lttng_userspace_probe_location *a, + const struct lttng_userspace_probe_location *b) +{ + bool is_equal = false; + + if (!a || !b) { + goto end; + } + + if (a == b) { + is_equal = true; + goto end; + } + + /* Should we use the getter here? */ + if (!lttng_userspace_probe_location_lookup_method_is_equal( + a->lookup_method, b->lookup_method)) { + goto end; + } + + /* Should we use the getter here? */ + if (a->type != b->type) { + goto end; + } + + is_equal = a->equal ? a->equal(a, b) : true; +end: + return is_equal; +} + +LTTNG_HIDDEN +int lttng_userspace_probe_location_set_binary_fd( + struct lttng_userspace_probe_location *location, int fd) +{ + int ret = 0; + const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; + /* + * Set the file descriptor received from the client through the unix + * socket in the probe location. + */ + lookup = lttng_userspace_probe_location_get_lookup_method(location); + if (!lookup) { + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto end; + } + + /* + * From the kernel tracer's perspective, all userspace probe event types + * are all the same: a file and an offset. + */ + switch (lttng_userspace_probe_location_lookup_method_get_type(lookup)) { + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF: + ret = lttng_userspace_probe_location_function_set_binary_fd( + location, fd); + break; + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: + ret = lttng_userspace_probe_location_tracepoint_set_binary_fd( + location, fd); + break; + default: + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto end; + } + + if (ret) { + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto end; + } +end: + return ret; +} diff --git a/src/common/utils.c b/src/common/utils.c index d6c67bb0f..a96ee66e4 100644 --- a/src/common/utils.c +++ b/src/common/utils.c @@ -1663,3 +1663,40 @@ end: free(buf); return ret_val; } + +LTTNG_HIDDEN +int utils_parse_unsigned_long_long(const char *str, + unsigned long long *value) +{ + int ret; + char *endptr; + + assert(str); + assert(value); + + errno = 0; + *value = strtoull(str, &endptr, 10); + + /* Conversion failed. Out of range? */ + if (errno != 0) { + ret = -1; + goto end; + } + + /* Not the end of the string? */ + if (*endptr) { + ret = -1; + goto end; + } + + /* Empty string? */ + if (endptr == str) { + ret = -1; + goto end; + } + + ret = 0; + +end: + return ret; +} diff --git a/src/common/utils.h b/src/common/utils.h index dec4cd8aa..570216d53 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -59,4 +59,16 @@ enum lttng_error_code utils_user_id_from_name( enum lttng_error_code utils_group_id_from_name( const char *group_name, gid_t *group_id); +/* + * Parse `str` as an unsigned long long value. + * + * Return 0 on success. Return -1 on failure which can be because: + * + * - `str` is zero length + * - `str` contains invalid + */ +LTTNG_HIDDEN +int utils_parse_unsigned_long_long(const char *str, + unsigned long long *value); + #endif /* _COMMON_UTILS_H */ diff --git a/src/lib/lttng-ctl/Makefile.am b/src/lib/lttng-ctl/Makefile.am index 1bee05f0c..8c271f5ef 100644 --- a/src/lib/lttng-ctl/Makefile.am +++ b/src/lib/lttng-ctl/Makefile.am @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only -SUBDIRS = filter +SUBDIRS = AM_CPPFLAGS += -I$(srcdir) -I$(builddir) @@ -16,8 +16,7 @@ liblttng_ctl_la_LDFLAGS = \ liblttng_ctl_la_LIBADD = \ $(top_builddir)/src/common/sessiond-comm/libsessiond-comm.la \ - $(top_builddir)/src/common/libcommon.la \ - $(top_builddir)/src/lib/lttng-ctl/filter/libfilter.la + $(top_builddir)/src/common/libcommon.la pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = lttng-ctl.pc diff --git a/src/lib/lttng-ctl/channel.c b/src/lib/lttng-ctl/channel.c index f2a65885d..5fc77d0e4 100644 --- a/src/lib/lttng-ctl/channel.c +++ b/src/lib/lttng-ctl/channel.c @@ -632,7 +632,7 @@ enum lttng_notification_channel_status send_condition_command( goto end_unlock; } - ret = lttng_condition_serialize(condition, &buffer); + ret = lttng_condition_serialize(condition, &buffer, NULL); if (ret) { status = LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID; goto end_unlock; diff --git a/src/lib/lttng-ctl/filter/filter-bytecode.h b/src/lib/lttng-ctl/filter/filter-bytecode.h deleted file mode 100644 index 053bb08bb..000000000 --- a/src/lib/lttng-ctl/filter/filter-bytecode.h +++ /dev/null @@ -1,243 +0,0 @@ -#ifndef _FILTER_BYTECODE_H -#define _FILTER_BYTECODE_H - -/* - * filter-bytecode.h - * - * LTTng filter bytecode - * - * Copyright 2012 Mathieu Desnoyers - * - * SPDX-License-Identifier: LGPL-2.1-only - * - */ - -#include -#include - -#include "filter-ast.h" - -/* - * offsets are absolute from start of bytecode. - */ - -struct field_ref { - /* Initially, symbol offset. After link, field offset. */ - uint16_t offset; -} LTTNG_PACKED; - -struct get_symbol { - /* Symbol offset. */ - uint16_t offset; -} LTTNG_PACKED; - -struct get_index_u16 { - uint16_t index; -} LTTNG_PACKED; - -struct get_index_u64 { - uint64_t index; -} LTTNG_PACKED; - -struct literal_numeric { - int64_t v; -} LTTNG_PACKED; - -struct literal_double { - double v; -} LTTNG_PACKED; - -struct literal_string { - char string[0]; -} LTTNG_PACKED; - -enum filter_op { - FILTER_OP_UNKNOWN = 0, - - FILTER_OP_RETURN = 1, - - /* binary */ - FILTER_OP_MUL = 2, - FILTER_OP_DIV = 3, - FILTER_OP_MOD = 4, - FILTER_OP_PLUS = 5, - FILTER_OP_MINUS = 6, - FILTER_OP_BIT_RSHIFT = 7, - FILTER_OP_BIT_LSHIFT = 8, - FILTER_OP_BIT_AND = 9, - FILTER_OP_BIT_OR = 10, - FILTER_OP_BIT_XOR = 11, - - /* binary comparators */ - FILTER_OP_EQ = 12, - FILTER_OP_NE = 13, - FILTER_OP_GT = 14, - FILTER_OP_LT = 15, - FILTER_OP_GE = 16, - FILTER_OP_LE = 17, - - /* string binary comparator: apply to */ - FILTER_OP_EQ_STRING = 18, - FILTER_OP_NE_STRING = 19, - FILTER_OP_GT_STRING = 20, - FILTER_OP_LT_STRING = 21, - FILTER_OP_GE_STRING = 22, - FILTER_OP_LE_STRING = 23, - - /* s64 binary comparator */ - FILTER_OP_EQ_S64 = 24, - FILTER_OP_NE_S64 = 25, - FILTER_OP_GT_S64 = 26, - FILTER_OP_LT_S64 = 27, - FILTER_OP_GE_S64 = 28, - FILTER_OP_LE_S64 = 29, - - /* double binary comparator */ - FILTER_OP_EQ_DOUBLE = 30, - FILTER_OP_NE_DOUBLE = 31, - FILTER_OP_GT_DOUBLE = 32, - FILTER_OP_LT_DOUBLE = 33, - FILTER_OP_GE_DOUBLE = 34, - FILTER_OP_LE_DOUBLE = 35, - - /* Mixed S64-double binary comparators */ - FILTER_OP_EQ_DOUBLE_S64 = 36, - FILTER_OP_NE_DOUBLE_S64 = 37, - FILTER_OP_GT_DOUBLE_S64 = 38, - FILTER_OP_LT_DOUBLE_S64 = 39, - FILTER_OP_GE_DOUBLE_S64 = 40, - FILTER_OP_LE_DOUBLE_S64 = 41, - - FILTER_OP_EQ_S64_DOUBLE = 42, - FILTER_OP_NE_S64_DOUBLE = 43, - FILTER_OP_GT_S64_DOUBLE = 44, - FILTER_OP_LT_S64_DOUBLE = 45, - FILTER_OP_GE_S64_DOUBLE = 46, - FILTER_OP_LE_S64_DOUBLE = 47, - - /* unary */ - FILTER_OP_UNARY_PLUS = 48, - FILTER_OP_UNARY_MINUS = 49, - FILTER_OP_UNARY_NOT = 50, - FILTER_OP_UNARY_PLUS_S64 = 51, - FILTER_OP_UNARY_MINUS_S64 = 52, - FILTER_OP_UNARY_NOT_S64 = 53, - FILTER_OP_UNARY_PLUS_DOUBLE = 54, - FILTER_OP_UNARY_MINUS_DOUBLE = 55, - FILTER_OP_UNARY_NOT_DOUBLE = 56, - - /* logical */ - FILTER_OP_AND = 57, - FILTER_OP_OR = 58, - - /* load field ref */ - FILTER_OP_LOAD_FIELD_REF = 59, - FILTER_OP_LOAD_FIELD_REF_STRING = 60, - FILTER_OP_LOAD_FIELD_REF_SEQUENCE = 61, - FILTER_OP_LOAD_FIELD_REF_S64 = 62, - FILTER_OP_LOAD_FIELD_REF_DOUBLE = 63, - - /* load immediate from operand */ - FILTER_OP_LOAD_STRING = 64, - FILTER_OP_LOAD_S64 = 65, - FILTER_OP_LOAD_DOUBLE = 66, - - /* cast */ - FILTER_OP_CAST_TO_S64 = 67, - FILTER_OP_CAST_DOUBLE_TO_S64 = 68, - FILTER_OP_CAST_NOP = 69, - - /* get context ref */ - FILTER_OP_GET_CONTEXT_REF = 70, - FILTER_OP_GET_CONTEXT_REF_STRING = 71, - FILTER_OP_GET_CONTEXT_REF_S64 = 72, - FILTER_OP_GET_CONTEXT_REF_DOUBLE = 73, - - /* load userspace field ref */ - FILTER_OP_LOAD_FIELD_REF_USER_STRING = 74, - FILTER_OP_LOAD_FIELD_REF_USER_SEQUENCE = 75, - - /* - * load immediate star globbing pattern (literal string) - * from immediate - */ - FILTER_OP_LOAD_STAR_GLOB_STRING = 76, - - /* globbing pattern binary operator: apply to */ - FILTER_OP_EQ_STAR_GLOB_STRING = 77, - FILTER_OP_NE_STAR_GLOB_STRING = 78, - - /* - * Instructions for recursive traversal through composed types. - */ - FILTER_OP_GET_CONTEXT_ROOT = 79, - FILTER_OP_GET_APP_CONTEXT_ROOT = 80, - FILTER_OP_GET_PAYLOAD_ROOT = 81, - - FILTER_OP_GET_SYMBOL = 82, - FILTER_OP_GET_SYMBOL_FIELD = 83, - FILTER_OP_GET_INDEX_U16 = 84, - FILTER_OP_GET_INDEX_U64 = 85, - - FILTER_OP_LOAD_FIELD = 86, - FILTER_OP_LOAD_FIELD_S8 = 87, - FILTER_OP_LOAD_FIELD_S16 = 88, - FILTER_OP_LOAD_FIELD_S32 = 89, - FILTER_OP_LOAD_FIELD_S64 = 90, - FILTER_OP_LOAD_FIELD_U8 = 91, - FILTER_OP_LOAD_FIELD_U16 = 92, - FILTER_OP_LOAD_FIELD_U32 = 93, - FILTER_OP_LOAD_FIELD_U64 = 94, - FILTER_OP_LOAD_FIELD_STRING = 95, - FILTER_OP_LOAD_FIELD_SEQUENCE = 96, - FILTER_OP_LOAD_FIELD_DOUBLE = 97, - - FILTER_OP_UNARY_BIT_NOT = 98, - - FILTER_OP_RETURN_S64 = 99, - - NR_FILTER_OPS, -}; - -typedef uint8_t filter_opcode_t; - -struct load_op { - filter_opcode_t op; - char data[0]; - /* data to load. Size known by enum filter_opcode and null-term char. */ -} LTTNG_PACKED; - -struct binary_op { - filter_opcode_t op; -} LTTNG_PACKED; - -struct unary_op { - filter_opcode_t op; -} LTTNG_PACKED; - -/* skip_offset is absolute from start of bytecode */ -struct logical_op { - filter_opcode_t op; - uint16_t skip_offset; /* bytecode insn, if skip second test */ -} LTTNG_PACKED; - -struct cast_op { - filter_opcode_t op; -} LTTNG_PACKED; - -struct return_op { - filter_opcode_t op; -} LTTNG_PACKED; - -struct lttng_filter_bytecode_alloc { - uint32_t alloc_len; - struct lttng_filter_bytecode b; -}; - -static inline -unsigned int bytecode_get_len(struct lttng_filter_bytecode *bytecode) -{ - return bytecode->len; -} - -#endif /* _FILTER_BYTECODE_H */ diff --git a/src/lib/lttng-ctl/lttng-ctl-health.c b/src/lib/lttng-ctl/lttng-ctl-health.c index d6a3e4f13..91108c166 100644 --- a/src/lib/lttng-ctl/lttng-ctl-health.c +++ b/src/lib/lttng-ctl/lttng-ctl-health.c @@ -62,6 +62,7 @@ const char *sessiond_thread_name[NR_HEALTH_SESSIOND_TYPES] = { [ HEALTH_SESSIOND_TYPE_APP_REG_DISPATCH ] = "Session daemon application registration dispatcher", [ HEALTH_SESSIOND_TYPE_ROTATION ] = "Session daemon rotation manager", [ HEALTH_SESSIOND_TYPE_TIMER ] = "Session daemon timer manager", + [ HEALTH_SESSIOND_TYPE_ACTION_EXECUTOR ] = "Session daemon trigger action executor", }; static diff --git a/src/lib/lttng-ctl/lttng-ctl-helper.h b/src/lib/lttng-ctl/lttng-ctl-helper.h index f252bbbde..4ce33aa13 100644 --- a/src/lib/lttng-ctl/lttng-ctl-helper.h +++ b/src/lib/lttng-ctl/lttng-ctl-helper.h @@ -60,6 +60,23 @@ int lttng_ctl_ask_sessiond_fds_no_cmd_header(struct lttcomm_session_msg *lsm, return lttng_ctl_ask_sessiond_fds_varlen(lsm, fds, nb_fd, NULL, 0, NULL, NULL, NULL); } + +/* + * Calls lttng_ctl_ask_sessiond_fds_varlen() with fds with no expected command + * header and with varlen data. + */ +static inline int lttng_ctl_ask_sessiond_fds_varlen_no_cmd_header( + struct lttcomm_session_msg *lsm, + const int *fds, + size_t nb_fd, + void *vardata, + size_t vardata_len, + void **user_payload_buf) +{ + return lttng_ctl_ask_sessiond_fds_varlen(lsm, fds, nb_fd, vardata, + vardata_len, user_payload_buf, NULL, NULL); +} + /* * Use this if no variable length data needs to be sent. */ diff --git a/src/lib/lttng-ctl/lttng-ctl.c b/src/lib/lttng-ctl/lttng-ctl.c index 7622a5137..fadb116c6 100644 --- a/src/lib/lttng-ctl/lttng-ctl.c +++ b/src/lib/lttng-ctl/lttng-ctl.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -38,26 +39,11 @@ #include #include -#include "filter/filter-ast.h" -#include "filter/filter-parser.h" -#include "filter/filter-bytecode.h" -#include "filter/memstream.h" +#include +#include +#include #include "lttng-ctl-helper.h" -#ifdef DEBUG -static const int print_xml = 1; -#define dbg_printf(fmt, args...) \ - printf("[debug liblttng-ctl] " fmt, ## args) -#else -static const int print_xml = 0; -#define dbg_printf(fmt, args...) \ -do { \ - /* do nothing but check printf format */ \ - if (0) \ - printf("[debug liblttnctl] " fmt, ## args); \ -} while (0) -#endif - #define COPY_DOMAIN_PACKED(dst, src) \ do { \ struct lttng_domain _tmp_domain; \ @@ -930,133 +916,6 @@ error: return NULL; } -/* - * Generate the filter bytecode from a given filter expression string. Put the - * newly allocated parser context in ctxp and populate the lsm object with the - * expression len. - * - * Return 0 on success else a LTTNG_ERR_* code and ctxp is untouched. - */ -static int generate_filter(char *filter_expression, - struct lttcomm_session_msg *lsm, struct filter_parser_ctx **ctxp) -{ - int ret; - struct filter_parser_ctx *ctx = NULL; - FILE *fmem = NULL; - - assert(filter_expression); - assert(lsm); - assert(ctxp); - - /* - * Casting const to non-const, as the underlying function will use it in - * read-only mode. - */ - fmem = lttng_fmemopen((void *) filter_expression, - strlen(filter_expression), "r"); - if (!fmem) { - fprintf(stderr, "Error opening memory as stream\n"); - ret = -LTTNG_ERR_FILTER_NOMEM; - goto error; - } - ctx = filter_parser_ctx_alloc(fmem); - if (!ctx) { - fprintf(stderr, "Error allocating parser\n"); - ret = -LTTNG_ERR_FILTER_NOMEM; - goto filter_alloc_error; - } - ret = filter_parser_ctx_append_ast(ctx); - if (ret) { - fprintf(stderr, "Parse error\n"); - ret = -LTTNG_ERR_FILTER_INVAL; - goto parse_error; - } - if (print_xml) { - ret = filter_visitor_print_xml(ctx, stdout, 0); - if (ret) { - fflush(stdout); - fprintf(stderr, "XML print error\n"); - ret = -LTTNG_ERR_FILTER_INVAL; - goto parse_error; - } - } - - dbg_printf("Generating IR... "); - fflush(stdout); - ret = filter_visitor_ir_generate(ctx); - if (ret) { - fprintf(stderr, "Generate IR error\n"); - ret = -LTTNG_ERR_FILTER_INVAL; - goto parse_error; - } - dbg_printf("done\n"); - - dbg_printf("Validating IR... "); - fflush(stdout); - ret = filter_visitor_ir_check_binary_op_nesting(ctx); - if (ret) { - ret = -LTTNG_ERR_FILTER_INVAL; - goto parse_error; - } - - /* Normalize globbing patterns in the expression. */ - ret = filter_visitor_ir_normalize_glob_patterns(ctx); - if (ret) { - ret = -LTTNG_ERR_FILTER_INVAL; - goto parse_error; - } - - /* Validate strings used as literals in the expression. */ - ret = filter_visitor_ir_validate_string(ctx); - if (ret) { - ret = -LTTNG_ERR_FILTER_INVAL; - goto parse_error; - } - - /* Validate globbing patterns in the expression. */ - ret = filter_visitor_ir_validate_globbing(ctx); - if (ret) { - ret = -LTTNG_ERR_FILTER_INVAL; - goto parse_error; - } - - dbg_printf("done\n"); - - dbg_printf("Generating bytecode... "); - fflush(stdout); - ret = filter_visitor_bytecode_generate(ctx); - if (ret) { - fprintf(stderr, "Generate bytecode error\n"); - ret = -LTTNG_ERR_FILTER_INVAL; - goto parse_error; - } - dbg_printf("done\n"); - dbg_printf("Size of bytecode generated: %u bytes.\n", - bytecode_get_len(&ctx->bytecode->b)); - - lsm->u.enable.bytecode_len = sizeof(ctx->bytecode->b) - + bytecode_get_len(&ctx->bytecode->b); - lsm->u.enable.expression_len = strlen(filter_expression) + 1; - - /* No need to keep the memory stream. */ - if (fclose(fmem) != 0) { - PERROR("fclose"); - } - - *ctxp = ctx; - return 0; - -parse_error: - filter_ir_free(ctx); - filter_parser_ctx_free(ctx); -filter_alloc_error: - if (fclose(fmem) != 0) { - PERROR("fclose"); - } -error: - return ret; -} - /* * Enable event(s) for a channel, possibly with exclusions and a filter. * If no event name is specified, all events are enabled. @@ -1160,10 +1019,14 @@ int lttng_enable_event_with_exclusions(struct lttng_handle *handle, } } - ret = generate_filter(filter_expression, &lsm, &ctx); + ret = filter_parser_ctx_create_from_filter_expression(filter_expression, &ctx); if (ret) { goto filter_error; } + + lsm.u.enable.bytecode_len = sizeof(ctx->bytecode->b) + + bytecode_get_len(&ctx->bytecode->b); + lsm.u.enable.expression_len = strlen(filter_expression) + 1; } ret = lttng_dynamic_buffer_set_capacity(&send_buffer, @@ -1369,10 +1232,14 @@ int lttng_disable_event_ext(struct lttng_handle *handle, } } - ret = generate_filter(filter_expression, &lsm, &ctx); + ret = filter_parser_ctx_create_from_filter_expression(filter_expression, &ctx); if (ret) { goto filter_error; } + + lsm.u.enable.bytecode_len = sizeof(ctx->bytecode->b) + + bytecode_get_len(&ctx->bytecode->b); + lsm.u.enable.expression_len = strlen(filter_expression) + 1; } varlen_data = zmalloc(lsm.u.disable.bytecode_len @@ -2888,8 +2755,15 @@ end: int lttng_register_trigger(struct lttng_trigger *trigger) { int ret; + int reply_ret; struct lttcomm_session_msg lsm; struct lttng_dynamic_buffer buffer; + void *reply = NULL; + struct lttng_buffer_view reply_view; + struct lttng_trigger *reply_trigger = NULL; + bool send_fd = false; + int fd_to_send; + enum lttng_domain_type domain_type; lttng_dynamic_buffer_init(&buffer); if (!trigger) { @@ -2902,23 +2776,91 @@ int lttng_register_trigger(struct lttng_trigger *trigger) goto end; } - ret = lttng_trigger_serialize(trigger, &buffer); + domain_type = lttng_trigger_get_underlying_domain_type_restriction( + trigger); + + ret = lttng_trigger_serialize(trigger, &buffer, &fd_to_send); if (ret < 0) { ret = -LTTNG_ERR_UNK; goto end; } + if (getenv("LTTNG_REGISTER_TRIGGER_DRY_RUN")) { + /* + * Don't really send the request, just deserialize, validate + * that it is equal to the original trigger (to test + * serialization and deserialization), and return. + */ + struct lttng_buffer_view bv; + ssize_t sz; + + bv = lttng_buffer_view_from_dynamic_buffer(&buffer, 0, -1); + sz = lttng_trigger_create_from_buffer(&bv, &reply_trigger); + if (sz != bv.size) { + ret = -LTTNG_ERR_UNK; + goto end; + } + + if (!reply_trigger) { + ret = -LTTNG_ERR_UNK; + goto end; + } + + if (!lttng_trigger_is_equal(trigger, reply_trigger)) { + ret = -LTTNG_ERR_UNK; + goto end; + } + + /* Give it a dummy name. */ + lttng_trigger_set_name(trigger, "yop"); + + ret = 0; + goto end; + } + + send_fd = fd_to_send >= 0; + memset(&lsm, 0, sizeof(lsm)); lsm.cmd_type = LTTNG_REGISTER_TRIGGER; + lsm.domain.type = domain_type; lsm.u.trigger.length = (uint32_t) buffer.size; - ret = lttng_ctl_ask_sessiond_varlen_no_cmd_header(&lsm, buffer.data, - buffer.size, NULL); + reply_ret = lttng_ctl_ask_sessiond_fds_varlen_no_cmd_header(&lsm, + send_fd ? &fd_to_send : NULL, + send_fd ? 1 : 0, + buffer.data, + buffer.size, + &reply); + if (reply_ret < 0) { + ret = reply_ret; + goto end; + } else if (reply_ret == 0) { + /* Socket unexpectedly closed by the session daemon. */ + ret = -LTTNG_ERR_FATAL; + goto end; + } + + reply_view = lttng_buffer_view_init(reply, 0, reply_ret); + ret = lttng_trigger_create_from_buffer(&reply_view, &reply_trigger); + if (ret < 0) { + ret = -LTTNG_ERR_FATAL; + goto end; + } + + ret = lttng_trigger_assign(trigger, reply_trigger); + if (ret < 0) { + ret = -LTTNG_ERR_FATAL; + goto end; + } + + ret = 0; end: + free(reply); lttng_dynamic_buffer_reset(&buffer); + lttng_trigger_destroy(reply_trigger); return ret; } -int lttng_unregister_trigger(struct lttng_trigger *trigger) +int lttng_unregister_trigger(const struct lttng_trigger *trigger) { int ret; struct lttcomm_session_msg lsm; @@ -2935,7 +2877,7 @@ int lttng_unregister_trigger(struct lttng_trigger *trigger) goto end; } - ret = lttng_trigger_serialize(trigger, &buffer); + ret = lttng_trigger_serialize(trigger, &buffer, NULL); if (ret < 0) { ret = -LTTNG_ERR_UNK; goto end; @@ -2951,6 +2893,49 @@ end: return ret; } +/* + * Ask the session daemon for all registered triggers. + * Allocate a lttng_triggers collection. + * On error, returns a negative value. + */ +int lttng_list_triggers(struct lttng_triggers **triggers) +{ + int ret; + int reply_ret; + struct lttcomm_session_msg lsm; + struct lttng_buffer_view reply_view; + struct lttng_triggers *local_triggers = NULL; + void *reply = NULL; + + memset(&lsm, 0, sizeof(lsm)); + lsm.cmd_type = LTTNG_LIST_TRIGGERS; + + reply_ret = lttng_ctl_ask_sessiond(&lsm, &reply); + if (reply_ret < 0) { + ret = reply_ret; + goto end; + } else if (reply_ret == 0) { + /* Socket unexpectedly closed by the session daemon. */ + ret = -LTTNG_ERR_FATAL; + goto end; + } + + reply_view = lttng_buffer_view_init(reply, 0, reply_ret); + ret = lttng_triggers_create_from_buffer(&reply_view, &local_triggers); + if (ret < 0) { + ret = -LTTNG_ERR_FATAL; + goto end; + } + + *triggers = local_triggers; + local_triggers = NULL; + ret = 0; +end: + free(reply); + free(local_triggers); + return ret; +} + /* * lib constructor. */ diff --git a/src/lib/lttng-ctl/snapshot.c b/src/lib/lttng-ctl/snapshot.c index 0aebf1575..8d3ec2013 100644 --- a/src/lib/lttng-ctl/snapshot.c +++ b/src/lib/lttng-ctl/snapshot.c @@ -238,29 +238,29 @@ void lttng_snapshot_output_destroy(struct lttng_snapshot_output *obj) * Getter family functions of snapshot output. */ -uint32_t lttng_snapshot_output_get_id(struct lttng_snapshot_output *output) +uint32_t lttng_snapshot_output_get_id(const struct lttng_snapshot_output *output) { return output->id; } const char *lttng_snapshot_output_get_name( - struct lttng_snapshot_output *output) + const struct lttng_snapshot_output *output) { return output->name; } -const char *lttng_snapshot_output_get_data_url(struct lttng_snapshot_output *output) +const char *lttng_snapshot_output_get_data_url(const struct lttng_snapshot_output *output) { return output->data_url; } -const char *lttng_snapshot_output_get_ctrl_url(struct lttng_snapshot_output *output) +const char *lttng_snapshot_output_get_ctrl_url(const struct lttng_snapshot_output *output) { return output->ctrl_url; } uint64_t lttng_snapshot_output_get_maxsize( - struct lttng_snapshot_output *output) + const struct lttng_snapshot_output *output) { return output->max_size; } @@ -323,3 +323,126 @@ int lttng_snapshot_output_set_data_url(const char *url, lttng_ctl_copy_string(output->data_url, url, sizeof(output->data_url)); return 0; } + +int lttng_snapshot_output_set_local_path(const char *path, + struct lttng_snapshot_output *output) +{ + int ret; + struct lttng_uri *uris = NULL; + ssize_t num_uris; + + if (!path || !output) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + num_uris = uri_parse_str_urls(path, NULL, &uris); + if (num_uris != 1) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + if (uris[0].dtype != LTTNG_DST_PATH) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + ret = lttng_strncpy(output->ctrl_url, path, sizeof(output->ctrl_url)); + if (ret != 0) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + +end: + free(uris); + return ret; +} + +int lttng_snapshot_output_set_network_url(const char *url, + struct lttng_snapshot_output *output) +{ + int ret; + struct lttng_uri *uris = NULL; + ssize_t num_uris; + + if (!url || !output) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + num_uris = uri_parse_str_urls(url, NULL, &uris); + if (num_uris != 2) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + if (uris[0].dtype != LTTNG_DST_IPV4 && + uris[0].dtype != LTTNG_DST_IPV6) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + if (uris[1].dtype != LTTNG_DST_IPV4 && + uris[1].dtype != LTTNG_DST_IPV6) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + ret = lttng_strncpy(output->ctrl_url, url, sizeof(output->ctrl_url)); + if (ret != 0) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + +end: + free(uris); + return ret; +} + +int lttng_snapshot_output_set_network_urls( + const char *ctrl_url, const char *data_url, + struct lttng_snapshot_output *output) +{ + int ret; + struct lttng_uri *uris = NULL; + ssize_t num_uris; + + if (!ctrl_url || !data_url || !output) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + num_uris = uri_parse_str_urls(ctrl_url, data_url, &uris); + if (num_uris != 2) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + if (uris[0].dtype != LTTNG_DST_IPV4 && + uris[0].dtype != LTTNG_DST_IPV6) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + if (uris[1].dtype != LTTNG_DST_IPV4 && + uris[1].dtype != LTTNG_DST_IPV6) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + ret = lttng_strncpy(output->ctrl_url, ctrl_url, sizeof(output->ctrl_url)); + if (ret != 0) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + + ret = lttng_strncpy(output->data_url, data_url, sizeof(output->data_url)); + if (ret != 0) { + ret = -LTTNG_ERR_INVALID; + goto end; + } + +end: + free(uris); + return ret; +} diff --git a/tests/regression/Makefile.am b/tests/regression/Makefile.am index cbac90da7..3499e3cb1 100644 --- a/tests/regression/Makefile.am +++ b/tests/regression/Makefile.am @@ -27,8 +27,16 @@ TESTS = tools/filtering/test_invalid_filter \ tools/crash/test_crash \ tools/regen-metadata/test_ust \ tools/regen-statedump/test_ust \ - tools/notification/test_notification_ust \ - tools/notification/test_notification_kernel \ + tools/notification/test_notification_ust_error \ + tools/notification/test_notification_ust_buffer_usage \ + tools/notification/test_notification_ust_capture \ + tools/notification/test_notification_ust_event_rule_condition_exclusion \ + tools/notification/test_notification_kernel_error \ + tools/notification/test_notification_kernel_buffer_usage \ + tools/notification/test_notification_kernel_capture \ + tools/notification/test_notification_kernel_instrumentation \ + tools/notification/test_notification_kernel_syscall \ + tools/notification/test_notification_kernel_userspace_probe \ tools/notification/test_notification_multi_app \ tools/rotation/test_ust \ tools/rotation/test_kernel \ @@ -36,10 +44,13 @@ TESTS = tools/filtering/test_invalid_filter \ tools/rotation/test_schedule_api \ tools/metadata/test_kernel \ tools/working-directory/test_relayd_working_directory \ - tools/notification/test_notification_multi_app \ tools/clear/test_ust \ tools/clear/test_kernel \ - tools/tracker/test_event_tracker + tools/tracker/test_event_tracker \ + tools/trigger/start-stop/test_start_stop \ + tools/trigger/test_add_trigger_cli \ + tools/trigger/test_list_triggers_cli \ + tools/trigger/test_remove_trigger_cli if HAVE_LIBLTTNG_UST_CTL SUBDIRS += ust diff --git a/tests/regression/kernel/test_callstack b/tests/regression/kernel/test_callstack index c4a6200da..42c3fcce4 100755 --- a/tests/regression/kernel/test_callstack +++ b/tests/regression/kernel/test_callstack @@ -33,12 +33,15 @@ function lttng_track_pid() function run_workload() { local TEST_APP=$1 + # shift the first argument, passing along the other args if any to the + # test app. + shift local start_file_sync start_file_sync=$(mktemp -u) lttng_untrack_all - ./"$TEST_APP" "$start_file_sync" & + ./"$TEST_APP" "$start_file_sync" "$@" & PID=$! lttng_track_pid $PID @@ -108,7 +111,7 @@ function test_kernel_callstack() lttng_enable_kernel_syscall_ok "$SESSION_NAME" "$EVENT_NAME" "$CHANNEL_NAME" add_context_kernel_ok "$SESSION_NAME" "$CHANNEL_NAME" "callstack-kernel" - run_workload $TEST_APP_KERNELSPACE + run_workload "$TEST_APP_KERNELSPACE" "/proc/cpuinfo" "/proc/cmdline" destroy_lttng_session_ok "$SESSION_NAME" diff --git a/tests/regression/kernel/test_syscall b/tests/regression/kernel/test_syscall index 487ee668e..d09d61a95 100755 --- a/tests/regression/kernel/test_syscall +++ b/tests/regression/kernel/test_syscall @@ -27,7 +27,7 @@ function trace_testapp() lttng_untrack_kernel_all_ok # Launch the testapp and save its Process ID - ./"$TESTCMD" "$start_file_sync" & + ./"$TESTCMD" "$start_file_sync" "/proc/cpuinfo" "/proc/cmdline" & PID=$! # Set LTTng to track this PID and start the tracing diff --git a/tests/regression/tools/Makefile.am b/tests/regression/tools/Makefile.am index 22691e5e2..d561a6479 100644 --- a/tests/regression/tools/Makefile.am +++ b/tests/regression/tools/Makefile.am @@ -2,4 +2,4 @@ SUBDIRS = streaming filtering health tracefile-limits snapshots live exclusion save-load mi \ wildcard crash regen-metadata regen-statedump notification rotation \ - base-path metadata working-directory relayd-grouping clear tracker + base-path metadata working-directory relayd-grouping clear tracker trigger diff --git a/tests/regression/tools/notification/Makefile.am b/tests/regression/tools/notification/Makefile.am index bc2c9123c..b1168ffac 100644 --- a/tests/regression/tools/notification/Makefile.am +++ b/tests/regression/tools/notification/Makefile.am @@ -10,8 +10,22 @@ noinst_PROGRAMS = base_client notification rotation if NO_SHARED CLEANFILES = libpause_consumer.so libpause_consumer.so.debug -EXTRA_DIST = test_notification_ust test_notification_kernel test_notification_multi_app base_client.c notification.c consumer_testpoints.c - +EXTRA_DIST = \ + base_client.c \ + consumer_testpoints.c \ + notification.c \ + test_notification_kernel_buffer_usage \ + test_notification_kernel_capture \ + test_notification_kernel_error \ + test_notification_kernel_instrumentation \ + test_notification_kernel_syscall \ + test_notification_kernel_userspace_probe \ + test_notification_multi_app \ + test_notification_ust_buffer_usage \ + test_notification_ust_capture \ + test_notification_ust_error \ + test_notification_ust_event_rule_condition_exclusion\ + util_event_generator.sh else # In order to test the health check feature, the helper library @@ -36,9 +50,32 @@ notification_LDADD = $(LIB_LTTNG_CTL) $(LIBTAP) -lm rotation_SOURCES = rotation.c rotation_LDADD = $(LIB_LTTNG_CTL) $(LIBTAP) -lm -noinst_SCRIPTS = test_notification_ust test_notification_kernel test_notification_multi_app test_rotation -EXTRA_DIST = test_notification_ust test_notification_kernel test_notification_multi_app test_rotation +noinst_SCRIPTS = \ + test_notification_kernel_buffer_usage \ + test_notification_kernel_error \ + test_notification_kernel_instrumentation \ + test_notification_kernel_syscall \ + test_notification_kernel_userspace_probe \ + test_notification_multi_app \ + test_notification_ust_buffer_usage \ + test_notification_ust_error \ + test_notification_ust_event_rule_condition_exclusion \ + test_rotation +EXTRA_DIST = \ + test_notification_kernel_buffer_usage \ + test_notification_kernel_capture \ + test_notification_kernel_error \ + test_notification_kernel_instrumentation \ + test_notification_kernel_syscall \ + test_notification_kernel_userspace_probe \ + test_notification_multi_app \ + test_notification_ust_buffer_usage \ + test_notification_ust_capture \ + test_notification_ust_error \ + test_notification_ust_event_rule_condition_exclusion \ + test_rotation \ + util_event_generator.sh all-local: @if [ x"$(srcdir)" != x"$(builddir)" ]; then \ diff --git a/tests/regression/tools/notification/notification.c b/tests/regression/tools/notification/notification.c index 535a71fce..0e9013c45 100644 --- a/tests/regression/tools/notification/notification.c +++ b/tests/regression/tools/notification/notification.c @@ -29,691 +29,2431 @@ #include #include #include +#include #include #include +#include +#include +#include +#include +#include #include +#include #include #include +#include #include -#include +#include #include -#define NUM_TESTS 104 +/* A callback to populate the condition capture descriptor */ +typedef int (*condition_capture_desc_cb)(struct lttng_condition *condition); + +/* A callback for captured field validation */ +typedef int (*validate_cb)(const struct lttng_event_field_value *event_field, unsigned iteration); int nb_args = 0; int named_pipe_args_start = 0; -pid_t app_pid = -1; +pid_t app_pid = 0; const char *app_state_file = NULL; +enum field_type { + FIELD_TYPE_PAYLOAD, + FIELD_TYPE_CONTEXT, + FIELD_TYPE_APP_CONTEXT, + FIELD_TYPE_ARRAY_FIELD, +}; + +struct capture_base_field_tuple { + char* field_name; + enum field_type field_type; + bool expected_ust; // Do we expect a capture? + bool expected_kernel; // Do we expect a capture? + validate_cb validate_ust; + validate_cb validate_kernel; +}; + static -void wait_on_file(const char *path, bool file_exist) +const char *field_value_type_to_str(enum lttng_event_field_value_type type) { - if (!path) { - return; + switch (type) { + case LTTNG_EVENT_FIELD_VALUE_TYPE_UNKNOWN: + return "UNKNOWN"; + case LTTNG_EVENT_FIELD_VALUE_TYPE_INVALID: + return "INVALID"; + case LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT: + return "UNSIGNED INT"; + case LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_INT: + return "SIGNED INT"; + case LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM: + return "UNSIGNED ENUM"; + case LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM: + return "SIGNED ENUM"; + case LTTNG_EVENT_FIELD_VALUE_TYPE_REAL: + return "REAL"; + case LTTNG_EVENT_FIELD_VALUE_TYPE_STRING: + return "STRING"; + case LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY: + return "ARRAY"; + default: + abort(); } - for (;;) { - int ret; - struct stat buf; +} - ret = stat(path, &buf); - if (ret == -1 && errno == ENOENT) { - if (file_exist) { - /* - * The file does not exist. wait a bit and - * continue looping until it does. - */ - (void) poll(NULL, 0, 10); - continue; - } +static int validate_type( + const struct lttng_event_field_value *event_field, + enum lttng_event_field_value_type expect) +{ + int ret; + enum lttng_event_field_value_type value; - /* - * File does not exist and the exit condition we want. - * Break from the loop and return. - */ - break; - } - if (ret) { - perror("stat"); - exit(EXIT_FAILURE); - } - /* - * stat() returned 0, so the file exists. break now only if - * that's the exit condition we want. - */ - if (file_exist) { - break; - } + value = lttng_event_field_value_get_type(event_field); + if (value == LTTNG_EVENT_FIELD_VALUE_TYPE_INVALID) { + ret = 1; + goto end; } + + ret = (expect == value); + ok(ret, "Expected field type: %s got %s", + field_value_type_to_str(expect), + field_value_type_to_str(value)); + + ret = !ret; + +end: + return ret; } -static -int write_pipe(const char *path, uint8_t data) +/* + * Validate unsigned captured field against the iteration number. + * The iteration number is always unsigned and will always be compared to value + * under MAX_UINT. + */ +static int validate_unsigned_int_field( + const struct lttng_event_field_value *event_field, + unsigned int iteration) { - int ret = 0; - int fd = 0; + int ret; + uint64_t value; + enum lttng_event_field_value_status status; - fd = open(path, O_WRONLY | O_NONBLOCK); - if (fd < 0) { - perror("Could not open consumer control named pipe"); + ret = validate_type(event_field, LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT); + if (ret) { goto end; } - ret = write(fd, &data , sizeof(data)); - if (ret < 1) { - perror("Named pipe write failed"); - if (close(fd)) { - perror("Named pipe close failed"); - } - ret = -1; + status = lttng_event_field_value_unsigned_int_get_value( + event_field , &value); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_unsigned_int_get_value"); + ret = 1; goto end; } - ret = close(fd); - if (ret < 0) { - perror("Name pipe closing failed"); - ret = -1; - goto end; - } + ret = (value == (uint64_t) iteration); + ok (ret, "Expected unsigned int of value: %u got %" PRIu64, iteration, value); + + ret = !ret; + end: + return ret; } -static -int stop_consumer(const char **argv) +/* + * Validate signed captured field. + * Value should be -1. + */ +static int validate_signed_int_field( + const struct lttng_event_field_value *event_field, + unsigned int iteration) { - int ret = 0, i; + int ret; + int64_t expected = -1; + int64_t value; + enum lttng_event_field_value_status status; - for (i = named_pipe_args_start; i < nb_args; i++) { - ret = write_pipe(argv[i], 49); + /* Unused */ + (void) iteration; + + ret = validate_type(event_field, LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_INT); + if (ret) { + goto end; + } + + status = lttng_event_field_value_signed_int_get_value( + event_field , &value); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_signed_int_get_value"); + ret = 1; + goto end; } + + ret = (value == expected); + ok(ret, "Expected signed int of value: %" PRId64 " got %" PRId64, expected, value); + + ret = !ret; + +end: + return ret; } -static -int resume_consumer(const char **argv) +/* + * Validate array of unsigned int. + */ +static int validate_array_unsigned_int_field( + const struct lttng_event_field_value *event_field, + unsigned int iteration) { - int ret = 0, i; + int ret; + enum lttng_event_field_value_status status; + unsigned int expected = 3; + unsigned int count; - for (i = named_pipe_args_start; i < nb_args; i++) { - ret = write_pipe(argv[i], 0); + /* Unused */ + (void) iteration; + + ret = validate_type(event_field, LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY); + if (ret) { + goto end; + } + + status = lttng_event_field_value_array_get_length(event_field, &count); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_array_get_length"); + ret = 1; + goto end; + } + + ret = (count == expected); + ok(ret, "Expected %d subelements got %d", expected, count); + if (!ret) { + ret = 1; + goto end; + } + + for (unsigned int i = 1; i < count + 1; i++) { + const struct lttng_event_field_value *value; + status = lttng_event_field_value_array_get_element_at_index( + event_field, i - 1, &value); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_array_get_element_at_index"); + ret = 1; + goto end; + } + ret = validate_unsigned_int_field(value, i); + if (ret) { + goto end; + } } + + ret = 0; +end: + return ret; } - -static -int suspend_application(void) +static int validate_array_unsigned_int_field_at_index( + const struct lttng_event_field_value *event_field, + unsigned int iteration) { int ret; - struct stat buf; + uint64_t expected_value = 2; + enum lttng_event_field_value_status status; + uint64_t value; - if (!stat(app_state_file, &buf)) { - fail("App is already in a suspended state."); - ret = -1; - goto error; - } + /* Unused */ + (void) iteration; - /* - * Send SIGUSR1 to application instructing it to bypass tracepoint. - */ - ret = kill(app_pid, SIGUSR1); + ret = validate_type(event_field, LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_INT); if (ret) { - fail("SIGUSR1 failed. errno %d", errno); - ret = -1; - goto error; + goto end; } - wait_on_file(app_state_file, true); + status = lttng_event_field_value_unsigned_int_get_value( + event_field , &value); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_unsigned_int_get_value"); + ret = 1; + goto end; + } -error: - return ret; + ret = (value == expected_value); + ok (ret, "Expected unsigned int of value: %u got %" PRIu64, + expected_value, value); + ret = 0; +end: + return ret; } -static -int resume_application() +/* + * Validate sequence for a string (seqfield1): + * + * Value: "test" in utf8 [116, 101, 115, 116] + */ +static int validate_seqfield1( + const struct lttng_event_field_value *event_field, + unsigned int iteration) { int ret; - struct stat buf; + enum lttng_event_field_value_status status; + unsigned int count; + unsigned int expect[4] = {116, 101, 115, 116}; - ret = stat(app_state_file, &buf); - if (ret == -1 && errno == ENOENT) { - fail("State file does not exist"); - goto error; - } + /* Unused */ + (void) iteration; + + ret = validate_type(event_field, LTTNG_EVENT_FIELD_VALUE_TYPE_ARRAY); if (ret) { - perror("stat"); - goto error; + goto end; } - ret = kill(app_pid, SIGUSR1); - if (ret) { - fail("SIGUSR1 failed. errno %d", errno); - ret = -1; - goto error; + status = lttng_event_field_value_array_get_length(event_field, &count); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_array_get_length"); + ret = 1; + goto end; } - wait_on_file(app_state_file, false); + ret = (count == 4); + ok(ret, "Expected 4 subelement got %d", count); + if (!ret) { + ret = 1; + goto end; + } -error: - return ret; + for (unsigned int i = 0; i < count ; i++) { + const struct lttng_event_field_value *value; + status = lttng_event_field_value_array_get_element_at_index( + event_field, i, &value); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_array_get_element_at_index"); + ret = 1; + goto end; + } + ret = validate_unsigned_int_field(value, expect[i]); + if (ret) { + goto end; + } + } -} + ret = 0; +end: + return ret; +} -static -void test_triggers_buffer_usage_condition(const char *session_name, - const char *channel_name, - enum lttng_domain_type domain_type, - enum lttng_condition_type condition_type) +static int validate_string( + const struct lttng_event_field_value *event_field, + const char *expect) { - unsigned int test_vector_size = 5, i; - enum lttng_condition_status condition_status; - struct lttng_action *action; + int ret; + const char *value = NULL; - /* Set-up */ - action = lttng_action_notify_create(); - if (!action) { - fail("Setup error on action creation"); + ret = validate_type(event_field, LTTNG_EVENT_FIELD_VALUE_TYPE_STRING); + if (ret) { goto end; } - /* Test lttng_register_trigger with null value */ - ok(lttng_register_trigger(NULL) == -LTTNG_ERR_INVALID, "Registering a NULL trigger fails as expected"); + value = lttng_event_field_value_string_get_value(event_field); + if (!value) { + fail("lttng_event_field_value_array_get_length"); + ret = 1; + goto end; + } - /* Test: register a trigger */ + ok(!strcmp(value, expect), "Expected string: \"%s\" got \"%s\"", expect, value); - for (i = 0; i < pow(2,test_vector_size); i++) { - int loop_ret = 0; - char *test_tuple_string = NULL; - unsigned int mask_position = 0; - bool session_name_set = false; - bool channel_name_set = false; - bool threshold_ratio_set = false; - bool threshold_byte_set = false; - bool domain_type_set = false; + ret = 0; +end: - struct lttng_trigger *trigger = NULL; - struct lttng_condition *condition = NULL; + return ret; +} - /* Create base condition */ - switch (condition_type) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - condition = lttng_condition_buffer_usage_low_create(); - break; - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - condition = lttng_condition_buffer_usage_high_create(); - break; - default: - loop_ret = 1; - goto loop_end; - } +/* + * Validate string. Expected value is "test". + */ +static int validate_string_test( + const struct lttng_event_field_value *event_field, + unsigned int iteration) +{ + int ret; + const char *expect = "test"; - if (!condition) { - loop_ret = 1; - goto loop_end; + /* Unused */ + (void) iteration; - } + ret = validate_string(event_field, expect); + return ret; +} - /* Prepare the condition for trigger registration test */ +/* + * Validate escaped string. Expected value is "\*". + */ +static int validate_string_escaped( + const struct lttng_event_field_value *event_field, + unsigned int iteration) +{ + int ret; + const char *expect = "\\*"; - /* Set session name */ - if ((1 << mask_position) & i) { - condition_status = lttng_condition_buffer_usage_set_session_name( - condition, session_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - loop_ret = 1; - goto loop_end; - } - session_name_set = true; - } - mask_position++; + /* Unused */ + (void) iteration; - /* Set channel name */ - if ((1 << mask_position) & i) { - condition_status = lttng_condition_buffer_usage_set_channel_name( - condition, channel_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - loop_ret = 1; - goto loop_end; - } - channel_name_set = true; + ret = validate_string(event_field, expect); + return ret; +} + +/* + * Validate real field. + */ +static int validate_real( + const struct lttng_event_field_value *event_field, + double expect) +{ + int ret; + double value; + enum lttng_event_field_value_status status; + + ret = validate_type(event_field, LTTNG_EVENT_FIELD_VALUE_TYPE_REAL); + if (ret) { + goto end; + } + + status = lttng_event_field_value_real_get_value(event_field, &value); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_real_get_value"); + ret = 1; + goto end; + } + + ret = (value == expect); + ok(ret, "Real expected: %f got: %f", expect, value); + + ret = !ret; +end: + return ret; +} + +/* + * Validate floatfield. + */ +static int validate_floatfield( + const struct lttng_event_field_value *event_field, + unsigned int iteration) +{ + int ret; + double expect = 2222.0; + + /* Unused */ + (void) iteration; + + ret = validate_real(event_field, expect); + return ret; +} + +/* + * Validate doublefield. + */ +static int validate_doublefield( + const struct lttng_event_field_value *event_field, + unsigned int iteration) +{ + int ret; + double expect = 2.0; + + /* Unused */ + (void) iteration; + + ret = validate_real(event_field, expect); + return ret; +} + +/* + * Validate enum0: enum0 = ( "AUTO: EXPECT 0" : container = 0 ) + */ +static int validate_enum0(const struct lttng_event_field_value *event_field, + unsigned int iteration) +{ + int ret; + enum lttng_event_field_value_status status; + uint64_t value; + uint64_t expected_value = 0; + + /* Unused */ + (void) iteration; + + ret = validate_type(event_field, + LTTNG_EVENT_FIELD_VALUE_TYPE_UNSIGNED_ENUM); + if (ret) { + goto end; + } + + status = lttng_event_field_value_unsigned_int_get_value( + event_field, &value); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_unsigned_int_get_value"); + ret = 1; + goto end; + } + + ok(value == expected_value, + "Enum value expected: %" PRIu64 " got %" PRIu64, + expected_value, value); + +end: + return ret; +} + +/* + * Validate enumnegative: enumnegative = ( "AUTO: EXPECT 0" : container = 0 ) + * + * We expect 2 labels here. + */ +static int validate_enumnegative( + const struct lttng_event_field_value *event_field, + unsigned int iteration) +{ + int ret; + enum lttng_event_field_value_status status; + int64_t value; + int64_t expected_value = -1; + + /* Unused */ + (void) iteration; + + ret = validate_type( + event_field, LTTNG_EVENT_FIELD_VALUE_TYPE_SIGNED_ENUM); + if (ret) { + goto end; + } + + status = lttng_event_field_value_signed_int_get_value( + event_field, &value); + if (status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + fail("lttng_event_field_value_unsigned_int_get_value"); + ret = 1; + goto end; + } + + ok(value == expected_value, + "Enum value expected: %" PRId64 " got %" PRId64, + expected_value, value); + +end: + return ret; +} + +static int validate_context_procname_ust( + const struct lttng_event_field_value *event_field, + unsigned int iteration) +{ + int ret; + + /* Unused */ + (void) iteration; + + ret = validate_string(event_field, "gen-ust-events"); + return ret; +} + +static int validate_context_procname_kernel( + const struct lttng_event_field_value *event_field, + unsigned int iteration) +{ + int ret; + + /* Unused */ + (void) iteration; + + ret = validate_string(event_field, "echo"); + return ret; +} + +struct capture_base_field_tuple test_capture_base_fields[] = { + {"DOESNOTEXIST", FIELD_TYPE_PAYLOAD, false, false, NULL, NULL}, + {"intfield", FIELD_TYPE_PAYLOAD, true, true, validate_unsigned_int_field, validate_unsigned_int_field}, + {"longfield", FIELD_TYPE_PAYLOAD, true, true, validate_unsigned_int_field, validate_unsigned_int_field}, + {"signedfield", FIELD_TYPE_PAYLOAD, true, true, validate_signed_int_field, validate_signed_int_field}, + {"arrfield1", FIELD_TYPE_PAYLOAD, true, true, validate_array_unsigned_int_field, validate_array_unsigned_int_field}, + {"arrfield2", FIELD_TYPE_PAYLOAD, true, true, validate_string_test, validate_string_test}, + {"arrfield3", FIELD_TYPE_PAYLOAD, true, true, validate_array_unsigned_int_field, validate_array_unsigned_int_field}, + {"seqfield1", FIELD_TYPE_PAYLOAD, true, true, validate_seqfield1, validate_seqfield1}, + {"seqfield2", FIELD_TYPE_PAYLOAD, true, true, validate_string_test, validate_string_test}, + {"seqfield3", FIELD_TYPE_PAYLOAD, true, true, validate_array_unsigned_int_field, validate_array_unsigned_int_field}, + {"seqfield4", FIELD_TYPE_PAYLOAD, true, true, validate_array_unsigned_int_field, validate_array_unsigned_int_field}, + {"arrfield1[1]", FIELD_TYPE_ARRAY_FIELD, true, true, validate_array_unsigned_int_field_at_index, validate_array_unsigned_int_field_at_index}, + {"stringfield", FIELD_TYPE_PAYLOAD, true, true, validate_string_test, validate_string_test}, + {"stringfield2", FIELD_TYPE_PAYLOAD, true, true, validate_string_escaped, validate_string_escaped}, + {"floatfield", FIELD_TYPE_PAYLOAD, true, false, validate_floatfield, validate_floatfield}, + {"doublefield", FIELD_TYPE_PAYLOAD, true, false, validate_doublefield, validate_doublefield}, + {"enum0", FIELD_TYPE_PAYLOAD, true, true, validate_enum0, validate_enum0}, + {"enumnegative", FIELD_TYPE_PAYLOAD, true, true, validate_enumnegative, validate_enumnegative}, + {"$ctx.procname", FIELD_TYPE_CONTEXT, true, true, validate_context_procname_ust, validate_context_procname_kernel}, +}; + +static const char *get_notification_trigger_name( + struct lttng_notification *notification) +{ + const char *name = NULL; + enum lttng_evaluation_status status; + const struct lttng_evaluation *evaluation; + evaluation = lttng_notification_get_evaluation(notification); + if (evaluation == NULL) { + fail("lttng_notification_get_evaluation"); + goto end; + } + + switch (lttng_evaluation_get_type(evaluation)) { + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + { + status = lttng_evaluation_event_rule_get_trigger_name( + evaluation, &name); + if (status != LTTNG_EVALUATION_STATUS_OK) { + fail("lttng_evaluation_event_rule_get_trigger_name"); + name = NULL; + goto end; + } + break; + } + default: + fail("Wrong notification evaluation type \n"); + goto end; + } +end: + return name; +} + +static int validator_notification_trigger_name( + struct lttng_notification *notification, + const char *trigger_name) +{ + int ret; + bool name_is_equal; + const char *name; + + assert(notification); + assert(trigger_name); + + name = get_notification_trigger_name(notification); + if (name == NULL) { + ret = 1; + goto end; + } + + name_is_equal = (strcmp(trigger_name, name) == 0); + ok(name_is_equal, "Expected trigger name: %s got %s", trigger_name, + name); + + ret = !name_is_equal; + +end: + return ret; +} + +static +void wait_on_file(const char *path, bool file_exist) +{ + if (!path) { + return; + } + for (;;) { + int ret; + struct stat buf; + + ret = stat(path, &buf); + if (ret == -1 && errno == ENOENT) { + if (file_exist) { + /* + * The file does not exist. wait a bit and + * continue looping until it does. + */ + (void) poll(NULL, 0, 10); + continue; + } + + /* + * File does not exist and the exit condition we want. + * Break from the loop and return. + */ + break; + } + if (ret) { + perror("stat"); + exit(EXIT_FAILURE); + } + /* + * stat() returned 0, so the file exists. break now only if + * that's the exit condition we want. + */ + if (file_exist) { + break; + } + } +} + +static +int write_pipe(const char *path, uint8_t data) +{ + int ret = 0; + int fd = 0; + + fd = open(path, O_WRONLY | O_NONBLOCK); + if (fd < 0) { + perror("Could not open consumer control named pipe"); + goto end; + } + + ret = write(fd, &data , sizeof(data)); + if (ret < 1) { + perror("Named pipe write failed"); + if (close(fd)) { + perror("Named pipe close failed"); + } + ret = -1; + goto end; + } + + ret = close(fd); + if (ret < 0) { + perror("Name pipe closing failed"); + ret = -1; + goto end; + } +end: + return ret; +} + +static +int stop_consumer(const char **argv) +{ + int ret = 0, i; + + for (i = named_pipe_args_start; i < nb_args; i++) { + ret = write_pipe(argv[i], 49); + } + return ret; +} + +static +int resume_consumer(const char **argv) +{ + int ret = 0, i; + + for (i = named_pipe_args_start; i < nb_args; i++) { + ret = write_pipe(argv[i], 0); + } + return ret; +} + +static +int suspend_application(void) +{ + int ret; + struct stat buf; + + if (!stat(app_state_file, &buf)) { + fail("App is already in a suspended state."); + ret = -1; + goto error; + } + + /* + * Send SIGUSR1 to application instructing it to bypass tracepoint. + */ + assert(app_pid > 1); + + ret = kill(app_pid, SIGUSR1); + if (ret) { + fail("SIGUSR1 failed. errno %d", errno); + ret = -1; + goto error; + } + + wait_on_file(app_state_file, true); + +error: + return ret; + +} + +static +int resume_application() +{ + int ret; + struct stat buf; + + ret = stat(app_state_file, &buf); + if (ret == -1 && errno == ENOENT) { + fail("State file does not exist"); + goto error; + } + if (ret) { + perror("stat"); + goto error; + } + + assert(app_pid > 1); + + ret = kill(app_pid, SIGUSR1); + if (ret) { + fail("SIGUSR1 failed. errno %d", errno); + ret = -1; + goto error; + } + + wait_on_file(app_state_file, false); + +error: + return ret; + +} + + +static +void test_triggers_buffer_usage_condition(const char *session_name, + const char *channel_name, + enum lttng_domain_type domain_type, + enum lttng_condition_type condition_type) +{ + unsigned int test_vector_size = 5, i; + enum lttng_condition_status condition_status; + struct lttng_action *action; + + /* Set-up */ + action = lttng_action_notify_create(); + if (!action) { + fail("Setup error on action creation"); + goto end; + } + + /* Test lttng_register_trigger with null value */ + ok(lttng_register_trigger(NULL) == -LTTNG_ERR_INVALID, "Registering a NULL trigger fails as expected"); + + /* Test: register a trigger */ + + for (i = 0; i < pow(2,test_vector_size); i++) { + int loop_ret = 0; + char *test_tuple_string = NULL; + unsigned int mask_position = 0; + bool session_name_set = false; + bool channel_name_set = false; + bool threshold_ratio_set = false; + bool threshold_byte_set = false; + bool domain_type_set = false; + + struct lttng_trigger *trigger = NULL; + struct lttng_condition *condition = NULL; + + /* Create base condition */ + switch (condition_type) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + condition = lttng_condition_buffer_usage_low_create(); + break; + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + condition = lttng_condition_buffer_usage_high_create(); + break; + default: + loop_ret = 1; + goto loop_end; + } + + if (!condition) { + loop_ret = 1; + goto loop_end; + + } + + /* Prepare the condition for trigger registration test */ + + /* Set session name */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_session_name( + condition, session_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + session_name_set = true; + } + mask_position++; + + /* Set channel name */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_channel_name( + condition, channel_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + channel_name_set = true; + } + mask_position++; + + /* Set threshold ratio */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_threshold_ratio( + condition, 0.0); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + threshold_ratio_set = true; + } + mask_position++; + + /* Set threshold byte */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_threshold( + condition, 0); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + threshold_byte_set = true; + } + mask_position++; + + /* Set domain type */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_domain_type( + condition, LTTNG_DOMAIN_UST); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + domain_type_set = true; + } + + /* Safety check */ + if (mask_position != test_vector_size -1) { + assert("Logic error for test vector generation"); + } + + loop_ret = asprintf(&test_tuple_string, "session name %s, channel name %s, threshold ratio %s, threshold byte %s, domain type %s", + session_name_set ? "set" : "unset", + channel_name_set ? "set" : "unset", + threshold_ratio_set ? "set" : "unset", + threshold_byte_set ? "set" : "unset", + domain_type_set? "set" : "unset"); + if (!test_tuple_string || loop_ret < 0) { + loop_ret = 1; + goto loop_end; + } + + /* Create trigger */ + trigger = lttng_trigger_create(condition, action); + if (!trigger) { + loop_ret = 1; + goto loop_end; + } + + loop_ret = lttng_register_trigger(trigger); + +loop_end: + if (loop_ret == 1) { + fail("Setup error occurred for tuple: %s", test_tuple_string); + goto loop_cleanup; + } + + /* This combination happens three times */ + if (session_name_set && channel_name_set + && (threshold_ratio_set || threshold_byte_set) + && domain_type_set) { + ok(loop_ret == 0, "Trigger is registered: %s", test_tuple_string); + + /* + * Test that a trigger cannot be registered + * multiple time. + */ + loop_ret = lttng_register_trigger(trigger); + ok(loop_ret == -LTTNG_ERR_TRIGGER_EXISTS, "Re-register trigger fails as expected: %s", test_tuple_string); + + /* Test that a trigger can be unregistered */ + loop_ret = lttng_unregister_trigger(trigger); + ok(loop_ret == 0, "Unregister trigger: %s", test_tuple_string); + + /* + * Test that unregistration of a non-previously + * registered trigger fail. + */ + loop_ret = lttng_unregister_trigger(trigger); + ok(loop_ret == -LTTNG_ERR_TRIGGER_NOT_FOUND, "Unregister of a non-registered trigger fails as expected: %s", test_tuple_string); + } else { + ok(loop_ret == -LTTNG_ERR_INVALID_TRIGGER, "Trigger is invalid as expected and cannot be registered: %s", test_tuple_string); + } + +loop_cleanup: + free(test_tuple_string); + lttng_trigger_destroy(trigger); + lttng_condition_destroy(condition); + } + +end: + lttng_action_destroy(action); +} + +static +void wait_data_pending(const char *session_name) +{ + int ret; + + do { + ret = lttng_data_pending(session_name); + assert(ret >= 0); + } while (ret != 0); +} + +static +int setup_buffer_usage_condition(struct lttng_condition *condition, + const char *condition_name, + const char *session_name, + const char *channel_name, + const enum lttng_domain_type domain_type) +{ + enum lttng_condition_status condition_status; + int ret = 0; + + condition_status = lttng_condition_buffer_usage_set_session_name( + condition, session_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Error setting session name on %s creation", condition_name); + ret = -1; + goto end; + } + + condition_status = lttng_condition_buffer_usage_set_channel_name( + condition, channel_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Error setting channel name on %s creation", condition_name); + ret = -1; + goto end; + } + + condition_status = lttng_condition_buffer_usage_set_domain_type( + condition, domain_type); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Error setting domain type on %s creation", condition_name); + ret = -1; + goto end; + } + +end: + return ret; +} + +static +void test_invalid_channel_subscription( + const enum lttng_domain_type domain_type) +{ + enum lttng_condition_status condition_status; + enum lttng_notification_channel_status nc_status; + struct lttng_condition *dummy_condition = NULL; + struct lttng_condition *dummy_invalid_condition = NULL; + struct lttng_notification_channel *notification_channel = NULL; + int ret = 0; + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); + if (!notification_channel) { + goto end; + } + + /* + * Create a dummy, empty (thus invalid) condition to test error paths. + */ + dummy_invalid_condition = lttng_condition_buffer_usage_low_create(); + if (!dummy_invalid_condition) { + fail("Setup error on condition creation"); + goto end; + } + + /* + * Test subscription and unsubscription of an invalid condition to/from + * a channel. + */ + nc_status = lttng_notification_channel_subscribe( + notification_channel, dummy_invalid_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, + "Subscribing to an invalid condition"); + + nc_status = lttng_notification_channel_unsubscribe( + notification_channel, dummy_invalid_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, + "Unsubscribing from an invalid condition"); + + /* Create a valid dummy condition with a ratio of 0.5 */ + dummy_condition = lttng_condition_buffer_usage_low_create(); + if (!dummy_condition) { + fail("Setup error on dummy_condition creation"); + goto end; + } + + condition_status = lttng_condition_buffer_usage_set_threshold_ratio( + dummy_condition, 0.5); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on condition creation"); + goto end; + } + + ret = setup_buffer_usage_condition(dummy_condition, "dummy_condition", + "dummy_session", "dummy_channel", domain_type); + if (ret) { + fail("Setup error on dummy condition creation"); + goto end; + } + + /* + * Test subscription and unsubscription to/from a channel with invalid + * parameters. + */ + nc_status = lttng_notification_channel_subscribe(NULL, NULL); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, + "Notification channel subscription is invalid: NULL, NULL"); + + nc_status = lttng_notification_channel_subscribe( + notification_channel, NULL); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, + "Notification channel subscription is invalid: NON-NULL, NULL"); + + nc_status = lttng_notification_channel_subscribe(NULL, dummy_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, + "Notification channel subscription is invalid: NULL, NON-NULL"); + + nc_status = lttng_notification_channel_unsubscribe( + notification_channel, dummy_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_UNKNOWN_CONDITION, + "Unsubscribing from a valid unknown condition"); + +end: + lttng_notification_channel_destroy(notification_channel); + lttng_condition_destroy(dummy_invalid_condition); + lttng_condition_destroy(dummy_condition); + return; +} + +enum buffer_usage_type { + BUFFER_USAGE_TYPE_LOW, + BUFFER_USAGE_TYPE_HIGH, +}; + +static int register_buffer_usage_notify_trigger(const char *session_name, + const char *channel_name, + const enum lttng_domain_type domain_type, + enum buffer_usage_type buffer_usage_type, + double ratio, + struct lttng_condition **condition, + struct lttng_action **action, + struct lttng_trigger **trigger) +{ + enum lttng_condition_status condition_status; + struct lttng_action *tmp_action = NULL; + struct lttng_condition *tmp_condition = NULL; + struct lttng_trigger *tmp_trigger = NULL; + int ret = 0; + + /* Set-up */ + tmp_action = lttng_action_notify_create(); + if (!action) { + fail("Setup error on action creation"); + ret = -1; + goto error; + } + + if (buffer_usage_type == BUFFER_USAGE_TYPE_LOW) { + tmp_condition = lttng_condition_buffer_usage_low_create(); + } else { + tmp_condition = lttng_condition_buffer_usage_high_create(); + } + + if (!tmp_condition) { + fail("Setup error on condition creation"); + ret = -1; + goto error; + } + + /* Set the buffer usage threashold */ + condition_status = lttng_condition_buffer_usage_set_threshold_ratio( + tmp_condition, ratio); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on condition creation"); + ret = -1; + goto error; + } + + ret = setup_buffer_usage_condition(tmp_condition, "condition_name", + session_name, channel_name, domain_type); + if (ret) { + fail("Setup error on condition creation"); + ret = -1; + goto error; + } + + /* Register the triggers for condition */ + tmp_trigger = lttng_trigger_create(tmp_condition, tmp_action); + if (!tmp_trigger) { + fail("Setup error on trigger creation"); + ret = -1; + goto error; + } + + ret = lttng_register_trigger(tmp_trigger); + if (ret) { + fail("Setup error on trigger registration"); + ret = -1; + goto error; + } + + *condition = tmp_condition; + *trigger = tmp_trigger; + *action = tmp_action; + goto end; + +error: + lttng_action_destroy(tmp_action); + lttng_condition_destroy(tmp_condition); + lttng_trigger_destroy(tmp_trigger); + +end: + return ret; +} + +static void test_subscription_twice(const char *session_name, + const char *channel_name, + const enum lttng_domain_type domain_type) +{ + int ret = 0; + enum lttng_notification_channel_status nc_status; + + struct lttng_action *action = NULL; + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_trigger *trigger = NULL; + + struct lttng_condition *condition = NULL; + + ret = register_buffer_usage_notify_trigger(session_name, channel_name, + domain_type, BUFFER_USAGE_TYPE_LOW, 0.99, &condition, + &action, &trigger); + if (ret) { + fail("Setup error on trigger registration"); + goto end; + } + + /* Begin testing. */ + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); + if (!notification_channel) { + goto end; + } + + /* Subscribe a valid condition. */ + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to condition"); + + /* Subscribing again should fail. */ + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_ALREADY_SUBSCRIBED, + "Subscribe to a condition for which subscription was already done"); + +end: + lttng_unregister_trigger(trigger); + lttng_trigger_destroy(trigger); + lttng_notification_channel_destroy(notification_channel); + lttng_action_destroy(action); + lttng_condition_destroy(condition); +} + +static void test_buffer_usage_notification_channel(const char *session_name, + const char *channel_name, + const enum lttng_domain_type domain_type, + const char **argv) +{ + int ret = 0; + enum lttng_notification_channel_status nc_status; + + struct lttng_action *low_action = NULL; + struct lttng_action *high_action = NULL; + struct lttng_notification *notification = NULL; + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_trigger *low_trigger = NULL; + struct lttng_trigger *high_trigger = NULL; + + struct lttng_condition *low_condition = NULL; + struct lttng_condition *high_condition = NULL; + + double low_ratio = 0.0; + /* This is not 99 since we can end up in scenario where an event is + * bigger than 1% of the buffer and hence the buffer ratio will never + * trigger since the event will always be discarder by the tracer. + */ + + double high_ratio = 0.90; + + ret = register_buffer_usage_notify_trigger(session_name, channel_name, + domain_type, BUFFER_USAGE_TYPE_LOW, low_ratio, + &low_condition, &low_action, &low_trigger); + if (ret) { + fail("Setup error on low trigger registration"); + goto end; + } + + ret = register_buffer_usage_notify_trigger(session_name, channel_name, + domain_type, BUFFER_USAGE_TYPE_HIGH, high_ratio, + &high_condition, &high_action, &high_trigger); + if (ret) { + fail("Setup error on high trigger registration"); + goto end; + } + + /* Begin testing */ + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); + if (!notification_channel) { + goto end; + } + + /* Subscribe a valid low condition */ + nc_status = lttng_notification_channel_subscribe( + notification_channel, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to low condition"); + + /* Subscribe a valid high condition */ + nc_status = lttng_notification_channel_subscribe( + notification_channel, high_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to high condition"); + + resume_application(); + + /* Wait for notification to happen */ + stop_consumer(argv); + lttng_start_tracing(session_name); + + /* Wait for high notification */ + nc_status = lttng_notification_channel_get_next_notification( + notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && + lttng_condition_get_type(lttng_notification_get_condition( + notification)) == + LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH, + "High notification received after intermediary communication"); + lttng_notification_destroy(notification); + notification = NULL; + + suspend_application(); + lttng_stop_tracing_no_wait(session_name); + resume_consumer(argv); + wait_data_pending(session_name); + + /* + * Test that communication still work even if there is notification + * waiting for consumption. + */ + + nc_status = lttng_notification_channel_unsubscribe( + notification_channel, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Unsubscribe with pending notification"); + + nc_status = lttng_notification_channel_subscribe( + notification_channel, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe with pending notification"); + + nc_status = lttng_notification_channel_get_next_notification( + notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && + lttng_condition_get_type(lttng_notification_get_condition( + notification)) == + LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW, + "Low notification received after intermediary communication"); + lttng_notification_destroy(notification); + notification = NULL; + + /* Stop consumer to force a high notification */ + stop_consumer(argv); + resume_application(); + lttng_start_tracing(session_name); + + nc_status = lttng_notification_channel_get_next_notification( + notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && + lttng_condition_get_type(lttng_notification_get_condition( + notification)) == + LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH, + "High notification received after intermediary communication"); + lttng_notification_destroy(notification); + notification = NULL; + + suspend_application(); + lttng_stop_tracing_no_wait(session_name); + resume_consumer(argv); + wait_data_pending(session_name); + + nc_status = lttng_notification_channel_get_next_notification( + notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && + lttng_condition_get_type(lttng_notification_get_condition( + notification)) == + LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW, + "Low notification received after re-subscription"); + lttng_notification_destroy(notification); + notification = NULL; + + stop_consumer(argv); + resume_application(); + /* Stop consumer to force a high notification */ + lttng_start_tracing(session_name); + + nc_status = lttng_notification_channel_get_next_notification( + notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && + lttng_condition_get_type(lttng_notification_get_condition( + notification)) == + LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH, + "High notification"); + lttng_notification_destroy(notification); + notification = NULL; + + suspend_application(); + + /* Resume consumer to allow event consumption */ + lttng_stop_tracing_no_wait(session_name); + resume_consumer(argv); + wait_data_pending(session_name); + + nc_status = lttng_notification_channel_unsubscribe( + notification_channel, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Unsubscribe low condition with pending notification"); + + nc_status = lttng_notification_channel_unsubscribe( + notification_channel, high_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Unsubscribe high condition with pending notification"); + +end: + lttng_notification_channel_destroy(notification_channel); + lttng_trigger_destroy(low_trigger); + lttng_trigger_destroy(high_trigger); + lttng_action_destroy(low_action); + lttng_action_destroy(high_action); + lttng_condition_destroy(low_condition); + lttng_condition_destroy(high_condition); +} + +static void create_tracepoint_event_rule_trigger(const char *event_pattern, + const char *trigger_name, + const char *filter, + unsigned int exclusion_count, + const char **exclusions, + enum lttng_domain_type domain_type, + condition_capture_desc_cb capture_desc_cb, + struct lttng_condition **condition, + struct lttng_action **action, + struct lttng_trigger **trigger) +{ + enum lttng_event_rule_status event_rule_status; + enum lttng_trigger_status trigger_status; + + struct lttng_action *tmp_action = NULL; + struct lttng_event_rule *event_rule = NULL; + struct lttng_condition *tmp_condition = NULL; + struct lttng_trigger *tmp_trigger = NULL; + int ret; + + assert(event_pattern); + assert(trigger_name); + assert(condition); + assert(trigger); + + event_rule = lttng_event_rule_tracepoint_create(domain_type); + ok(event_rule, "Tracepoint event rule object creation"); + + event_rule_status = lttng_event_rule_tracepoint_set_pattern( + event_rule, event_pattern); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting tracepoint event rule pattern: %s", + event_pattern); + + if (filter) { + event_rule_status = lttng_event_rule_tracepoint_set_filter( + event_rule, filter); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting tracepoint event rule filter: %s", + filter); + } + + if (exclusions) { + assert(domain_type == LTTNG_DOMAIN_UST); + assert(exclusion_count > 0); + + event_rule_status = lttng_event_rule_tracepoint_set_exclusions( + event_rule, exclusion_count, exclusions); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting tracepoint event rule exclusions"); + } + + tmp_condition = lttng_condition_event_rule_create(event_rule); + ok(tmp_condition, "Condition event rule object creation"); + /* Ownership was passed to condition */ + event_rule = NULL; + + if (capture_desc_cb) { + ret = capture_desc_cb(tmp_condition); + if (ret) { + assert("Generating the condition capture descriptor"); } - mask_position++; + } + + tmp_action = lttng_action_notify_create(); + ok(tmp_action, "Action event rule object creation"); + + tmp_trigger = lttng_trigger_create(tmp_condition, tmp_action); + ok(tmp_trigger, "Trigger object creation %s", trigger_name); + + trigger_status = lttng_trigger_set_name(tmp_trigger, trigger_name); + ok(trigger_status == LTTNG_TRIGGER_STATUS_OK, + "Setting name to trigger %s", trigger_name); + + ret = lttng_register_trigger(tmp_trigger); + ok(ret == 0, "Trigger registration %s", trigger_name); + + *condition = tmp_condition; + *action = tmp_action; + *trigger = tmp_trigger; + + return; +} + +static struct lttng_notification *get_next_notification( + struct lttng_notification_channel *notification_channel) +{ + struct lttng_notification *local_notification = NULL; + enum lttng_notification_channel_status status; + + /* Receive the next notification. */ + status = lttng_notification_channel_get_next_notification( + notification_channel, &local_notification); + + switch (status) { + case LTTNG_NOTIFICATION_CHANNEL_STATUS_OK: + break; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_NOTIFICATIONS_DROPPED: + fail("Notifications have been dropped"); + local_notification = NULL; + break; + default: + /* Unhandled conditions / errors. */ + fail("error: Unknown notification channel status\n"); + local_notification = NULL; + break; + } + + return local_notification; +} + +static void test_tracepoint_event_rule_notification( + enum lttng_domain_type domain_type) +{ + int i; + int ret; + enum lttng_notification_channel_status nc_status; + + struct lttng_action *action = NULL; + struct lttng_condition *condition = NULL; + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_trigger *trigger = NULL; + const char *trigger_name = "my_precious"; + const char *pattern; + + if (domain_type == LTTNG_DOMAIN_UST) { + pattern = "tp:tptest"; + } else { + pattern = "lttng_test_filter_event"; + } + + create_tracepoint_event_rule_trigger(pattern, trigger_name, NULL, 0, + NULL, domain_type, NULL, &condition, &action, &trigger); + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); + + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); + + resume_application(); - /* Set threshold ratio */ - if ((1 << mask_position) & i) { - condition_status = lttng_condition_buffer_usage_set_threshold_ratio( - condition, 0.0); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - loop_ret = 1; - goto loop_end; - } - threshold_ratio_set = true; - } - mask_position++; + /* Get 3 notifications */ + for (i = 0; i < 3; i++) { + struct lttng_notification *notification = get_next_notification( + notification_channel); + ok(notification, "Received notification"); - /* Set threshold byte */ - if ((1 << mask_position) & i) { - condition_status = lttng_condition_buffer_usage_set_threshold( - condition, 0); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - loop_ret = 1; - goto loop_end; - } - threshold_byte_set = true; + /* Error */ + if (notification == NULL) { + goto end; } - mask_position++; - /* Set domain type */ - if ((1 << mask_position) & i) { - condition_status = lttng_condition_buffer_usage_set_domain_type( - condition, LTTNG_DOMAIN_UST); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - loop_ret = 1; - goto loop_end; - } - domain_type_set = true; + ret = validator_notification_trigger_name(notification, trigger_name); + lttng_notification_destroy(notification); + if (ret) { + goto end; } + } - /* Safety check */ - if (mask_position != test_vector_size -1) { - assert("Logic error for test vector generation"); +end: + suspend_application(); + lttng_notification_channel_destroy(notification_channel); + lttng_unregister_trigger(trigger); + lttng_trigger_destroy(trigger); + lttng_action_destroy(action); + lttng_condition_destroy(condition); + return; +} + +static void test_tracepoint_event_rule_notification_filter( + enum lttng_domain_type domain_type) +{ + int i; + enum lttng_notification_channel_status nc_status; + + struct lttng_condition *ctrl_condition = NULL, *condition = NULL; + struct lttng_action *ctrl_action = NULL, *action = NULL; + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_trigger *ctrl_trigger = NULL, *trigger = NULL; + const char *ctrl_trigger_name = "control_trigger"; + const char *trigger_name = "trigger"; + const char *pattern; + int ctrl_count = 0, count = 0; + + if (domain_type == LTTNG_DOMAIN_UST) { + pattern = "tp:tptest"; + } else { + pattern = "lttng_test_filter_event"; + } + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); + + create_tracepoint_event_rule_trigger(pattern, ctrl_trigger_name, NULL, + 0, NULL, domain_type, NULL, &ctrl_condition, + &ctrl_action, &ctrl_trigger); + + nc_status = lttng_notification_channel_subscribe( + notification_channel, ctrl_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); + + /* + * Attach a filter expression to get notification only if the + * `intfield` is even. + */ + create_tracepoint_event_rule_trigger(pattern, trigger_name, + "(intfield & 1) == 0", 0, NULL, domain_type, NULL, + &condition, &action, &trigger); + + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); + + /* + * We registered 2 notifications triggers, one with a filter and one + * without (control). The one with a filter will only fired when the + * `intfield` is a multiple of 2. We should get two times as many + * control notifications as filter notifications. + */ + resume_application(); + + /* + * Get 3 notifications. We should get 1 for the regular trigger (with + * the filter) and 2 from the control trigger. This works whatever + * the order we receive the notifications. + */ + for (i = 0; i < 3; i++) { + const char *name; + struct lttng_notification *notification = get_next_notification( + notification_channel); + ok(notification, "Received notification"); + + /* Error */ + if (notification == NULL) { + goto end; } - loop_ret = asprintf(&test_tuple_string, "session name %s, channel name %s, threshold ratio %s, threshold byte %s, domain type %s", - session_name_set ? "set" : "unset", - channel_name_set ? "set" : "unset", - threshold_ratio_set ? "set" : "unset", - threshold_byte_set ? "set" : "unset", - domain_type_set? "set" : "unset"); - if (!test_tuple_string || loop_ret < 0) { - loop_ret = 1; - goto loop_end; + name = get_notification_trigger_name(notification); + if (name == NULL) { + lttng_notification_destroy(notification); + goto end; } - /* Create trigger */ - trigger = lttng_trigger_create(condition, action); - if (!trigger) { - loop_ret = 1; - goto loop_end; + if (strcmp(ctrl_trigger_name, name) == 0) { + ctrl_count++; + } else if (strcmp(trigger_name, name) == 0) { + count++; } + lttng_notification_destroy(notification); + } + ok(ctrl_count / 2 == count, + "Get twice as many control notif as of regular notif"); - loop_ret = lttng_register_trigger(trigger); +end: + suspend_application(); + lttng_unregister_trigger(trigger); + lttng_unregister_trigger(ctrl_trigger); + lttng_notification_channel_destroy(notification_channel); + lttng_trigger_destroy(trigger); + lttng_trigger_destroy(ctrl_trigger); + lttng_condition_destroy(condition); + lttng_condition_destroy(ctrl_condition); + lttng_action_destroy(action); + lttng_action_destroy(ctrl_action); + return; +} -loop_end: - if (loop_ret == 1) { - fail("Setup error occurred for tuple: %s", test_tuple_string); - goto loop_cleanup; - } +static void test_tracepoint_event_rule_notification_exclusion( + enum lttng_domain_type domain_type) +{ + enum lttng_notification_channel_status nc_status; + struct lttng_condition *ctrl_condition = NULL, *condition = NULL; + struct lttng_action *ctrl_action = NULL, *action = NULL; + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_trigger *ctrl_trigger = NULL, *trigger = NULL; + const char *ctrl_trigger_name = "control_exclusion_trigger"; + const char *trigger_name = "exclusion_trigger"; + const char *pattern = "tp:tptest*"; + const char *exclusions[4] = { + "tp:tptest2", "tp:tptest3", "tp:tptest4", "tp:tptest5"}; + int ctrl_count = 0, count = 0; + int i; + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); - /* This combination happens three times */ - if (session_name_set && channel_name_set - && (threshold_ratio_set || threshold_byte_set) - && domain_type_set) { - ok(loop_ret == 0, "Trigger is registered: %s", test_tuple_string); + create_tracepoint_event_rule_trigger(pattern, ctrl_trigger_name, NULL, + 0, NULL, domain_type, NULL, &ctrl_condition, + &ctrl_action, &ctrl_trigger); - /* - * Test that a trigger cannot be registered - * multiple time. - */ - loop_ret = lttng_register_trigger(trigger); - ok(loop_ret == -LTTNG_ERR_TRIGGER_EXISTS, "Re-register trigger fails as expected: %s", test_tuple_string); + nc_status = lttng_notification_channel_subscribe( + notification_channel, ctrl_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); - /* Test that a trigger can be unregistered */ - loop_ret = lttng_unregister_trigger(trigger); - ok(loop_ret == 0, "Unregister trigger: %s", test_tuple_string); + create_tracepoint_event_rule_trigger(pattern, trigger_name, NULL, 4, + exclusions, domain_type, NULL, &condition, &action, + &trigger); - /* - * Test that unregistration of a non-previously - * registered trigger fail. - */ - loop_ret = lttng_unregister_trigger(trigger); - ok(loop_ret == -LTTNG_ERR_TRIGGER_NOT_FOUND, "Unregister of a non-registered trigger fails as expected: %s", test_tuple_string); - } else { - ok(loop_ret == -LTTNG_ERR_INVALID_TRIGGER, "Trigger is invalid as expected and cannot be registered: %s", test_tuple_string); + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); + + /* + * We registered 2 notifications triggers, one with an exclusion and + * one without (control). + * - The trigger with an exclusion will fire once every iteration. + * - The trigger without an exclusion will fire 5 times every + * iteration. + * + * We should get 5 times as many notifications from the control + * trigger. + */ + resume_application(); + + /* + * Get 6 notifications. We should get 1 for the regular trigger (with + * the exclusion) and 5 from the control trigger. This works whatever + * the order we receive the notifications. + */ + for (i = 0; i < 6; i++) { + const char *name; + struct lttng_notification *notification = get_next_notification( + notification_channel); + ok(notification, "Received notification"); + + /* Error */ + if (notification == NULL) { + goto end; } -loop_cleanup: - free(test_tuple_string); - lttng_trigger_destroy(trigger); - lttng_condition_destroy(condition); + name = get_notification_trigger_name(notification); + if (name == NULL) { + lttng_notification_destroy(notification); + goto end; + } + + if (strcmp(ctrl_trigger_name, name) == 0) { + ctrl_count++; + } else if (strcmp(trigger_name, name) == 0) { + count++; + } + lttng_notification_destroy(notification); } + ok(ctrl_count / 5 == count, + "Got 5 times as many control notif as of regular notif"); end: + suspend_application(); + lttng_unregister_trigger(trigger); + lttng_unregister_trigger(ctrl_trigger); + lttng_notification_channel_destroy(notification_channel); + lttng_trigger_destroy(trigger); + lttng_trigger_destroy(ctrl_trigger); + lttng_condition_destroy(condition); + lttng_condition_destroy(ctrl_condition); lttng_action_destroy(action); + lttng_action_destroy(ctrl_action); + return; } -static -void wait_data_pending(const char *session_name) +static void test_kprobe_event_rule_notification( + enum lttng_domain_type domain_type) { - int ret; + enum lttng_notification_channel_status nc_status; + enum lttng_event_rule_status event_rule_status; + enum lttng_trigger_status trigger_status; - do { - ret = lttng_data_pending(session_name); - assert(ret >= 0); - } while (ret != 0); + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_condition *condition = NULL; + struct lttng_event_rule *event_rule = NULL; + struct lttng_action *action = NULL; + struct lttng_trigger *trigger = NULL; + const char *trigger_name = "kprobe_trigger"; + const char *symbol_name = "_do_fork"; + int i, ret; + + action = lttng_action_notify_create(); + if (!action) { + fail("Setup error on action creation"); + goto end; + } + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); + + event_rule = lttng_event_rule_kprobe_create(); + ok(event_rule, "kprobe event rule object creation"); + + event_rule_status = lttng_event_rule_kprobe_set_source( + event_rule, symbol_name); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting kprobe event rule source: %s", symbol_name); + + event_rule_status = lttng_event_rule_kprobe_set_name( + event_rule, trigger_name); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting kprobe event rule name: %s", trigger_name); + + condition = lttng_condition_event_rule_create(event_rule); + ok(condition, "Condition event rule object creation"); + + /* Register the triggers for condition */ + trigger = lttng_trigger_create(condition, action); + if (!trigger) { + fail("Setup error on trigger creation"); + goto end; + } + + trigger_status = lttng_trigger_set_name(trigger, trigger_name); + ok(trigger_status == LTTNG_TRIGGER_STATUS_OK, + "Setting name to trigger %s", trigger_name); + + ret = lttng_register_trigger(trigger); + if (ret) { + fail("Setup error on trigger registration"); + goto end; + } + + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); + + resume_application(); + + for (i = 0; i < 3; i++) { + struct lttng_notification *notification = get_next_notification( + notification_channel); + ok(notification, "Received notification"); + + /* Error */ + if (notification == NULL) { + goto end; + } + + ret = validator_notification_trigger_name(notification, trigger_name); + lttng_notification_destroy(notification); + if (ret) { + goto end; + } + } + +end: + suspend_application(); + lttng_notification_channel_destroy(notification_channel); + lttng_unregister_trigger(trigger); + lttng_trigger_destroy(trigger); + lttng_action_destroy(action); + lttng_condition_destroy(condition); + return; } -static -void test_notification_channel(const char *session_name, const char *channel_name, const enum lttng_domain_type domain_type, const char **argv) +static void test_uprobe_event_rule_notification( + enum lttng_domain_type domain_type, + const char *testapp_path, + const char *test_symbol_name) { - int ret = 0; - enum lttng_condition_status condition_status; enum lttng_notification_channel_status nc_status; + enum lttng_event_rule_status event_rule_status; + enum lttng_trigger_status trigger_status; - struct lttng_action *action = NULL; - struct lttng_notification *notification = NULL; struct lttng_notification_channel *notification_channel = NULL; + struct lttng_userspace_probe_location *probe_location = NULL; + struct lttng_userspace_probe_location_lookup_method *lookup_method = + NULL; + struct lttng_condition *condition = NULL; + struct lttng_event_rule *event_rule = NULL; + struct lttng_action *action = NULL; struct lttng_trigger *trigger = NULL; + const char *trigger_name = "uprobe_trigger"; + int i, ret; - struct lttng_condition *low_condition = NULL; - struct lttng_condition *high_condition = NULL; - struct lttng_condition *dummy_invalid_condition = NULL; - struct lttng_condition *dummy_condition = NULL; - - double low_ratio = 0.0; - double high_ratio = 0.99; - - /* Set-up */ action = lttng_action_notify_create(); if (!action) { fail("Setup error on action creation"); goto end; } - /* Create a dummy, empty condition for later test */ - dummy_invalid_condition = lttng_condition_buffer_usage_low_create(); - if (!dummy_invalid_condition) { - fail("Setup error on condition creation"); + lookup_method = lttng_userspace_probe_location_lookup_method_function_elf_create(); + if (!lookup_method) { + fail("Setup error on userspace probe lookup method creation"); goto end; } - /* Create a valid dummy condition with a ratio of 0.5 */ - dummy_condition = lttng_condition_buffer_usage_low_create(); - if (!dummy_condition) { - fail("Setup error on dummy_condition creation"); + probe_location = lttng_userspace_probe_location_function_create( + testapp_path, test_symbol_name, lookup_method); + if (!probe_location) { + fail("Setup error on userspace probe location creation"); goto end; - } - condition_status = lttng_condition_buffer_usage_set_threshold_ratio( - dummy_condition, 0.5); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on condition creation"); + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); + + event_rule = lttng_event_rule_uprobe_create(); + ok(event_rule, "kprobe event rule object creation"); + + event_rule_status = lttng_event_rule_uprobe_set_location( + event_rule, probe_location); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting uprobe event rule location"); + + event_rule_status = lttng_event_rule_uprobe_set_name( + event_rule, trigger_name); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting uprobe event rule name: %s", trigger_name); + + condition = lttng_condition_event_rule_create(event_rule); + ok(condition, "Condition event rule object creation"); + + /* Register the triggers for condition */ + trigger = lttng_trigger_create(condition, action); + if (!trigger) { + fail("Setup error on trigger creation"); goto end; } - condition_status = lttng_condition_buffer_usage_set_session_name( - dummy_condition, session_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on dummy_condition creation"); + trigger_status = lttng_trigger_set_name(trigger, trigger_name); + ok(trigger_status == LTTNG_TRIGGER_STATUS_OK, + "Setting name to trigger %s", trigger_name); + + ret = lttng_register_trigger(trigger); + if (ret) { + fail("Setup error on trigger registration"); goto end; } - condition_status = lttng_condition_buffer_usage_set_channel_name( - dummy_condition, channel_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on dummy_condition creation"); - goto end; + + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); + + resume_application(); + + for (i = 0; i < 3; i++) { + struct lttng_notification *notification = get_next_notification( + notification_channel); + ok(notification, "Received notification"); + + /* Error */ + if (notification == NULL) { + goto end; + } + + ret = validator_notification_trigger_name(notification, trigger_name); + lttng_notification_destroy(notification); + if (ret) { + goto end; + } } - condition_status = lttng_condition_buffer_usage_set_domain_type( - dummy_condition, domain_type); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on dummy_condition creation"); +end: + suspend_application(); + lttng_notification_channel_destroy(notification_channel); + lttng_unregister_trigger(trigger); + lttng_trigger_destroy(trigger); + lttng_action_destroy(action); + lttng_condition_destroy(condition); + return; +} + +static void test_syscall_event_rule_notification( + enum lttng_domain_type domain_type) +{ + enum lttng_notification_channel_status nc_status; + enum lttng_event_rule_status event_rule_status; + enum lttng_trigger_status trigger_status; + + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_condition *condition = NULL; + struct lttng_event_rule *event_rule = NULL; + struct lttng_action *action = NULL; + struct lttng_trigger *trigger = NULL; + const char *trigger_name = "syscall_trigger"; + const char *syscall_name = "openat"; + int i, ret; + + action = lttng_action_notify_create(); + if (!action) { + fail("Setup error on action creation"); goto end; } - /* Register a low condition with a ratio */ - low_condition = lttng_condition_buffer_usage_low_create(); - if (!low_condition) { - fail("Setup error on low_condition creation"); - goto end; - } - condition_status = lttng_condition_buffer_usage_set_threshold_ratio( - low_condition, low_ratio); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on low_condition creation"); - goto end; - } + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); - condition_status = lttng_condition_buffer_usage_set_session_name( - low_condition, session_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on low_condition creation"); - goto end; - } - condition_status = lttng_condition_buffer_usage_set_channel_name( - low_condition, channel_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on low_condition creation"); - goto end; - } - condition_status = lttng_condition_buffer_usage_set_domain_type( - low_condition, domain_type); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on low_condition creation"); - goto end; + event_rule = lttng_event_rule_syscall_create(); + ok(event_rule, "syscall event rule object creation"); - } + event_rule_status = lttng_event_rule_syscall_set_pattern( + event_rule, syscall_name); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting syscall event rule pattern: %s", syscall_name); - /* Register a high condition with a ratio */ - high_condition = lttng_condition_buffer_usage_high_create(); - if (!high_condition) { - fail("Setup error on high_condition creation"); - goto end; - } + condition = lttng_condition_event_rule_create(event_rule); + ok(condition, "Condition event rule object creation"); - condition_status = lttng_condition_buffer_usage_set_threshold_ratio( - high_condition, high_ratio); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on high_condition creation"); + /* Register the triggers for condition */ + trigger = lttng_trigger_create(condition, action); + if (!trigger) { + fail("Setup error on trigger creation"); goto end; } - condition_status = lttng_condition_buffer_usage_set_session_name( - high_condition, session_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on high_condition creation"); + trigger_status = lttng_trigger_set_name(trigger, trigger_name); + ok(trigger_status == LTTNG_TRIGGER_STATUS_OK, + "Setting name to trigger %s", trigger_name); + + ret = lttng_register_trigger(trigger); + if (ret) { + fail("Setup error on trigger registration"); goto end; } - condition_status = lttng_condition_buffer_usage_set_channel_name( - high_condition, channel_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on high_condition creation"); - goto end; + + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); + + resume_application(); + + for (i = 0; i < 3; i++) { + struct lttng_notification *notification = get_next_notification( + notification_channel); + ok(notification, "Received notification"); + + /* Error */ + if (notification == NULL) { + goto end; + } + + ret = validator_notification_trigger_name(notification, trigger_name); + lttng_notification_destroy(notification); + if (ret) { + goto end; + } } - condition_status = lttng_condition_buffer_usage_set_domain_type( - high_condition, domain_type); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on high_condition creation"); +end: + suspend_application(); + lttng_notification_channel_destroy(notification_channel); + lttng_unregister_trigger(trigger); + lttng_trigger_destroy(trigger); + lttng_action_destroy(action); + lttng_condition_destroy(condition); + return; +} + +static void test_syscall_event_rule_notification_filter( + enum lttng_domain_type domain_type) +{ + enum lttng_notification_channel_status nc_status; + enum lttng_event_rule_status event_rule_status; + enum lttng_trigger_status trigger_status; + + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_condition *condition = NULL; + struct lttng_event_rule *event_rule = NULL; + struct lttng_action *action = NULL; + struct lttng_trigger *trigger = NULL; + const char *trigger_name = "syscall_trigger"; + const char *syscall_name = "openat"; + int i, ret; + + action = lttng_action_notify_create(); + if (!action) { + fail("Setup error on action creation"); goto end; } - /* Register the triggers for low and high condition */ - trigger = lttng_trigger_create(low_condition, action); + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); + + event_rule = lttng_event_rule_syscall_create(); + ok(event_rule, "syscall event rule object creation"); + + event_rule_status = lttng_event_rule_syscall_set_pattern( + event_rule, syscall_name); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting syscall event rule pattern: %s", syscall_name); + + event_rule_status = lttng_event_rule_syscall_set_filter( + event_rule, "filename==\"/proc/cpuinfo\""); + ok(event_rule_status == LTTNG_EVENT_RULE_STATUS_OK, + "Setting syscall event rule pattern: %s", syscall_name); + + condition = lttng_condition_event_rule_create(event_rule); + ok(condition, "Condition event rule object creation"); + + /* Register the triggers for condition */ + trigger = lttng_trigger_create(condition, action); if (!trigger) { - fail("Setup error on low trigger creation"); + fail("Setup error on trigger creation"); goto end; } + trigger_status = lttng_trigger_set_name(trigger, trigger_name); + ok(trigger_status == LTTNG_TRIGGER_STATUS_OK, + "Setting name to trigger %s", trigger_name); + ret = lttng_register_trigger(trigger); if (ret) { - fail("Setup error on low trigger registration"); + fail("Setup error on trigger registration"); goto end; } + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); + + resume_application(); + + for (i = 0; i < 3; i++) { + struct lttng_notification *notification = get_next_notification( + notification_channel); + ok(notification, "Received notification"); + + /* Error */ + if (notification == NULL) { + goto end; + } + + ret = validator_notification_trigger_name(notification, trigger_name); + lttng_notification_destroy(notification); + if (ret) { + goto end; + } + } + +end: + suspend_application(); + lttng_unregister_trigger(trigger); + lttng_notification_channel_destroy(notification_channel); lttng_trigger_destroy(trigger); - trigger = NULL; + lttng_condition_destroy(condition); + return; +} - trigger = lttng_trigger_create(high_condition, action); - if (!trigger) { - fail("Setup error on high trigger creation"); - goto end; +static int generate_capture_descr(struct lttng_condition *condition) +{ + int ret; + struct lttng_event_expr *expr = NULL; + unsigned int basic_field_size; + enum lttng_condition_status cond_status; + + basic_field_size = sizeof(test_capture_base_fields) / sizeof(*test_capture_base_fields); + for (int i = 0; i < basic_field_size; i++) { + + diag("Adding capture descriptor \"%s\"", test_capture_base_fields[i].field_name); + + switch (test_capture_base_fields[i].field_type) { + case FIELD_TYPE_PAYLOAD: + expr = lttng_event_expr_event_payload_field_create( + test_capture_base_fields[i].field_name); + break; + case FIELD_TYPE_CONTEXT: + expr = lttng_event_expr_channel_context_field_create( + test_capture_base_fields[i].field_name); + break; + case FIELD_TYPE_ARRAY_FIELD: + { + int nb_matches; + unsigned int index; + char field_name[256]; + struct lttng_event_expr *array_expr = NULL; + nb_matches = sscanf(test_capture_base_fields[i].field_name, + "%[^[][%u]", field_name, &index); + if (nb_matches != 2) { + ret = 1; + goto end; + } + array_expr = lttng_event_expr_event_payload_field_create( + field_name); + + expr = lttng_event_expr_array_field_element_create( + array_expr, index); + break; + } + case FIELD_TYPE_APP_CONTEXT: + fail("Application context not tested yet."); + default: + ret = 1; + goto end; + } + if (expr == NULL) { + fail("Creating capture expression"); + ret = -1; + goto end; + } + cond_status = lttng_condition_event_rule_append_capture_descriptor( + condition, expr); + if (cond_status != LTTNG_CONDITION_STATUS_OK) { + fail("Appending capture_descriptor"); + ret = -1; + lttng_event_expr_destroy(expr); + goto end; + } } - ret = lttng_register_trigger(trigger); - if (ret) { - fail("Setup error on high trigger registration"); + ret = 0; + +end: + return ret; +} + +static int validator_notification_trigger_capture( + enum lttng_domain_type domain, + struct lttng_notification *notification, + const int iteration) +{ + int ret; + unsigned int capture_count, i; + enum lttng_evaluation_status evaluation_status; + enum lttng_event_field_value_status event_field_value_status; + const struct lttng_evaluation *evaluation; + const struct lttng_event_field_value *captured_fields; + bool at_least_one_error = false; + + evaluation = lttng_notification_get_evaluation(notification); + if (evaluation == NULL) { + fail("lttng_notification_get_evaluation"); + ret = 1; goto end; } - /* Begin testing */ - notification_channel = lttng_notification_channel_create(lttng_session_daemon_notification_endpoint); - ok(notification_channel, "Notification channel object creation"); - if (!notification_channel) { + /* TODO: it seems weird that lttng_evaluation_get_captured_values return + * INVALID if no capture were present. might be better to return + * something with more meaning. Another question is how we link the + * notion of capture and the descriptor from the perspective of a + * client. Is it really invalid to ask for captured value when there might + * not be any? + */ + evaluation_status = lttng_evaluation_get_captured_values(evaluation, &captured_fields); + if (evaluation_status != LTTNG_EVALUATION_STATUS_OK) { + diag("lttng_evaluation_get_captured_values"); + ret = 1; goto end; } - /* Basic error path check */ - nc_status = lttng_notification_channel_subscribe(NULL, NULL); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Notification channel subscription is invalid: NULL, NULL"); - - nc_status = lttng_notification_channel_subscribe(notification_channel, NULL); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Notification channel subscription is invalid: NON-NULL, NULL"); + event_field_value_status = + lttng_event_field_value_array_get_length(captured_fields, + &capture_count); + if (event_field_value_status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + assert(0 && "get count of captured field"); + } - nc_status = lttng_notification_channel_subscribe(NULL, low_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Notification channel subscription is invalid: NULL, NON-NULL"); + for (i = 0; i < capture_count; i++) { + const struct lttng_event_field_value *captured_field = NULL; + validate_cb validate; + bool expected; - nc_status = lttng_notification_channel_subscribe(notification_channel, dummy_invalid_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Subscribing to an invalid condition"); + diag("Validating capture \"%s\"", test_capture_base_fields[i].field_name); + event_field_value_status = lttng_event_field_value_array_get_element_at_index(captured_fields, i, &captured_field); - nc_status = lttng_notification_channel_unsubscribe(notification_channel, dummy_invalid_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Unsubscribing from an invalid condition"); + switch(domain) { + case LTTNG_DOMAIN_UST: + expected = test_capture_base_fields[i].expected_ust; + break; + case LTTNG_DOMAIN_KERNEL: + expected = test_capture_base_fields[i].expected_kernel; + break; + default: + assert(0 && "Domain invalid for this test"); + } - nc_status = lttng_notification_channel_unsubscribe(notification_channel, dummy_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_UNKNOWN_CONDITION, "Unsubscribing from a valid unknown condition"); + if (!expected) { + ok(event_field_value_status == LTTNG_EVENT_FIELD_VALUE_STATUS_UNAVAILABLE, "No payload captured"); + continue; + } - /* Subscribe a valid low condition */ - nc_status = lttng_notification_channel_subscribe(notification_channel, low_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Subscribe to condition"); + if (domain == LTTNG_DOMAIN_UST) { + validate = test_capture_base_fields[i].validate_ust; + } else { + validate = test_capture_base_fields[i].validate_kernel; + } - /* Subscribe a valid high condition */ - nc_status = lttng_notification_channel_subscribe(notification_channel, high_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Subscribe to condition"); + if (event_field_value_status != LTTNG_EVENT_FIELD_VALUE_STATUS_OK) { + const char *reason; + if (event_field_value_status == + LTTNG_EVENT_FIELD_VALUE_STATUS_UNAVAILABLE) { + reason = "Expected a capture but it is unavailable"; + } else { + reason = "lttng_event_field_value_array_get_element_at_index"; + } + fail(reason); + ret = 1; + goto end; + } + diag("Captured field of type %s", + field_value_type_to_str( + lttng_event_field_value_get_type(captured_field))); - nc_status = lttng_notification_channel_subscribe(notification_channel, low_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_ALREADY_SUBSCRIBED, "Subscribe to a condition for which subscription was already done"); + assert(validate); + ret = validate(captured_field, iteration); + if (ret) { + at_least_one_error = true; + } + } - nc_status = lttng_notification_channel_subscribe(notification_channel, high_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_ALREADY_SUBSCRIBED, "Subscribe to a condition for which subscription was already done"); + ret = at_least_one_error; - /* Wait for notification to happen */ - stop_consumer(argv); - lttng_start_tracing(session_name); +end: + return ret; +} - /* Wait for high notification */ - nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK - && notification - && lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH, - "High notification received after intermediary communication"); - lttng_notification_destroy(notification); - notification = NULL; +static void test_tracepoint_event_rule_notification_capture( + enum lttng_domain_type domain_type) +{ + enum lttng_notification_channel_status nc_status; - suspend_application(); - lttng_stop_tracing_no_wait(session_name); - resume_consumer(argv); - wait_data_pending(session_name); + int i, ret; + struct lttng_action *action = NULL; + struct lttng_condition *condition = NULL; + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_trigger *trigger = NULL; + const char *trigger_name = "my_precious"; + const char *pattern; - /* - * Test that communication still work even if there is notification - * waiting for consumption. - */ + if (domain_type == LTTNG_DOMAIN_UST) { + pattern = "tp:tptest"; + } else { + pattern = "lttng_test_filter_event"; + } - nc_status = lttng_notification_channel_unsubscribe(notification_channel, low_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Unsubscribe with pending notification"); + create_tracepoint_event_rule_trigger(pattern, trigger_name, NULL, 0, + NULL, domain_type, generate_capture_descr, &condition, + &action, &trigger); - nc_status = lttng_notification_channel_subscribe(notification_channel, low_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "subscribe with pending notification"); + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); - nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK - && notification - && lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW, - "Low notification received after intermediary communication"); - lttng_notification_destroy(notification); - notification = NULL; + nc_status = lttng_notification_channel_subscribe( + notification_channel, condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to tracepoint event rule condition"); - /* Stop consumer to force a high notification */ - stop_consumer(argv); resume_application(); - lttng_start_tracing(session_name); - - nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && - lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH, - "High notification received after intermediary communication"); - lttng_notification_destroy(notification); - notification = NULL; - - suspend_application(); - lttng_stop_tracing_no_wait(session_name); - resume_consumer(argv); - wait_data_pending(session_name); - nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && - lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW, - "Low notification received after re-subscription"); - lttng_notification_destroy(notification); - notification = NULL; + /* Get 3 notifications */ + for (i = 0; i < 3; i++) { + struct lttng_notification *notification = get_next_notification( + notification_channel); + ok(notification, "Received notification"); - stop_consumer(argv); - resume_application(); - /* Stop consumer to force a high notification */ - lttng_start_tracing(session_name); + /* Error */ + if (notification == NULL) { + goto end; + } - nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && - lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH, - "High notification"); - lttng_notification_destroy(notification); - notification = NULL; + ret = validator_notification_trigger_name(notification, trigger_name); + if (ret) { + lttng_notification_destroy(notification); + goto end; + } - /* Resume consumer to allow event consumption */ - suspend_application(); - lttng_stop_tracing_no_wait(session_name); - resume_consumer(argv); - wait_data_pending(session_name); + ret = validator_notification_trigger_capture(domain_type, notification, i); + if (ret) { + lttng_notification_destroy(notification); + goto end; + } - nc_status = lttng_notification_channel_unsubscribe(notification_channel, low_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Unsubscribe low condition with pending notification"); - nc_status = lttng_notification_channel_unsubscribe(notification_channel, high_condition); - ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Unsubscribe high condition with pending notification"); + lttng_notification_destroy(notification); + } end: + suspend_application(); lttng_notification_channel_destroy(notification_channel); + lttng_unregister_trigger(trigger); lttng_trigger_destroy(trigger); lttng_action_destroy(action); - lttng_condition_destroy(low_condition); - lttng_condition_destroy(high_condition); - lttng_condition_destroy(dummy_invalid_condition); - lttng_condition_destroy(dummy_condition); + lttng_condition_destroy(condition); + return; } int main(int argc, const char *argv[]) { - const char *session_name = NULL; - const char *channel_name = NULL; + int test_scenario; const char *domain_type_string = NULL; enum lttng_domain_type domain_type = LTTNG_DOMAIN_NONE; - plan_tests(NUM_TESTS); - - /* Argument 6 and upward are named pipe location for consumerd control */ - named_pipe_args_start = 6; - - if (argc < 7) { - fail("Missing parameter for tests to run %d", argc); + if (argc < 5) { + fail("Missing test scenario, domain type, pid, or application state file argument(s)"); goto error; } - nb_args = argc; - - domain_type_string = argv[1]; - session_name = argv[2]; - channel_name = argv[3]; - app_pid = (pid_t) atoi(argv[4]); - app_state_file = argv[5]; + test_scenario = atoi(argv[1]); + domain_type_string = argv[2]; + app_pid = (pid_t) atoi(argv[3]); + app_state_file = argv[4]; if (!strcmp("LTTNG_DOMAIN_UST", domain_type_string)) { domain_type = LTTNG_DOMAIN_UST; @@ -726,13 +2466,172 @@ int main(int argc, const char *argv[]) goto error; } - diag("Test trigger for domain %s with buffer_usage_low condition", domain_type_string); - test_triggers_buffer_usage_condition(session_name, channel_name, domain_type, LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW); - diag("Test trigger for domain %s with buffer_usage_high condition", domain_type_string); - test_triggers_buffer_usage_condition(session_name, channel_name, domain_type, LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH); + /* + * Test cases are responsible for resuming the app when needed + * and making sure it's suspended when returning. + */ + suspend_application(); + + switch (test_scenario) { + case 1: + { + plan_tests(44); + + /* Test cases that need gen-ust-event testapp. */ + diag("Test basic notification error paths for domain %s", + domain_type_string); + test_invalid_channel_subscription(domain_type); + + diag("Test tracepoint event rule notifications for domain %s", + domain_type_string); + test_tracepoint_event_rule_notification(domain_type); + + diag("Test tracepoint event rule notifications with filter for domain %s", + domain_type_string); + test_tracepoint_event_rule_notification_filter(domain_type); + break; + } + case 2: + { + const char *session_name, *channel_name; + /* Test cases that need a tracing session enabled. */ + plan_tests(99); + + /* + * Argument 7 and upward are named pipe location for consumerd + * control. + */ + named_pipe_args_start = 7; + + if (argc < 8) { + fail("Missing parameter for tests to run %d", argc); + goto error; + } + + nb_args = argc; + + session_name = argv[5]; + channel_name = argv[6]; + + test_subscription_twice(session_name, channel_name, + domain_type); + + diag("Test trigger for domain %s with buffer_usage_low condition", + domain_type_string); + test_triggers_buffer_usage_condition(session_name, channel_name, + domain_type, + LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW); + + diag("Test trigger for domain %s with buffer_usage_high condition", + domain_type_string); + test_triggers_buffer_usage_condition(session_name, channel_name, + domain_type, + LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH); + + diag("Test buffer usage notification channel api for domain %s", + domain_type_string); + test_buffer_usage_notification_channel(session_name, channel_name, + domain_type, argv); + break; + } + case 3: + { + /* + * Test cases that need a test app with more than one event + * type. + */ + plan_tests(25); + + /* + * At the moment, the only test case of this scenario is + * exclusion which is only supported by UST + */ + assert(domain_type == LTTNG_DOMAIN_UST); + diag("Test tracepoint event rule notifications with exclusion for domain %s", + domain_type_string); + test_tracepoint_event_rule_notification_exclusion(domain_type); + + break; + } + case 4: + { + plan_tests(13); + /* Test cases that need the kernel tracer. */ + assert(domain_type == LTTNG_DOMAIN_KERNEL); + + diag("Test kprobe event rule notifications for domain %s", + domain_type_string); + + test_kprobe_event_rule_notification(domain_type); + + break; + } + case 5: + { + plan_tests(25); + /* Test cases that need the kernel tracer. */ + assert(domain_type == LTTNG_DOMAIN_KERNEL); + + diag("Test syscall event rule notifications for domain %s", + domain_type_string); + + test_syscall_event_rule_notification(domain_type); + + diag("Test syscall filtering event rule notifications for domain %s", + domain_type_string); + + test_syscall_event_rule_notification_filter(domain_type); + + break; + } + case 6: + { + const char *testapp_path, *test_symbol_name; + + plan_tests(13); + + if (argc < 7) { + fail("Missing parameter for tests to run %d", argc); + goto error; + } + + testapp_path = argv[5]; + test_symbol_name = argv[6]; + /* Test cases that need the kernel tracer. */ + assert(domain_type == LTTNG_DOMAIN_KERNEL); + + diag("Test userspace-probe event rule notifications for domain %s", + domain_type_string); + + test_uprobe_event_rule_notification( + domain_type, testapp_path, test_symbol_name); + + break; + } + case 7: + { + switch(domain_type) { + case LTTNG_DOMAIN_UST: + plan_tests(222); + break; + case LTTNG_DOMAIN_KERNEL: + plan_tests(216); + break; + default: + assert(0); + } + + diag("Test tracepoint event rule notification captures for domain %s", + domain_type_string); + test_tracepoint_event_rule_notification_capture(domain_type); + + break; + } + + default: + abort(); + } - diag("Test notification channel api for domain %s", domain_type_string); - test_notification_channel(session_name, channel_name, domain_type, argv); error: return exit_status(); } diff --git a/tests/regression/tools/notification/test_notification_kernel_buffer_usage b/tests/regression/tools/notification/test_notification_kernel_buffer_usage new file mode 100755 index 000000000..3214f90f9 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_buffer_usage @@ -0,0 +1,88 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +#This is needed since the testpoint create a pipe with the consumerd type suffixed +TESTPOINT_BASE_PATH=$(readlink -f "$TMPDIR/lttng.t_p_n") +TESTPOINT_PIPE_PATH=$(mktemp -u "${TESTPOINT_BASE_PATH}.XXXXXX") +TESTPOINT=$(readlink -f "${CURDIR}/.libs/libpause_consumer.so") +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +NUM_TESTS=104 + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_buffer_usage_notification +{ + local event_name="lttng_test_filter_event" + local trace_path + local page_size + local consumerd_pipe=() + + trace_path=$(mktemp -d) + page_size=$(getconf PAGE_SIZE) + + create_lttng_session_notap $SESSION_NAME "$trace_path" + + lttng_enable_kernel_channel_notap $SESSION_NAME $CHANNEL_NAME \ + --subbuf-size="$page_size" + enable_kernel_lttng_event_notap $SESSION_NAME $event_name $CHANNEL_NAME + + kernel_event_generator generate_filter_events "$TESTAPP_STATE_PATH" & + APP_PID=$! + + # This is needed since the testpoint create a pipe with the consumer + # type suffixed. + for f in "$TESTPOINT_BASE_PATH"*; do + consumerd_pipe+=("$f") + done + + "$CURDIR/notification" 2 LTTNG_DOMAIN_KERNEL $APP_PID "$TESTAPP_STATE_PATH" \ + $SESSION_NAME $CHANNEL_NAME "${consumerd_pipe[@]}" + + destroy_lttng_session_notap $SESSION_NAME + + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null +} + +if [ "$(id -u)" == "0" ]; then + + validate_lttng_modules_present + + + modprobe lttng-test + + # Used on sessiond launch. + LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 \ + CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} \ + LD_PRELOAD=${TESTPOINT}" + start_lttng_sessiond_notap + + test_buffer_usage_notification + + stop_lttng_sessiond_notap + rmmod lttng-test + + rm -rf "${consumerd_pipe[@]}" 2> /dev/null +else + # Kernel tests are skipped. + plan_tests $NUM_TESTS + skip 0 "Root access is needed. Skipping all kernel notification tests." $NUM_TESTS +fi + +rm -rf "$TMPDIR" diff --git a/tests/regression/tools/notification/test_notification_kernel_capture b/tests/regression/tools/notification/test_notification_kernel_capture new file mode 100755 index 000000000..9b6e7ac57 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_capture @@ -0,0 +1,55 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +TESTAPP_PATH="$TESTDIR/utils/testapp" +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +NUM_TESTS=104 + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_basic_error_path +{ + kernel_event_generator_run_once_per_transition generate_filter_events \ + "$TESTAPP_STATE_PATH" 10 & + APP_PID=$! + + "$CURDIR/notification" 7 LTTNG_DOMAIN_KERNEL $APP_PID \ + "$TESTAPP_STATE_PATH" + + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null +} + + +if [ "$(id -u)" == "0" ]; then + validate_lttng_modules_present + + modprobe lttng-test + + start_lttng_sessiond_notap + + test_basic_error_path + + stop_lttng_sessiond_notap + rmmod lttng-test + +else + # Kernel tests are skipped. + plan_tests $NUM_TESTS + skip 0 "Root access is needed. Skipping all kernel notification tests." $NUM_TESTS +fi + +# Just in case cleanup +rm -rf "$TMPDIR" diff --git a/tests/regression/tools/notification/test_notification_kernel_error b/tests/regression/tools/notification/test_notification_kernel_error new file mode 100755 index 000000000..ae39e9e3e --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_error @@ -0,0 +1,55 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +TESTAPP_PATH="$TESTDIR/utils/testapp" +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +NUM_TESTS=104 + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_basic_error_path +{ + kernel_event_generator_run_once_per_transition generate_filter_events \ + "$TESTAPP_STATE_PATH" 10 & + APP_PID=$! + + "$CURDIR/notification" 1 LTTNG_DOMAIN_KERNEL $APP_PID \ + "$TESTAPP_STATE_PATH" + + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null +} + + +if [ "$(id -u)" == "0" ]; then + validate_lttng_modules_present + + modprobe lttng-test + + start_lttng_sessiond_notap + + test_basic_error_path + + stop_lttng_sessiond_notap + rmmod lttng-test + +else + # Kernel tests are skipped. + plan_tests $NUM_TESTS + skip 0 "Root access is needed. Skipping all kernel notification tests." $NUM_TESTS +fi + +# Just in case cleanup +rm -rf "$TMPDIR" diff --git a/tests/regression/tools/notification/test_notification_kernel_instrumentation b/tests/regression/tools/notification/test_notification_kernel_instrumentation new file mode 100755 index 000000000..1bc702b70 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_instrumentation @@ -0,0 +1,51 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +NUM_TESTS=104 + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_kernel_instrumentation_notification +{ + kernel_event_generator generate_filter_events "$TESTAPP_STATE_PATH" & + APP_PID=$! + + "$CURDIR/notification" 4 LTTNG_DOMAIN_KERNEL $APP_PID \ + "$TESTAPP_STATE_PATH" + + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null +} + +if [ "$(id -u)" == "0" ]; then + validate_lttng_modules_present + + modprobe lttng-test + + start_lttng_sessiond_notap + + test_kernel_instrumentation_notification + + stop_lttng_sessiond_notap + rmmod lttng-test + +else + # Kernel tests are skipped. + plan_tests $NUM_TESTS + skip 0 "Root access is needed. Skipping all kernel notification tests." $NUM_TESTS +fi + +rm -rf "$TMPDIR" diff --git a/tests/regression/tools/notification/test_notification_kernel_syscall b/tests/regression/tools/notification/test_notification_kernel_syscall new file mode 100755 index 000000000..cf6a0e097 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_syscall @@ -0,0 +1,52 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +NUM_TESTS=104 + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_kernel_syscall_notification +{ + kernel_event_generator_run_once_per_transition generate_syscalls \ + "$TESTAPP_STATE_PATH" 10 /proc/cpuinfo /proc/cmdline & + APP_PID=$! + + # Pass the syscall_generator_file for filtering + "$CURDIR/notification" 5 LTTNG_DOMAIN_KERNEL $APP_PID \ + "$TESTAPP_STATE_PATH" + + + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null +} + +if [ "$(id -u)" == "0" ]; then + validate_lttng_modules_present + + start_lttng_sessiond_notap + + test_kernel_syscall_notification + + stop_lttng_sessiond_notap + +else + # Kernel tests are skipped. + plan_tests $NUM_TESTS + skip 0 "Root access is needed. Skipping all kernel notification tests." $NUM_TESTS +fi + +# Just in case cleanup +rm -rf "$TMPDIR" diff --git a/tests/regression/tools/notification/test_notification_kernel_userspace_probe b/tests/regression/tools/notification/test_notification_kernel_userspace_probe new file mode 100755 index 000000000..6a50869f0 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_userspace_probe @@ -0,0 +1,49 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +NUM_TESTS=104 + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_kernel_userspace_probe_notification +{ + kernel_event_generator_run_once_per_transition userspace_probe_testapp "$TESTAPP_STATE_PATH" 10 & + APP_PID=$! + + "$CURDIR/notification" 6 LTTNG_DOMAIN_KERNEL \ + $APP_PID "$TESTAPP_STATE_PATH" \ + "$USERSPACE_PROBE_ELF_TESTAPP_BIN" "test_function" + + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null +} + +if [ "$(id -u)" == "0" ]; then + validate_lttng_modules_present + + start_lttng_sessiond_notap + + test_kernel_userspace_probe_notification + + stop_lttng_sessiond_notap +else + # Kernel tests are skipped. + plan_tests $NUM_TESTS + skip 0 "Root access is needed. Skipping all kernel notification tests." $NUM_TESTS +fi + +# Just in case cleanup +rm -rf "$TMPDIR" diff --git a/tests/regression/tools/notification/test_notification_multi_app b/tests/regression/tools/notification/test_notification_multi_app index cfecd26c3..f2cbf43a8 100755 --- a/tests/regression/tools/notification/test_notification_multi_app +++ b/tests/regression/tools/notification/test_notification_multi_app @@ -32,6 +32,7 @@ NUM_TEST_KERNEL=50 NUM_TESTS=$(($NUM_TEST_UST + $NUM_TEST_KERNEL)) source $TESTDIR/utils/utils.sh +source $CURDIR/util_event_generator.sh consumerd_pipe=() file_sync_after_first_event=$(mktemp -u) @@ -43,54 +44,6 @@ print_test_banner "$TEST_DESC" app_pids=() -function kernel_event_generator_toggle_state -{ - kernel_event_generator_suspended=$((kernel_event_generator_suspended==0)) - -} -function kernel_event_generator -{ - state_file=$1 - kernel_event_generator_suspended=0 - trap kernel_event_generator_toggle_state SIGUSR1 - - while (true); do - if [[ $kernel_event_generator_suspended -eq "1" ]]; then - touch $state_file - sleep 0.5 - else - if [[ -f $state_file ]]; then - rm $state_file 2> /dev/null - fi - echo -n "1000" > /proc/lttng-test-filter-event - fi - done -} - -function ust_event_generator_toggle_state -{ - ust_event_generator_suspended=$((ust_event_generator_suspended==0)) - -} -function ust_event_generator -{ - state_file=$1 - ust_event_generator_suspended=0 - trap ust_event_generator_toggle_state SIGUSR1 - trap "exit" SIGTERM SIGINT - while (true); do - if [[ $ust_event_generator_suspended -eq "1" ]]; then - touch $state_file - sleep 0.5 - else - if [[ -f $state_file ]]; then - rm $state_file 2> /dev/null - fi - taskset -c 0 $TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT > /dev/null 2>&1 - fi - done -} - function start_client { local pid=-1 local output_file=$1 @@ -286,7 +239,6 @@ function test_multi_app () print_errors $output_dir "${high_output_file_pattern}" fi - rm -rf $output_dir destroy_lttng_session_ok $SESSION_NAME stop_lttng_sessiond @@ -294,17 +246,19 @@ function test_multi_app () for pipe in "${consumerd_pipe[@]}"; do rm -rf "${pipe}" done + + rm -rf $output_dir } function test_multi_app_ust () { diag "Multi client app UST notification" - ust_event_generator $TESTAPP_STATE_FILE & + ust_event_generator "$TESTAPP_BIN" "$TESTAPP_STATE_FILE" & local generator_pid=$! test_multi_app ust $generator_pid - kill -s SIGTERM $generator_pid 2> /dev/null + kill -s SIGUSR2 $generator_pid 2> /dev/null wait $generator_pid 2> /dev/null rm -rf ${TESTAPP_STATE_FILE} 2> /dev/null } @@ -314,13 +268,13 @@ function test_multi_app_kernel () diag "Multi client app kernel notification" modprobe lttng-test - kernel_event_generator $TESTAPP_STATE_FILE & + kernel_event_generator generate_filter_events $TESTAPP_STATE_FILE & local generator_pid=$! test_multi_app kernel $generator_pid - kill -s SIGTERM $generator_pid 2> /dev/null + kill -s SIGUSR2 $generator_pid 2> /dev/null wait $generator_pid 2> /dev/null rm -rf ${TESTAPP_STATE_FILE} 2> /dev/null @@ -332,12 +286,12 @@ function test_on_register_evaluation_ust () diag "On register notification UST" # Start app in infinite loop - ust_event_generator $TESTAPP_STATE_FILE & + ust_event_generator "$TESTAPP_BIN" "$TESTAPP_STATE_FILE" & local generator_pid=$! test_on_register_evaluation ust $generator_pid - kill -s SIGTERM $generator_pid 2> /dev/null + kill -s SIGUSR2 $generator_pid 2> /dev/null wait $generator_pid 2> /dev/null rm -rf ${TESTAPP_STATE_FILE} 2> /dev/null @@ -349,13 +303,13 @@ function test_on_register_evaluation_kernel() modprobe lttng-test - kernel_event_generator $TESTAPP_STATE_FILE & + kernel_event_generator generate_filter_events $TESTAPP_STATE_FILE & local generator_pid=$! test_on_register_evaluation kernel $generator_pid - kill -s SIGTERM $generator_pid 2> /dev/null + kill -s SIGUSR2 $generator_pid 2> /dev/null wait $generator_pid 2> /dev/null rm -rf ${TESTAPP_STATE_FILE} 2> /dev/null @@ -442,18 +396,18 @@ function test_on_register_evaluation () print_errors "${high_output_file_pattern}" fi - rm -rf $output_dir destroy_lttng_session_ok $SESSION_NAME stop_lttng_sessiond - kill -s SIGTERM $generator_pid 2> /dev/null + kill -s SIGUSR2 $generator_pid 2> /dev/null wait $generator_pid 2> /dev/null for pipe in "${consumerd_pipe[@]}"; do rm -rf "${pipe}" done + rm -rf "$output_dir" } diff --git a/tests/regression/tools/notification/test_notification_ust_buffer_usage b/tests/regression/tools/notification/test_notification_ust_buffer_usage new file mode 100755 index 000000000..a8b96bb33 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_ust_buffer_usage @@ -0,0 +1,74 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +#This is needed since the testpoint create a pipe with the consumerd type suffixed +TESTPOINT_BASE_PATH=$(readlink -f "$TMPDIR/lttng.t_p_n") +TESTPOINT_PIPE_PATH=$(mktemp -u "${TESTPOINT_BASE_PATH}.XXXXXX") +TESTPOINT=$(readlink -f "${CURDIR}/.libs/libpause_consumer.so") + +TESTAPP_PATH="$TESTDIR/utils/testapp" + +GEN_UST_EVENTS_TESTAPP_NAME="gen-ust-events" +GEN_UST_EVENTS_TESTAPP_BIN="$TESTAPP_PATH/$GEN_UST_EVENTS_TESTAPP_NAME/$GEN_UST_EVENTS_TESTAPP_NAME" + +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +TRACE_PATH=$(mktemp -d) +PAGE_SIZE=$(getconf PAGE_SIZE) + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_buffer_usage_notification +{ + consumerd_pipe=() + event_name="tp:tptest" + + create_lttng_session_notap $SESSION_NAME "$TRACE_PATH" + + enable_ust_lttng_channel_notap $SESSION_NAME $CHANNEL_NAME --subbuf-size="$PAGE_SIZE" + enable_ust_lttng_event_notap $SESSION_NAME $event_name $CHANNEL_NAME + + # This is needed since the testpoint create a pipe with the consumer type suffixed + for f in "$TESTPOINT_BASE_PATH"*; do + consumerd_pipe+=("$f") + done + + ust_event_generator "$GEN_UST_EVENTS_TESTAPP_BIN" "$TESTAPP_STATE_PATH" & + APP_PID=$! + + "$CURDIR/notification" 2 LTTNG_DOMAIN_UST $APP_PID "$TESTAPP_STATE_PATH" \ + $SESSION_NAME $CHANNEL_NAME "${consumerd_pipe[@]}" + + destroy_lttng_session_notap $SESSION_NAME + + # On ungraceful kill the app is cleaned up via the full_cleanup call + # Suppress kill message + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null + + # Just in case cleanup + rm -rf "$TRACE_PATH" +} + +LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} LD_PRELOAD=${TESTPOINT}" +start_lttng_sessiond_notap + +test_buffer_usage_notification + +stop_lttng_sessiond_notap + +rm -rf "$TMPDIR" diff --git a/tests/regression/tools/notification/test_notification_ust_capture b/tests/regression/tools/notification/test_notification_ust_capture new file mode 100755 index 000000000..5003c2894 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_ust_capture @@ -0,0 +1,40 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +TESTAPP_PATH="$TESTDIR/utils/testapp" + +GEN_UST_EVENTS_TESTAPP_NAME="gen-ust-events" +GEN_UST_EVENTS_TESTAPP_BIN="$TESTAPP_PATH/$GEN_UST_EVENTS_TESTAPP_NAME/$GEN_UST_EVENTS_TESTAPP_NAME" + +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_basic_error_path +{ + ust_event_generator_run_once_per_transition \ + "$GEN_UST_EVENTS_TESTAPP_BIN" "$TESTAPP_STATE_PATH" 3 10 & + APP_PID=$! + + "$CURDIR/notification" 7 LTTNG_DOMAIN_UST $APP_PID "$TESTAPP_STATE_PATH" + + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null +} + +start_lttng_sessiond_notap + +test_basic_error_path + +stop_lttng_sessiond_notap diff --git a/tests/regression/tools/notification/test_notification_ust_error b/tests/regression/tools/notification/test_notification_ust_error new file mode 100755 index 000000000..57de30df8 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_ust_error @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +TESTAPP_PATH="$TESTDIR/utils/testapp" + +GEN_UST_EVENTS_TESTAPP_NAME="gen-ust-events" +GEN_UST_EVENTS_TESTAPP_BIN="$TESTAPP_PATH/$GEN_UST_EVENTS_TESTAPP_NAME/$GEN_UST_EVENTS_TESTAPP_NAME" + +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_basic_error_path +{ + ust_event_generator_run_once_per_transition "$GEN_UST_EVENTS_TESTAPP_BIN" "$TESTAPP_STATE_PATH" 5 5 & + APP_PID=$! + + "$CURDIR/notification" 1 LTTNG_DOMAIN_UST $APP_PID "$TESTAPP_STATE_PATH" + + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null +} + +start_lttng_sessiond_notap + +test_basic_error_path + +stop_lttng_sessiond_notap diff --git a/tests/regression/tools/notification/test_notification_ust_event_rule_condition_exclusion b/tests/regression/tools/notification/test_notification_ust_event_rule_condition_exclusion new file mode 100755 index 000000000..12ae728bb --- /dev/null +++ b/tests/regression/tools/notification/test_notification_ust_event_rule_condition_exclusion @@ -0,0 +1,41 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: LGPL-2.1-only + +CURDIR=$(dirname "$0")/ +TESTDIR=$CURDIR/../../../ + +TMPDIR=$(mktemp -d) + +TESTAPP_PATH="$TESTDIR/utils/testapp" + +GEN_UST_NEVENTS_TESTAPP_NAME="gen-ust-nevents" +GEN_UST_NEVENTS_TESTAPP_BIN="$TESTAPP_PATH/$GEN_UST_NEVENTS_TESTAPP_NAME/$GEN_UST_NEVENTS_TESTAPP_NAME" + +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" +# shellcheck source=./util_event_generator.sh +source "$CURDIR/util_event_generator.sh" + +function test_event_rule_condition_exclusion_notification +{ + ust_event_generator_run_once_per_transition "$GEN_UST_NEVENTS_TESTAPP_BIN" "$TESTAPP_STATE_PATH" 5 5 & + APP_PID=$! + + "$CURDIR/notification" 3 LTTNG_DOMAIN_UST $APP_PID "$TESTAPP_STATE_PATH" + + kill -SIGUSR2 $APP_PID + wait $APP_PID 2> /dev/null +} + +start_lttng_sessiond_notap + +test_event_rule_condition_exclusion_notification + +stop_lttng_sessiond_notap + +rm -rf "$TMPDIR" diff --git a/tests/regression/tools/notification/util_event_generator.sh b/tests/regression/tools/notification/util_event_generator.sh new file mode 100644 index 000000000..cafd0643e --- /dev/null +++ b/tests/regression/tools/notification/util_event_generator.sh @@ -0,0 +1,195 @@ +#!/bin/bash +# +# Copyright (C) 2020 Jonathan Rajotte-Julien +# +# SPDX-License-Identifier: LGPL-2.1-only + +GENERATOR_CURDIR=$(dirname "$0")/ +GENERATOR_TESTDIR=$GENERATOR_CURDIR/../../../ +TESTAPP_PATH=${TESTAPP_PATH:-"$GENERATOR_TESTDIR/utils/testapp"} + +SYSCALL_TESTAPP_NAME=${SYSCALL_TESTAPP_NAME:-"gen-syscall-events"} +SYSCALL_TESTAPP_BIN=${SYSCALL_TESTAPP_BIN:-"$TESTAPP_PATH/$SYSCALL_TESTAPP_NAME/$SYSCALL_TESTAPP_NAME"} + +USERSPACE_PROBE_ELF_TESTAPP_NAME=${USERSPACE_PROBE_ELF_TESTAPP_NAME:-"userspace-probe-elf-binary"} +USERSPACE_PROBE_ELF_TESTAPP_BIN=${USERSPACE_PROBE_ELF_TESTAPP_BIN:-"$TESTAPP_PATH/$USERSPACE_PROBE_ELF_TESTAPP_NAME/.libs/$USERSPACE_PROBE_ELF_TESTAPP_NAME"} + +function generate_filter_events +{ + local nr=$1 + /bin/echo -n "$nr" > /proc/lttng-test-filter-event 2> /dev/null +} + +function generate_syscalls +{ + local nr=$1 + shift + + for i in $(seq 1 "$nr"); do + # Pass /dev/null so to generate the syscall right away. + $SYSCALL_TESTAPP_BIN /dev/null "$@" + done +} + +function userspace_probe_testapp +{ + local nr=$1 + shift + + for i in $(seq 1 "$nr"); do + $USERSPACE_PROBE_ELF_TESTAPP_BIN "$@" + done +} + +function ust_event_generator_toggle_state +{ + ust_event_generator_suspended=$((ust_event_generator_suspended==0)) +} + +function generator_quit +{ + generator_quit=0 +} + +# Note: Only one generator can be used at a time per domain type +function ust_event_generator_run_once_per_transition +{ + # Used by the signal trap + ust_event_generator_suspended=0 + # Used by the signal trap for SIGUSR2 to end the generator + generator_quit=1 + + local test_app=$1 + local state_file=$2 + local nr_iter=$3 + local nr_usec_wait=$4 + local run=false + + # Pass any of the remaining arguments to the generator. + shift 4 + + trap ust_event_generator_toggle_state SIGUSR1 + trap generator_quit SIGUSR2 + + while [ $generator_quit -ne 0 ]; do + if [[ $ust_event_generator_suspended -eq "1" ]]; then + touch "$state_file" + # Reset the "run" state + run=true + sleep 0.5 + elif [ "$run" = true ]; then + taskset -c 0 "$test_app" -i "$nr_iter" -w "$nr_usec_wait" "$@"> /dev/null 2>&1 + run=false; + if [[ -f $state_file ]]; then + rm -rf "$state_file" 2> /dev/null + fi + else + # Wait for a "suspend" to reset the run state + sleep 0.1 + fi + done +} + +# Note: Only one generator can be used at a time per domain type +function ust_event_generator +{ + # Used by the signal trap + ust_event_generator_suspended=0 + # Used by the signal trap for SIGUSR2 to end the generator + generator_quit=1 + + local test_app=$1 + local state_file=$2 + local nr_iter=1000 + local nr_usec_wait=5 + + # Pass any of the remaining arguments to the generator. + shift 2 + + trap ust_event_generator_toggle_state SIGUSR1 + trap generator_quit SIGUSR2 + + while [ $generator_quit -ne 0 ]; do + if [[ $ust_event_generator_suspended -eq "1" ]]; then + touch "$state_file" + # Reset the "run" state + sleep 0.5 + else + taskset -c 0 "$test_app" -i $nr_iter -w $nr_usec_wait "$@" > /dev/null 2>&1 + if [[ -f $state_file ]]; then + rm -rf "$state_file" 2> /dev/null + fi + fi + done +} + +function kernel_event_generator_toggle_state +{ + kernel_event_generator_suspended=$((kernel_event_generator_suspended==0)) + +} + +function kernel_event_generator_run_once_per_transition +{ + # Used by the signal trap + kernel_event_generator_suspended=0 + # Used by the signal trap for SIGUSR2 to end the generator + generator_quit=1 + + local generator=$1 + local state_file=$2 + local nr_iter=$3 + + # Pass any of the remaining arguments to the generator. + shift 3 + + local run=false + trap kernel_event_generator_toggle_state SIGUSR1 + trap generator_quit SIGUSR2 + + while [ $generator_quit -ne 0 ]; do + if [[ $kernel_event_generator_suspended -eq "1" ]]; then + touch "$state_file" + run=true + sleep 0.5 + elif [ "$run" = true ]; then + $generator "$nr_iter" "$@" + run=false + if [[ -f $state_file ]]; then + rm "$state_file" 2> /dev/null + fi + else + # Wait for a "suspend" to reset the run state + sleep 0.1 + fi + done +} + +function kernel_event_generator +{ + # Used by the signal trap + kernel_event_generator_suspended=0 + # Used by the signal trap for SIGUSR2 to end the generator + generator_quit=1 + + local generator=$1 + local state_file=$2 + + # Pass any of the remaining arguments to the generator. + shift 2 + + trap kernel_event_generator_toggle_state SIGUSR1 + trap generator_quit SIGUSR2 + + while [ $generator_quit -ne 0 ]; do + if [[ $kernel_event_generator_suspended -eq "1" ]]; then + touch "$state_file" + sleep 0.5 + else + $generator "10" "$@" + if [[ -f $state_file ]]; then + rm "$state_file" 2> /dev/null + fi + fi + done +} diff --git a/tests/regression/tools/trigger/Makefile.am b/tests/regression/tools/trigger/Makefile.am new file mode 100644 index 000000000..11c546e1e --- /dev/null +++ b/tests/regression/tools/trigger/Makefile.am @@ -0,0 +1,56 @@ +AM_CFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src -I$(top_srcdir)/tests -I$(top_srcdir)/tests/utils/ -I$(srcdir) +AM_LDFLAGS = + +SUBDIRS=utils start-stop + +LIBTAP=$(top_builddir)/tests/utils/tap/libtap.la +LIB_LTTNG_CTL = $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la + +noinst_PROGRAMS = base_client trigger + +if NO_SHARED + +CLEANFILES = libpause_consumer.so libpause_consumer.so.debug +EXTRA_DIST = test_trigger_ust test_trigger_kernel base_client.c trigger.c consumer_testpoints.c + +else + +# In order to use testpoint, the helper library +# must be built as .so to be able to LD_PRELOAD it. +FORCE_SHARED_LIB_OPTIONS = -module -shared -avoid-version \ + -rpath $(abs_builddir) + +libpause_consumer_la_SOURCES = consumer_testpoints.c +libpause_consumer_la_LIBADD = \ + $(top_builddir)/src/common/libcommon.la \ + $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la \ + $(DL_LIBS) +libpause_consumer_la_LDFLAGS = $(FORCE_SHARED_LIB_OPTIONS) +noinst_LTLIBRARIES = libpause_consumer.la + +base_client_SOURCES = base_client.c +base_client_LDADD = $(LIB_LTTNG_CTL) + +trigger_SOURCES = trigger.c +trigger_LDADD = $(LIB_LTTNG_CTL) $(LIBTAP) -lm + +noinst_SCRIPTS = test_trigger_ust test_trigger_kernel +EXTRA_DIST = test_trigger_ust test_trigger_kernel \ + test_add_trigger_cli \ + test_list_triggers_cli \ + test_remove_trigger_cli + +all-local: + @if [ x"$(srcdir)" != x"$(builddir)" ]; then \ + for script in $(EXTRA_DIST); do \ + cp -f $(srcdir)/$$script $(builddir); \ + done; \ + fi + +clean-local: + @if [ x"$(srcdir)" != x"$(builddir)" ]; then \ + for script in $(EXTRA_DIST); do \ + rm -f $(builddir)/$$script; \ + done; \ + fi +endif diff --git a/tests/regression/tools/trigger/base_client.c b/tests/regression/tools/trigger/base_client.c new file mode 100644 index 000000000..f5bd7f159 --- /dev/null +++ b/tests/regression/tools/trigger/base_client.c @@ -0,0 +1,252 @@ +/* + * base_client.c + * + * Base client application for testing of LTTng trigger API + * + * Copyright 2019 Jonathan Rajotte + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const char *session_name = NULL; +enum lttng_domain_type domain_type = LTTNG_DOMAIN_NONE; +const char *pattern = NULL; + +int parse_arguments(char **argv) { + const char *domain_type_string = NULL; + + session_name = argv[1]; + domain_type_string = argv[2]; + pattern = argv[3]; + + /* Parse arguments */ + /* Domain type */ + if (!strcasecmp("LTTNG_DOMAIN_UST", domain_type_string)) { + domain_type = LTTNG_DOMAIN_UST; + } + if (!strcasecmp("LTTNG_DOMAIN_KERNEL", domain_type_string)) { + domain_type = LTTNG_DOMAIN_KERNEL; + } + if (!strcasecmp("LTTNG_DOMAIN_JUL", domain_type_string)) { + domain_type = LTTNG_DOMAIN_JUL; + } + if (!strcasecmp("LTTNG_DOMAIN_PYTHON", domain_type_string)) { + domain_type = LTTNG_DOMAIN_PYTHON; + } + if (!strcasecmp("LTTNG_DOMAIN_LOG4J", domain_type_string)) { + domain_type = LTTNG_DOMAIN_LOG4J; + } + if (domain_type == LTTNG_DOMAIN_NONE) { + printf("error: Unknown domain type\n"); + goto error; + } + + return 0; +error: + return 1; +} + +int main(int argc, char **argv) +{ + int ret = 0; + struct lttng_condition *condition = NULL; + + enum lttng_event_rule_status event_rule_status; + struct lttng_event_rule *event_rule = NULL; + + enum lttng_action_status action_status; + struct lttng_action *action = NULL; + + enum lttng_notification_channel_status nc_status; + struct lttng_notification_channel *notification_channel = NULL; + + const char* exclusions[] = { "sample_component:message2"}; + + struct lttng_trigger *trigger = NULL; + + if (argc < 4) { + printf("error: Missing arguments for tests\n"); + ret = 1; + goto end; + } + + ret = parse_arguments(argv); + if (ret) { + printf("error: Could not parse arguments\n"); + goto end; + } + + event_rule = lttng_event_rule_tracepoint_create(domain_type); + if (!event_rule) { + printf("error: Could not create condition object\n"); + ret = 1; + goto end; + } + + event_rule_status = lttng_event_rule_tracepoint_set_pattern(event_rule, pattern); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + printf("error: Could not set pattern\n"); + ret = 1; + goto end; + } + + event_rule_status = lttng_event_rule_tracepoint_set_filter(event_rule, "message=='Hello World'"); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + printf("error: Could not set pattern\n"); + ret = 1; + goto end; + } + + event_rule_status = lttng_event_rule_tracepoint_set_exclusions(event_rule, 1, exclusions); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + printf("error: Could not set exclusions\n"); + ret = 1; + goto end; + } + + condition = lttng_condition_event_rule_create(event_rule); + if (!condition) { + printf("error: Could not create condition\n"); + ret = 1; + goto end; + } + /* Ownership was passed to condition */ + event_rule = NULL; + + action = lttng_action_notify_create(); + if (!action) { + printf("error: Could not create action notify\n"); + ret = 1; + goto end; + } + + trigger = lttng_trigger_create(condition, action); + if (!trigger) { + printf("error: Could not create trigger\n"); + ret = 1; + goto end; + } + + ret = lttng_register_trigger(trigger); + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + if (!notification_channel) { + printf("error: Could not create notification channel\n"); + ret = 1; + goto end; + } + /* + * An equivalent trigger might already be registered if an other app + * registered an equivalent trigger. + */ + if (ret < 0 && ret != -LTTNG_ERR_TRIGGER_EXISTS) { + printf("error: %s\n", lttng_strerror(ret)); + ret = 1; + goto end; + } + + nc_status = lttng_notification_channel_subscribe(notification_channel, condition); + if (nc_status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { + printf("error: Could not subscribe\n"); + ret = 1; + goto end; + } + + for (;;) { + struct lttng_notification *notification; + enum lttng_notification_channel_status status; + const struct lttng_evaluation *notification_evaluation; + const struct lttng_condition *notification_condition; + const char *name; + + /* Receive the next notification. */ + status = lttng_notification_channel_get_next_notification( + notification_channel, + ¬ification); + + switch (status) { + case LTTNG_NOTIFICATION_CHANNEL_STATUS_OK: + break; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_NOTIFICATIONS_DROPPED: + ret = 1; + printf("error: No drop should be observed during this test app\n"); + goto end; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_CLOSED: + /* + * The notification channel has been closed by the + * session daemon. This is typically caused by a session + * daemon shutting down (cleanly or because of a crash). + */ + printf("error: Notification channel was closed\n"); + ret = 1; + goto end; + default: + /* Unhandled conditions / errors. */ + printf("error: Unknown notification channel status\n"); + ret = 1; + goto end; + } + + notification_condition = lttng_notification_get_condition(notification); + notification_evaluation = lttng_notification_get_evaluation(notification); + switch (lttng_evaluation_get_type(notification_evaluation)) { + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + lttng_evaluation_event_rule_get_trigger_name(notification_evaluation, &name); + printf("Received nootification from trigger \"%s\"\n", name); + break; + default: + printf("error: Wrong notification evaluation type \n"); + break; + + } + + lttng_notification_destroy(notification); + } +end: + if (trigger) { + lttng_unregister_trigger(trigger); + } + lttng_event_rule_destroy(event_rule); + lttng_trigger_destroy(trigger); + lttng_condition_destroy(condition); + lttng_action_destroy(action); + printf("exit: %d\n", ret); + return ret; +} diff --git a/tests/regression/tools/trigger/consumer_testpoints.c b/tests/regression/tools/trigger/consumer_testpoints.c new file mode 100644 index 000000000..1f5d83ecb --- /dev/null +++ b/tests/regression/tools/trigger/consumer_testpoints.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License, version 2 only, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char *pause_pipe_path; +static struct lttng_pipe *pause_pipe; +static int *data_consumption_state; +static enum lttng_consumer_type (*lttng_consumer_get_type)(void); + +int lttng_opt_verbose; +int lttng_opt_mi; +int lttng_opt_quiet; + +static +void __attribute__((destructor)) pause_pipe_fini(void) +{ + int ret; + + if (pause_pipe_path) { + ret = unlink(pause_pipe_path); + if (ret) { + PERROR("unlink pause pipe"); + } + } + + free(pause_pipe_path); + lttng_pipe_destroy(pause_pipe); +} + +/* + * We use this testpoint, invoked at the start of the consumerd's data handling + * thread to create a named pipe/FIFO which a test application can use to either + * pause or resume the consumption of data. + */ +int __testpoint_consumerd_thread_data(void) +{ + int ret = 0; + const char *pause_pipe_path_prefix, *domain; + + pause_pipe_path_prefix = lttng_secure_getenv( + "CONSUMER_PAUSE_PIPE_PATH"); + if (!pause_pipe_path_prefix) { + ret = -1; + goto end; + } + + /* + * These symbols are exclusive to the consumerd process, hence we can't + * rely on their presence in the sessiond. Not looking-up these symbols + * dynamically would not allow this shared object to be LD_PRELOAD-ed + * when launching the session daemon. + */ + data_consumption_state = dlsym(NULL, "data_consumption_paused"); + assert(data_consumption_state); + lttng_consumer_get_type = dlsym(NULL, "lttng_consumer_get_type"); + assert(lttng_consumer_get_type); + + switch (lttng_consumer_get_type()) { + case LTTNG_CONSUMER_KERNEL: + domain = "kernel"; + break; + case LTTNG_CONSUMER32_UST: + domain = "ust32"; + break; + case LTTNG_CONSUMER64_UST: + domain = "ust64"; + break; + default: + abort(); + } + + ret = asprintf(&pause_pipe_path, "%s-%s", pause_pipe_path_prefix, + domain); + if (ret < 1) { + ERR("Failed to allocate pause pipe path"); + goto end; + } + + DBG("Creating pause pipe at %s", pause_pipe_path); + pause_pipe = lttng_pipe_named_open(pause_pipe_path, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, O_NONBLOCK); + if (!pause_pipe) { + ERR("Failed to create pause pipe at %s", pause_pipe_path); + ret = -1; + goto end; + } + + /* Only the read end of the pipe is useful to us. */ + ret = lttng_pipe_write_close(pause_pipe); +end: + return ret; +} + +int __testpoint_consumerd_thread_data_poll(void) +{ + int ret = 0; + uint8_t value; + bool value_read = false; + + if (!pause_pipe) { + ret = -1; + goto end; + } + + /* Purge pipe and only consider the freshest value. */ + do { + ret = lttng_pipe_read(pause_pipe, &value, sizeof(value)); + if (ret == sizeof(value)) { + value_read = true; + } + } while (ret == sizeof(value)); + + ret = (errno == EAGAIN) ? 0 : -errno; + + if (value_read) { + *data_consumption_state = !!value; + DBG("Message received on pause pipe: %s data consumption", + *data_consumption_state ? "paused" : "resumed"); + } +end: + return ret; +} diff --git a/tests/regression/tools/trigger/start-stop/Makefile.am b/tests/regression/tools/trigger/start-stop/Makefile.am new file mode 100644 index 000000000..c28007d59 --- /dev/null +++ b/tests/regression/tools/trigger/start-stop/Makefile.am @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only + +noinst_SCRIPTS = test_start_stop + +EXTRA_DIST = test_start_stop + +all-local: + @if [ x"$(srcdir)" != x"$(builddir)" ]; then \ + for script in $(EXTRA_DIST); do \ + cp -f $(srcdir)/$$script $(builddir); \ + done; \ + fi + +clean-local: + @if [ x"$(srcdir)" != x"$(builddir)" ]; then \ + for script in $(EXTRA_DIST); do \ + rm -f $(builddir)/$$script; \ + done; \ + fi diff --git a/tests/regression/tools/trigger/start-stop/test_start_stop b/tests/regression/tools/trigger/start-stop/test_start_stop new file mode 100755 index 000000000..935d630cb --- /dev/null +++ b/tests/regression/tools/trigger/start-stop/test_start_stop @@ -0,0 +1,198 @@ +#!/bin/bash +# +# Copyright (C) 2020 Francis Deslauriers +# +# SPDX-License-Identifier: LGPL-2.1-only + +TEST_DESC="Triggers - Start and stop actions" + +CURDIR=$(dirname "$0")/ +TESTDIR=${CURDIR}/../../../.. + +# shellcheck source=../../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" + +TESTAPP_PATH="$TESTDIR/utils/testapp" +GEN_UST_EVENTS_TESTAPP_NAME="gen-ust-events" +GEN_UST_EVENTS_TESTAPP_BIN="$TESTAPP_PATH/$GEN_UST_EVENTS_TESTAPP_NAME/$GEN_UST_EVENTS_TESTAPP_NAME" +FULL_LTTNG_BIN="$TESTDIR/../src/bin/lttng/$LTTNG_BIN" +NOTIFICATION_CLIENT_BIN="$CURDIR/../utils/notification-client" +NUM_TESTS=18 + +NR_ITER=5 +NR_USEC_WAIT=5 + +function lttng_add_trigger_ust() +{ + local expected_to_fail="$1" + local trigger_name="$2" + shift 2 + + "$FULL_LTTNG_BIN" add-trigger --id "$trigger_name" "$@" 1> /dev/null 2> /dev/null + ret=$? + if [[ $expected_to_fail -eq "1" ]]; then + test "$ret" -ne "0" + ok $? "Add trigger $trigger_name failed as expected" + else + ok $ret "Add trigger $trigger_name" + fi +} + +function lttng_remove_trigger_ust() +{ + local expected_to_fail="$1" + local trigger_name="$2" + + "$FULL_LTTNG_BIN" remove-trigger "$trigger_name" 1> /dev/null 2> /dev/null + ret=$? + if [[ $expected_to_fail -eq "1" ]]; then + test "$ret" -ne "0" + ok $? "Remove trigger $trigger_name failed as expected" + else + ok $ret "Remove trigger $trigger_name" + fi +} + +function lttng_add_trigger_ust_ok() +{ + lttng_add_trigger_ust 0 "$@" +} + +function lttng_remove_trigger_ust_ok() +{ + lttng_remove_trigger_ust 0 "$@" +} + +function lttng_session_is_active() +{ + local SESSION_NAME="$1" + "$FULL_LTTNG_BIN" list "$SESSION_NAME" | grep "Tracing session" | grep -q "\[active\]" + + ok $ret "Session \"$SESSION_NAME\" is active" +} + +function lttng_session_is_inactive() +{ + local SESSION_NAME="$1" + "$FULL_LTTNG_BIN" list "$SESSION_NAME" | grep "Tracing session" | grep -q "\[inactive\]" + + ok $ret "Session \"$SESSION_NAME\" is inactive" +} + +function test_start_session_action() +{ + local SESSION_NAME="my_triggered_session" + local TRIGGER_NAME="trigger1" + local TRACE_PATH=$(mktemp -d test-start-action-trace.XXXXXX) + local SYNC_AFTER_NOTIF_REGISTER_PATH=$(mktemp test-notif-register.XXXXXX) + + diag "Start session action" + + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" + + enable_ust_lttng_event_ok $SESSION_NAME "tp:tptest" + + lttng_session_is_inactive $SESSION_NAME + + # Add `start-session` action to an event-rule condition _followed_ by + # a `notify` action. + lttng_add_trigger_ust_ok \ + $TRIGGER_NAME \ + --condition on-event -u "tp:tptest" \ + --action start-session $SESSION_NAME \ + --action notify + + # Launch notification listener. + $NOTIFICATION_CLIENT_BIN \ + --trigger $TRIGGER_NAME \ + --sync-after-notif-register "$SYNC_AFTER_NOTIF_REGISTER_PATH" + notif_client_pid=$! + + while [ ! -f "${SYNC_AFTER_NOTIF_REGISTER_PATH}" ]; do + sleep 0.5 + done + + # Artificially produce the desired event-rule condition. + $GEN_UST_EVENTS_TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT > /dev/null 2>&1 + + # notification-client will exit once it receives a notification. + wait $notif_client_pid + test "$?" -eq "0" + ok $? "notification client exited successfully" + + # Test that the session as started. + lttng_session_is_active $SESSION_NAME + + # Tearing down. + lttng_remove_trigger_ust_ok $TRIGGER_NAME + stop_lttng_tracing_ok $SESSION_NAME + destroy_lttng_session_ok $SESSION_NAME + + rm -f "$SYNC_AFTER_NOTIF_REGISTER_PATH" + rm -rf "$TRACE_PATH" +} + +function test_stop_session_action() +{ + local SESSION_NAME="my_triggered_session" + local TRIGGER_NAME="trigger1" + local TRACE_PATH=$(mktemp -d test-stop-action-trace.XXXXXX) + local SYNC_AFTER_NOTIF_REGISTER_PATH=$(mktemp test-notif-register.XXXXXX) + + diag "Stop session action" + create_lttng_session_ok $SESSION_NAME "$TRACE_PATH" + + enable_ust_lttng_event_ok $SESSION_NAME "tp:tptest" + + start_lttng_tracing_ok $SESSION_NAME + + lttng_session_is_active $SESSION_NAME + + # Add `stop-session` action to an event-rule condition _followed_ by + # a `notify` action. + lttng_add_trigger_ust_ok \ + $TRIGGER_NAME \ + --condition on-event -u "tp:tptest" \ + --action stop-session $SESSION_NAME \ + --action notify + + # Launch notification listener. + $NOTIFICATION_CLIENT_BIN \ + --trigger $TRIGGER_NAME \ + --sync-after-notif-register "$SYNC_AFTER_NOTIF_REGISTER_PATH" + notif_client_pid=$! + + while [ ! -f "${SYNC_AFTER_NOTIF_REGISTER_PATH}" ]; do + sleep 0.5 + done + + # Artificially produce the desired event-rule condition. + $GEN_UST_EVENTS_TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT > /dev/null 2>&1 + + # notification-client will exit once it receives a notification. + wait $notif_client_pid + test "$?" -eq "0" + ok $? "notification client exited successfully" + + # Test that the session as started. + lttng_session_is_inactive $SESSION_NAME + + # Tearing down. + lttng_remove_trigger_ust_ok $TRIGGER_NAME + destroy_lttng_session_ok $SESSION_NAME + + rm -f "$SYNC_AFTER_NOTIF_REGISTER_PATH" + rm -rf "$TRACE_PATH" +} + + # MUST set TESTDIR before calling those functions +plan_tests $NUM_TESTS + +print_test_banner "$TEST_DESC" + +start_lttng_sessiond_notap + +test_start_session_action +test_stop_session_action + +stop_lttng_sessiond_notap diff --git a/tests/regression/tools/trigger/test_add_trigger_cli b/tests/regression/tools/trigger/test_add_trigger_cli new file mode 100755 index 000000000..38d2b26d3 --- /dev/null +++ b/tests/regression/tools/trigger/test_add_trigger_cli @@ -0,0 +1,403 @@ +#!/bin/bash +# +# Copyright (C) - 2020 EfficiOS, inc +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# Test the `lttng add-trigger` command line interface. + +CURDIR="$(dirname "$0")" +TESTDIR="$CURDIR/../../.." + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" + +plan_tests 219 + +FULL_LTTNG_BIN="${TESTDIR}/../src/bin/lttng/${LTTNG_BIN}" + +# shellcheck disable=SC2119 +start_lttng_sessiond_notap + +tmp_stdout=$(mktemp -t test_parse_cli_trigger_stdout.XXXXXX) +tmp_stderr=$(mktemp -t test_parse_cli_trigger_stderr.XXXXXX) +uprobe_elf_binary="${TESTDIR}/utils/testapp/userspace-probe-elf-binary/userspace-probe-elf-binary" + +if [ "$(id -u)" == "0" ]; then + ist_root=1 +else + ist_root=0 +fi + +function test_success () +{ + local test_name="$1" + shift + + diag "${FULL_LTTNG_BIN} add-trigger $*" + "${FULL_LTTNG_BIN}" add-trigger "$@" > "${tmp_stdout}" 2> "${tmp_stderr}" + ok $? "${test_name}: exit code is 0" + + diff -u "${tmp_stdout}" <(echo "Trigger registered successfully.") + ok $? "${test_name}: expected stdout" + + diff -u "${tmp_stderr}" /dev/null + ok $? "${test_name}: expected stderr" +} + +function test_failure () +{ + local test_name="$1" + local error_msg="$2" + + shift 2 + + diag "${FULL_LTTNG_BIN} add-trigger $*" + "${FULL_LTTNG_BIN}" add-trigger "$@" > "${tmp_stdout}" 2> "${tmp_stderr}" + isnt $? 0 "${test_name}: exit code is not 0" + + diff -u "${tmp_stdout}" /dev/null + ok $? "${test_name}: expected stdout" + + diff -u "${tmp_stderr}" <(echo "${error_msg}") + ok $? "${test_name}: expected stderr" +} + +# top-level options +test_success "explicit id" \ + --id hohoho \ + --condition on-event some-event-id -u \ + --action notify + +# `--condition on-event` successes +test_success "--condition on-event some-event -u" \ + --condition on-event some-event -u \ + --action notify + +test_success "--condition on-event -a -u" \ + --condition on-event -a -u \ + --action notify + +test_success "--fire-once-after" \ + --condition on-event -u test-fire-once-after \ + --action notify \ + --fire-once-after=55 + +test_success "--fire-every" \ + --condition on-event -u test-fire-every \ + --action notify \ + --fire-every=55 + +skip $ist_root "non-root user: skipping kprobe tests" 12 || { + test_success "--condition on-event probe by symbol" \ + --condition on-event -k --probe=do_sys_open my_sys_open \ + --action notify + + test_success "--condition on-event probe by symbol with offset" \ + --condition on-event -k --probe=do_sys_open+10 my_sys_open \ + --action notify + + sys_open_addr=$(grep ' T do_sys_open$' /proc/kallsyms | cut -f 1 -d ' ') + + test_success "--condition on-event probe by address" \ + --condition on-event -k "--probe=${sys_open_addr}" my_sys_open \ + --action notify + + test_success "--condition on-event probe by address with offset" \ + --condition on-event -k "--probe=${sys_open_addr}+10" my_sys_open \ + --action notify +} + +skip $ist_root "non-root user: skipping uprobe tests" 6 || { + test_success "--condition on-event uprobe" \ + --condition on-event -k --userspace-probe=${uprobe_elf_binary}:test_function ma-probe \ + --action notify + + test_success "--condition on-event uprobe with elf prefix" \ + --condition on-event -k --userspace-probe=elf:${uprobe_elf_binary}:test_function ma-probe-2 \ + --action notify +} + +skip $ist_root "non-root user: skipping syscall tests" 9 || { + test_success "--condition on-event syscall" \ + --condition on-event -k --syscall open \ + --action notify + + test_success "--condition on-event syscall -a" \ + --condition on-event -k --syscall -a \ + --action notify + + test_success "--condition on-event syscall with filter" \ + --condition on-event -k --syscall --filter 'a > 2' open \ + --action notify +} + +# `--action notify` successes +test_success "--action notify" \ + --condition on-event some-event-notify -u \ + --action notify + +test_success "--action notify --capture foo" \ + --condition on-event some-event-notify-foo -u \ + --capture foo --action notify + +test_success "--action notify --capture foo[2]" \ + --condition on-event some-event-notify-foo2 -u \ + --capture 'foo[2]' --action notify + +test_success '--action notify --capture $ctx.foo' \ + --condition on-event some-event-notify-ctx-foo -u \ + --capture '$ctx.foo' --action notify + +test_success '--action notify --capture $ctx.foo[2]' \ + --condition on-event some-event-notify-ctx-foo2 -u \ + --capture '$ctx.foo[2]' --action notify + +test_success '--action notify --capture $app.prov:type' \ + --condition on-event some-event-notify-app-prov-type -u \ + --capture '$app.prov:type' --action notify + +test_success '--action notify --capture $app.prov:type[2]' \ + --condition on-event some-event-notify-app-prov-type-2 -u \ + --capture '$app.prov:type[2]' --action notify + +test_success '--action notify multiple captures' \ + --condition on-event some-event-notify-multiple-captures -u \ + --capture foo --capture '$app.hello:world' --action notify + +# `--action start-session` successes +test_success "--action start-session" \ + --condition on-event some-event-start-session -u \ + --action start-session ze-session + +# `--action stop-session` successes +test_success "--action stop-session foo" \ + --condition on-event some-event-stop-session -u \ + --action stop-session ze-session + +# `--action rotate-session` successes +test_success "--action rotate-session foo" \ + --condition on-event some-event-rotate-session -u \ + --action rotate-session ze-session + +# `--action snapshot-session` successes +test_success "--action snapshot-session foo" \ + --condition on-event some-event-snapshot-session -u \ + --action snapshot-session ze-session + +test_success "--action snapshot-session with file URI" \ + --condition on-event some-event-snapshot-session2 -u \ + --action snapshot-session ze-session /hello + +test_success "--action snapshot-session with net URI" \ + --condition on-event some-event-snapshot-session3 -u \ + --action snapshot-session ze-session net://1.2.3.4 + +test_success "--action snapshot-session with ctrl/data URIs" \ + --condition on-event some-event-snapshot-session4 -u \ + --action snapshot-session ze-session --ctrl-url=tcp://1.2.3.4:1234 --data-url=tcp://1.2.3.4:1235 + +# top-level failures +test_failure "no args" "Error: Missing --condition." + +test_failure "unknown option" \ + "Error: Unknown option \`--hello\`" \ + --hello + +test_failure "missing --action" \ + "Error: Need at least one --action." \ + --condition on-event hello -u + +test_failure "two --condition" \ + "Error: A --condition was already given." \ + --condition on-event aaa -u \ + --condition on-event bbb -u \ + --action notify + +test_failure "missing argument to --id" \ + "Error: While parsing argument #1 (\`--id\`): Missing required argument for option \`--id\`" \ + --id + +for cmd in fire-once-after fire-every; do + test_failure "missing argument to --${cmd}" \ + "Error: While parsing argument #1 (\`--${cmd}\`): Missing required argument for option \`--${cmd}\`" \ + --condition on-event -u -a --action notify \ + --${cmd} + + test_failure "invalid argument to --${cmd}: non-digit character" \ + "Error: Failed to parse \`123bob\` as an integer." \ + --condition on-event -u -a --action notify \ + --${cmd} 123bob + + test_failure "invalid argument to --${cmd}: empty string" \ + "Error: Failed to parse \`\` as an integer." \ + --condition on-event -u -a --action notify \ + --${cmd} "" +done + +# `--condition` failures +test_failure "missing args after --condition" \ + "Error: Missing condition name." \ + --condition +test_failure "unknown --condition" \ + "Error: Unknown condition name: zoofest" \ + --condition zoofest + +# `--condition on-event` failures +test_failure "missing args after --condition on-event" \ + "Error: Need to provide either a tracepoint name or -a/--all." \ + --condition on-event +test_failure "missing domain in --condition on-event" \ + "Error: Please specify a domain (-k/-u/-j)." \ + --condition on-event -a +test_failure "extra args after --condition on-event" \ + "Error: Unexpected argument: bozo" \ + --condition on-event foo -u bozo +test_failure "--condition on-event: --all with --probe" \ + "Error: Can't use -a/--all with event rule of type probe." \ + --condition on-event --probe=do_sys_open --all +test_failure "--condition on-event: missing tracepoint name with --probe" \ + "Error: Need to provide either a tracepoint name or -a/--all." \ + --condition on-event -k --probe do_sys_open + +test_failure "--condition on-event: missing tracepoint name with --userspace-probe" \ + "Error: Need to provide either a tracepoint name or -a/--all." \ + --condition on-event -k --userspace-probe=${uprobe_elf_binary}:test_function + +test_failure "--condition on-event: extra argument with --userspace-probe" \ + "Error: Unexpected argument: world" \ + --condition on-event -k --userspace-probe=${uprobe_elf_binary}:test_failure hello world + +test_failure "--condition on-event: missing tracepoint name with --syscall" \ + "Error: Need to provide either a tracepoint name or -a/--all." \ + --condition on-event -k --syscall + +test_failure "--condition on-event: extra argument with --syscall" \ + "Error: Unexpected argument: open" \ + --condition on-event -k --syscall open open + +test_failure "--condition on-event: both -a and a tracepoint name with --syscall" \ + "Error: Can't provide a tracepoint name with -a/--all." \ + --condition on-event -k --syscall -a open + +test_failure "--condition on-event --capture: missing argument (end of arg list)" \ + 'Error: While parsing argument #3 (`--capture`): Missing required argument for option `--capture`' \ + --action notify \ + --condition on-event -u -a --capture + +test_failure "--condition on-event --capture: missing argument (before another option)" \ + 'Error: While parsing expression `--action`: Unary operators are not allowed in capture expressions.' \ + --condition on-event -u -a --capture \ + --action notify \ + +test_failure "--condition on-event --capture: binary operator" \ + 'Error: While parsing expression `foo == 2`: Binary operators are not allowed in capture expressions.' \ + --condition on-event -u -a \ + --capture 'foo == 2' --action notify + +test_failure "--condition on-event --capture: unary operator" \ + 'Error: While parsing expression `!foo`: Unary operators are not allowed in capture expressions.' \ + --condition on-event -u -a \ + --capture '!foo' --action notify + +test_failure "--condition on-event --capture: logical operator" \ + 'Error: While parsing expression `foo || bar`: Logical operators are not allowed in capture expressions.' \ + --condition on-event -u -a \ + --capture 'foo || bar' --action notify + +test_failure "--condition on-event --capture: accessing a sub-field" \ + 'Error: While parsing expression `foo.bar`: Capturing subfields is not supported.' \ + --condition on-event -u -a \ + --capture 'foo.bar' --action notify + +test_failure "--condition on-event --capture: accessing the sub-field of an array element" \ + 'Error: While parsing expression `foo[3].bar`: Capturing subfields is not supported.' \ + --condition on-event -u -a \ + --capture 'foo[3].bar' --action notify + +test_failure "--condition on-event --capture: missing colon in app-specific context field" \ + 'Invalid app-specific context field name: missing colon in `foo`.' \ + --condition on-event -u -a \ + --capture '$app.foo' --action notify + +test_failure "--condition on-event --capture: missing colon in app-specific context field" \ + 'Invalid app-specific context field name: missing type name after colon in `foo:`.' \ + --condition on-event -u -a \ + --capture '$app.foo:' --action notify + +# `--action` failures +test_failure "missing args after --action" \ + "Error: Missing action name." \ + --condition on-event -u -a \ + --action + +# `--action notify` failures +test_failure "extra arg after --action notify" \ + "Error: Unexpected argument \`bob\`." \ + --condition on-event -u -a \ + --action notify bob + +# `--action start-session` failures +test_failure "missing arg after --action start-session" \ + "Error: Missing session name." \ + --condition on-event some-event-start-session -u \ + --action start-session +test_failure "extra arg after --action start-session" \ + "Error: Unexpected argument \`bob\`." \ + --condition on-event some-event-start-session -u \ + --action start-session ze-session bob + +# `--action stop-session` failures +test_failure "missing arg after --action stop-session" \ + "Error: Missing session name." \ + --condition on-event some-event-stop-session -u \ + --action stop-session +test_failure "extra arg after --action stop-session" \ + "Error: Unexpected argument \`bob\`." \ + --condition on-event some-event-stop-session -u \ + --action stop-session ze-session bob + +# `--action rotate-session` failures +test_failure "missing arg after --action rotate-session" \ + "Error: Missing session name." \ + --condition on-event some-event-rotate-session -u \ + --action rotate-session +test_failure "extra arg after --action rotate-session" \ + "Error: Unexpected argument \`bob\`." \ + --condition on-event some-event-rotate-session -u \ + --action rotate-session ze-session bob + +# `--action snapshot-session` failures +test_failure "missing arg after --action snapshot-session" \ + "Error: Missing session name." \ + --condition on-event some-event-snapshot-session -u \ + --action snapshot-session +test_failure "extra arg after --action snapshot-session" \ + "Error: Unexpected argument \`bob\`." \ + --condition on-event some-event-snapshot-session -u \ + --action snapshot-session ze-session /dest bob +test_failure "snapshot-session action, --max-size without destination" \ + "Error: Can't provide a snapshot output max size without a snapshot output destination." \ + --condition on-event some-event-snapshot-session -u \ + --action snapshot-session ze-session --max-size 10M +test_failure "snapshot-session action, --name without destination" \ + "Error: Can't provide a snapshot output name without a snapshot output destination." \ + --condition on-event some-event-snapshot-session -u \ + --action snapshot-session ze-session --name hallo + + +# Cleanup +stop_lttng_sessiond_notap +rm -f "${tmp_stdout}" +rm -f "${tmp_stderr}" diff --git a/tests/regression/tools/trigger/test_list_triggers_cli b/tests/regression/tools/trigger/test_list_triggers_cli new file mode 100755 index 000000000..b6ebd297c --- /dev/null +++ b/tests/regression/tools/trigger/test_list_triggers_cli @@ -0,0 +1,335 @@ +#!/bin/bash +# +# Copyright (C) - 2020 EfficiOS, inc +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# Test the `lttng list-trigger` command line interface. + +CURDIR="$(dirname "$0")" +TESTDIR="$CURDIR/../../.." + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" + +plan_tests 45 + +FULL_LTTNG_BIN="${TESTDIR}/../src/bin/lttng/${LTTNG_BIN}" + +tmp_stdout=$(mktemp -t test_list_triggers_cli_stdout.XXXXXX) +tmp_stderr=$(mktemp -t test_list_triggers_cli_stderr.XXXXXX) +tmp_expected_stdout=$(mktemp -t test_list_triggers_cli_expected_stdout.XXXXXX) +uprobe_elf_binary=$(realpath "${TESTDIR}/utils/testapp/userspace-probe-elf-binary/userspace-probe-elf-binary") + +if [ "$(id -u)" == "0" ]; then + ist_root=1 +else + ist_root=0 +fi + +function add_trigger () +{ + "${FULL_LTTNG_BIN}" add-trigger "$@" + ok $? "add trigger \`$*\`: exit code is 0" +} + +function list_triggers () +{ + local test_name="$1" + local expected_stdout_file="$2" + + "${FULL_LTTNG_BIN}" list-triggers > "${tmp_stdout}" 2> "${tmp_stderr}" + ok $? "${test_name}: exit code is 0" + + diff -u "${expected_stdout_file}" "${tmp_stdout}" + ok $? "${test_name}: expected stdout" + + diff -u /dev/null "${tmp_stderr}" + ok $? "${test_name}: expected stderr" +} + +test_top_level_options () +{ + # shellcheck disable=SC2119 + start_lttng_sessiond_notap + + + add_trigger --id hello --condition on-event -u test-id --action notify + add_trigger --fire-once-after 123 --condition on-event -u test-fire-once-after --action notify + add_trigger --fire-every 124 --condition on-event -u test-fire-every --action notify + + cat > "${tmp_expected_stdout}" <<- EOF + - id: T2 + firing policy: once after 123 occurences + condition: event rule hit + rule: test-fire-once-after (type: tracepoint, domain: ust) + actions: + notify + - id: T3 + firing policy: after every 124 occurences + condition: event rule hit + rule: test-fire-every (type: tracepoint, domain: ust) + actions: + notify + - id: hello + condition: event rule hit + rule: test-id (type: tracepoint, domain: ust) + actions: + notify + EOF + + list_triggers "top level options" "${tmp_expected_stdout}" + + stop_lttng_sessiond_notap +} + +test_on_event_tracepoint () +{ + # shellcheck disable=SC2119 + start_lttng_sessiond_notap + + add_trigger --condition on-event -u -a --action notify + add_trigger --id ABC --condition on-event aaa -u --filter 'p == 2' --action notify + add_trigger --condition on-event 'hello*' -u -x 'hello2,hello3,hello4' --action notify + add_trigger --id BCD --condition on-event -u gerboise --loglevel INFO --action notify + add_trigger --condition on-event -u lemming --loglevel-only WARNING --action notify + add_trigger --condition on-event -u capture-payload-field --capture a --action notify + add_trigger --condition on-event -u capture-array --capture 'a[2]' --capture '$ctx.tourlou[18]' --action notify + add_trigger --condition on-event -u capture-chan-ctx --capture '$ctx.vpid' --action notify + add_trigger --condition on-event -u capture-app-ctx --capture '$app.iga:active_clients' --action notify + + + cat > "${tmp_expected_stdout}" <<- EOF + - id: ABC + condition: event rule hit + rule: aaa (type: tracepoint, domain: ust, filter: p == 2) + actions: + notify + - id: BCD + condition: event rule hit + rule: gerboise (type: tracepoint, domain: ust, log level <= TRACE_INFO) + actions: + notify + - id: T1 + condition: event rule hit + rule: * (type: tracepoint, domain: ust) + actions: + notify + - id: T3 + condition: event rule hit + rule: hello* (type: tracepoint, domain: ust, exclusions: hello2,hello3,hello4) + actions: + notify + - id: T5 + condition: event rule hit + rule: lemming (type: tracepoint, domain: ust, log level == TRACE_WARNING) + actions: + notify + - id: T6 + condition: event rule hit + rule: capture-payload-field (type: tracepoint, domain: ust) + captures: + - a + actions: + notify + - id: T7 + condition: event rule hit + rule: capture-array (type: tracepoint, domain: ust) + captures: + - a[2] + - \$ctx.tourlou[18] + actions: + notify + - id: T8 + condition: event rule hit + rule: capture-chan-ctx (type: tracepoint, domain: ust) + captures: + - \$ctx.vpid + actions: + notify + - id: T9 + condition: event rule hit + rule: capture-app-ctx (type: tracepoint, domain: ust) + captures: + - \$app.iga:active_clients + actions: + notify + EOF + + list_triggers "on-event, tracepoint event rule" "${tmp_expected_stdout}" + + stop_lttng_sessiond_notap +} + +test_on_event_probe () +{ + local sys_open_addr + + # shellcheck disable=SC2119 + start_lttng_sessiond_notap + + sys_open_addr=$(grep ' T do_sys_open$' /proc/kallsyms | cut -f 1 -d ' ') + + add_trigger --condition on-event -k --probe=do_sys_open my_sys_open --action notify + add_trigger --condition on-event -k --probe=do_sys_open+10 my_sys_open --action notify + add_trigger --condition on-event -k --probe="${sys_open_addr}" my_sys_open --action notify + add_trigger --condition on-event -k --probe="${sys_open_addr}+10" my_sys_open --action notify + + cat > "${tmp_expected_stdout}" <<- EOF + - id: T1 + condition: event rule hit + rule: my_sys_open (type: probe, location: do_sys_open) + actions: + notify + - id: T2 + condition: event rule hit + rule: my_sys_open (type: probe, location: do_sys_open+10) + actions: + notify + - id: T3 + condition: event rule hit + rule: my_sys_open (type: probe, location: ${sys_open_addr}) + actions: + notify + - id: T4 + condition: event rule hit + rule: my_sys_open (type: probe, location: ${sys_open_addr}+10) + actions: + notify + EOF + + list_triggers "on-event, probe event rule" "${tmp_expected_stdout}" + + stop_lttng_sessiond_notap +} + +test_on_event_userspace_probe () +{ + # shellcheck disable=SC2119 + start_lttng_sessiond_notap + + add_trigger --condition on-event -k --userspace-probe=${uprobe_elf_binary}:test_function ma-probe --action notify + + cat > "${tmp_expected_stdout}" <<- EOF + - id: T1 + condition: event rule hit + rule: ma-probe (type: userspace probe, location: ${uprobe_elf_binary}:test_function) + actions: + notify + EOF + + list_triggers "on-event, userspace-probe event rule" "${tmp_expected_stdout}" + + stop_lttng_sessiond_notap +} + +test_on_event_syscall () +{ + # shellcheck disable=SC2119 + start_lttng_sessiond_notap + + add_trigger --condition on-event -k --syscall open --action notify + add_trigger --condition on-event -k --syscall ptrace --filter 'a > 2' --action notify + + cat > "${tmp_expected_stdout}" <<- EOF + - id: T1 + condition: event rule hit + - rule: open (type: syscall) + actions: + notify + - id: T2 + condition: event rule hit + - rule: ptrace (type: syscall, filter: a > 2) + actions: + notify + EOF + + list_triggers "on-event, syscall event rule" "${tmp_expected_stdout}" + + stop_lttng_sessiond_notap +} + +test_snapshot_action () +{ + start_lttng_sessiond_notap + + add_trigger --condition on-event -u some-event --action snapshot-session ze-session + add_trigger --condition on-event -u some-event --action snapshot-session ze-session /some/path + add_trigger --condition on-event -u some-event --action snapshot-session ze-session file:///some/other/path + add_trigger --condition on-event -u some-event --action snapshot-session ze-session net://1.2.3.4 + add_trigger --condition on-event -u some-event --action snapshot-session ze-session net://1.2.3.4:1234:1235 + add_trigger --condition on-event -u some-event --action snapshot-session ze-session --ctrl-url=tcp://1.2.3.4:1111 --data-url=tcp://1.2.3.4:1112 + add_trigger --condition on-event -u some-event --action snapshot-session ze-session /some/path --max-size=1234 + add_trigger --condition on-event -u some-event --action snapshot-session ze-session /some/path --name=meh + + + cat > "${tmp_expected_stdout}" <<- EOF + - id: T1 + condition: event rule hit + rule: some-event (type: tracepoint, domain: ust) + actions: + snapshot session \`ze-session\` + - id: T2 + condition: event rule hit + rule: some-event (type: tracepoint, domain: ust) + actions: + snapshot session \`ze-session\`, path: /some/path + - id: T3 + condition: event rule hit + rule: some-event (type: tracepoint, domain: ust) + actions: + snapshot session \`ze-session\`, path: /some/other/path + - id: T4 + condition: event rule hit + rule: some-event (type: tracepoint, domain: ust) + actions: + snapshot session \`ze-session\`, url: net://1.2.3.4 + - id: T5 + condition: event rule hit + rule: some-event (type: tracepoint, domain: ust) + actions: + snapshot session \`ze-session\`, url: net://1.2.3.4:1234:1235 + - id: T6 + condition: event rule hit + rule: some-event (type: tracepoint, domain: ust) + actions: + snapshot session \`ze-session\`, control url: tcp://1.2.3.4:1111, data url: tcp://1.2.3.4:1112 + - id: T7 + condition: event rule hit + rule: some-event (type: tracepoint, domain: ust) + actions: + snapshot session \`ze-session\`, path: /some/path, max size: 1234 + - id: T8 + condition: event rule hit + rule: some-event (type: tracepoint, domain: ust) + actions: + snapshot session \`ze-session\`, path: /some/path, name: meh + EOF + + list_triggers "snapshot action" "${tmp_expected_stdout}" + + stop_lttng_sessiond_notap +} + +test_top_level_options +test_on_event_tracepoint +skip $ist_root "non-root user: skipping kprobe tests" 7 || test_on_event_probe +skip $ist_root "non-root user: skipping uprobe tests" 4 || test_on_event_userspace_probe +skip $ist_root "non-root user: skipping syscall tests" 5 || test_on_event_syscall +test_snapshot_action + +# Cleanup +rm -f "${tmp_stdout}" +rm -f "${tmp_stderr}" +rm -f "${tmp_expected_stdout}" diff --git a/tests/regression/tools/trigger/test_remove_trigger_cli b/tests/regression/tools/trigger/test_remove_trigger_cli new file mode 100755 index 000000000..4b3cce50d --- /dev/null +++ b/tests/regression/tools/trigger/test_remove_trigger_cli @@ -0,0 +1,110 @@ +#!/bin/bash +# +# Copyright (C) - 2020 EfficiOS, inc +# +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +# Test the `lttng remove-trigger` command line interface. + +CURDIR="$(dirname "$0")" +TESTDIR="$CURDIR/../../.." + +# shellcheck source=../../../utils/utils.sh +source "$TESTDIR/utils/utils.sh" + +plan_tests 17 + +FULL_LTTNG_BIN="${TESTDIR}/../src/bin/lttng/${LTTNG_BIN}" + +tmp_stdout=$(mktemp -t test_list_triggers_cli_stdout.XXXXXX) +tmp_stderr=$(mktemp -t test_list_triggers_cli_stderr.XXXXXX) +tmp_expected_stdout=$(mktemp -t test_list_triggers_cli_expected_stdout.XXXXXX) + +function add_trigger () +{ + "${FULL_LTTNG_BIN}" add-trigger "$@" + ok $? "add trigger \`$*\`: exit code is 0" +} + +function list_triggers () +{ + local test_name="$1" + local expected_stdout_file="$2" + + "${FULL_LTTNG_BIN}" list-triggers > "${tmp_stdout}" 2> "${tmp_stderr}" + ok $? "${test_name}: exit code is 0" + + diff -u "${expected_stdout_file}" "${tmp_stdout}" + ok $? "${test_name}: expected stdout" + + diff -u /dev/null "${tmp_stderr}" + ok $? "${test_name}: expected stderr" +} + +function remove_trigger () +{ + local id="$1" + local test_name="remove trigger ${id}" + + "${FULL_LTTNG_BIN}" remove-trigger "${id}" > "${tmp_stdout}" 2> "${tmp_stderr}" + ok $? "${test_name}: exit code is 0" + + diff -u <(echo "Removed trigger \`${id}\`.") "${tmp_stdout}" + ok $? "${test_name}: expected stdout" + + diff -u /dev/null "${tmp_stderr}" + ok $? "${test_name}: expected stderr" +} + +# shellcheck disable=SC2119 +start_lttng_sessiond_notap + +# Add a few triggers +add_trigger --condition on-event -u -a --action notify +add_trigger --id ABC --condition on-event aaa -u --filter 'p == 2' --action notify + +cat > "${tmp_expected_stdout}" <<- EOF +- id: ABC + condition: event rule hit + rule: aaa (type: tracepoint, domain: ust, filter: p == 2) + actions: + notify +- id: T1 + condition: event rule hit + rule: * (type: tracepoint, domain: ust) + actions: + notify +EOF +list_triggers "two triggers left" "${tmp_expected_stdout}" + +remove_trigger "ABC" + +cat > "${tmp_expected_stdout}" <<- EOF +- id: T1 + condition: event rule hit + rule: * (type: tracepoint, domain: ust) + actions: + notify +EOF +list_triggers "one trigger left" "${tmp_expected_stdout}" + +remove_trigger "T1" + +list_triggers "no triggers left" "/dev/null" + +# Cleanup +stop_lttng_sessiond_notap +rm -f "${tmp_stdout}" +rm -f "${tmp_stderr}" +rm -f "${tmp_expected_stdout}" diff --git a/tests/regression/tools/notification/test_notification_kernel b/tests/regression/tools/trigger/test_trigger_kernel similarity index 72% rename from tests/regression/tools/notification/test_notification_kernel rename to tests/regression/tools/trigger/test_trigger_kernel index 1eb0d1067..cc6fc5816 100755 --- a/tests/regression/tools/notification/test_notification_kernel +++ b/tests/regression/tools/trigger/test_trigger_kernel @@ -1,8 +1,19 @@ #!/bin/bash # -# Copyright (C) 2017 Jonathan Rajotte +# Copyright (C) - 2017 Jonathan Rajotte-Julien # -# SPDX-License-Identifier: LGPL-2.1-only +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA CURDIR=$(dirname $0)/ TESTDIR=$CURDIR/../../../ @@ -12,6 +23,7 @@ TMPDIR=$(mktemp -d) #This is needed since the testpoint create a pipe with the consumerd type suffixed TESTPOINT_BASE_PATH=$(readlink -f "$TMPDIR/lttng.t_p_n") TESTPOINT_PIPE_PATH=$(mktemp -u "${TESTPOINT_BASE_PATH}.XXXXXX") +TESTPOIT_ARGS="CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} LTTNG_TESTPOINT_ENABLE=1" TESTPOINT=$(readlink -f ${CURDIR}/.libs/libpause_consumer.so) @@ -34,7 +46,7 @@ NUM_TESTS=104 source $TESTDIR/utils/utils.sh -function kernel_event_generator_toggle_state +function kernel_event_generator_toogle_state { kernel_event_generator_suspended=$((kernel_event_generator_suspended==0)) @@ -43,7 +55,7 @@ function kernel_event_generator { state_file=$1 kernel_event_generator_suspended=0 - trap kernel_event_generator_toggle_state SIGUSR1 + trap kernel_event_generator_toogle_state SIGUSR1 while (true); do if [[ $kernel_event_generator_suspended -eq "1" ]]; then diff --git a/tests/regression/tools/notification/test_notification_ust b/tests/regression/tools/trigger/test_trigger_ust similarity index 67% rename from tests/regression/tools/notification/test_notification_ust rename to tests/regression/tools/trigger/test_trigger_ust index 57ae52cbd..82f79a8e6 100755 --- a/tests/regression/tools/notification/test_notification_ust +++ b/tests/regression/tools/trigger/test_trigger_ust @@ -1,8 +1,19 @@ #!/bin/bash # -# Copyright (C) 2017 Jonathan Rajotte +# Copyright (C) - 2017 Jonathan Rajotte-Julien # -# SPDX-License-Identifier: LGPL-2.1-only +# This library is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; version 2.1 of the License. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA CURDIR=$(dirname $0)/ TESTDIR=$CURDIR/../../../ @@ -12,6 +23,7 @@ TMPDIR=$(mktemp -d) #This is needed since the testpoint create a pipe with the consumerd type suffixed TESTPOINT_BASE_PATH=$(readlink -f "$TMPDIR/lttng.t_p_n") TESTPOINT_PIPE_PATH=$(mktemp -u "${TESTPOINT_BASE_PATH}.XXXXXX") +TESTPOIT_ARGS="CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} LTTNG_TESTPOINT_ENABLE=1" TESTPOINT=$(readlink -f ${CURDIR}/.libs/libpause_consumer.so) @@ -34,7 +46,7 @@ DIR=$(readlink -f $TESTDIR) source $TESTDIR/utils/utils.sh -function ust_event_generator_toggle_state +function ust_event_generator_toogle_state { ust_event_generator_suspended=$((ust_event_generator_suspended==0)) @@ -43,7 +55,7 @@ function ust_event_generator { state_file=$1 ust_event_generator_suspended=0 - trap ust_event_generator_toggle_state SIGUSR1 + trap ust_event_generator_toogle_state SIGUSR1 while (true); do if [[ $ust_event_generator_suspended -eq "1" ]]; then @@ -53,7 +65,7 @@ function ust_event_generator if [[ -f $state_file ]]; then rm -rf $state_file 2> /dev/null fi - taskset -c 0 $TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT > /dev/null 2>&1 + taskset -c 0 $TESTAPP_BIN $NR_ITER $NR_USEC_WAIT > /dev/null 2>&1 fi done } diff --git a/tests/regression/tools/trigger/trigger.c b/tests/regression/tools/trigger/trigger.c new file mode 100644 index 000000000..88ef99d48 --- /dev/null +++ b/tests/regression/tools/trigger/trigger.c @@ -0,0 +1,729 @@ +/* + * notification.c + * + * Tests suite for LTTng notification API + * + * Copyright (C) 2017 Jonathan Rajotte + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NUM_TESTS 104 + +int nb_args = 0; +int named_pipe_args_start = 0; +pid_t app_pid = -1; +const char *app_state_file = NULL; + +static +void wait_on_file(const char *path, bool file_exist) +{ + if (!path) { + return; + } + for (;;) { + int ret; + struct stat buf; + + ret = stat(path, &buf); + if (ret == -1 && errno == ENOENT) { + if (file_exist) { + (void) poll(NULL, 0, 10); /* 10 ms delay */ + continue; /* retry */ + } + break; /* File does not exist */ + } + if (ret) { + perror("stat"); + exit(EXIT_FAILURE); + } + break; /* found */ + } +} + +int write_pipe(const char *path, uint8_t data) +{ + int ret = 0; + int fd = 0; + + fd = open(path, O_WRONLY | O_NONBLOCK); + if (fd < 0) { + perror("Could not open consumer control named pipe"); + goto end; + } + + ret = write(fd, &data , sizeof(data)); + if (ret < 1) { + perror("Named pipe write failed"); + if (close(fd)) { + perror("Named pipe close failed"); + } + ret = -1; + goto end; + } + + ret = close(fd); + if (ret < 0) { + perror("Name pipe closing failed"); + ret = -1; + goto end; + } +end: + return ret; +} + +int stop_consumer(const char **argv) +{ + int ret = 0; + for (int i = named_pipe_args_start; i < nb_args; i++) { + ret = write_pipe(argv[i], 49); + } + return ret; +} + +int resume_consumer(const char **argv) +{ + int ret = 0; + for (int i = named_pipe_args_start; i < nb_args; i++) { + ret = write_pipe(argv[i], 0); + } + return ret; +} + +int suspend_application() +{ + int ret; + struct stat buf; + + if (!stat(app_state_file, &buf)) { + fail("App is already in a suspended state."); + ret = -1; + goto error; + } + + /* + * Send SIGUSR1 to application instructing it to bypass tracepoint. + */ + ret = kill(app_pid, SIGUSR1); + if (ret) { + fail("SIGUSR1 failed. errno %d", errno); + ret = -1; + goto error; + } + + wait_on_file(app_state_file, true); + +error: + return ret; + +} + +int resume_application() +{ + int ret; + struct stat buf; + + ret = stat(app_state_file, &buf); + if (ret == -1 && errno == ENOENT) { + fail("State file does not exist"); + goto error; + } + if (ret) { + perror("stat"); + goto error; + } + + ret = kill(app_pid, SIGUSR1); + if (ret) { + fail("SIGUSR1 failed. errno %d", errno); + ret = -1; + goto error; + } + + wait_on_file(app_state_file, false); + +error: + return ret; + +} + + +void test_triggers_buffer_usage_condition(const char *session_name, + const char *channel_name, + enum lttng_domain_type domain_type, + enum lttng_condition_type condition_type) +{ + enum lttng_condition_status condition_status; + struct lttng_action *action; + + /* Set-up */ + action = lttng_action_notify_create(); + if (!action) { + fail("Setup error on action creation"); + goto end; + } + + /* Test lttng_register_trigger with null value */ + ok(lttng_register_trigger(NULL) == -LTTNG_ERR_INVALID, "Registering a NULL trigger fails as expected"); + + /* Test: register a trigger */ + unsigned int test_vector_size = 5; + for (unsigned int i = 0; i < pow(2,test_vector_size); i++) { + int loop_ret = 0; + char *test_tuple_string = NULL; + unsigned int mask_position = 0; + bool session_name_set = false; + bool channel_name_set = false; + bool threshold_ratio_set = false; + bool threshold_byte_set = false; + bool domain_type_set = false; + + struct lttng_trigger *trigger = NULL; + struct lttng_condition *condition = NULL; + + /* Create base condition */ + switch (condition_type) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + condition = lttng_condition_buffer_usage_low_create(); + break; + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + condition = lttng_condition_buffer_usage_high_create(); + break; + default: + loop_ret = 1; + goto loop_end; + } + + if (!condition) { + loop_ret = 1; + goto loop_end; + + } + + /* Prepare the condition for trigger registration test */ + + /* Set session name */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_session_name( + condition, session_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + session_name_set = true; + } + mask_position++; + + /* Set channel name */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_channel_name( + condition, channel_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + channel_name_set = true; + } + mask_position++; + + /* Set threshold ratio */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_threshold_ratio( + condition, 0.0); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + threshold_ratio_set = true; + } + mask_position++; + + /* Set threshold byte */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_threshold( + condition, 0); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + threshold_byte_set = true; + } + mask_position++; + + /* Set domain type */ + if ((1 << mask_position) & i) { + condition_status = lttng_condition_buffer_usage_set_domain_type( + condition, LTTNG_DOMAIN_UST); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + loop_ret = 1; + goto loop_end; + } + domain_type_set = true; + } + + /* Safety check */ + if (mask_position != test_vector_size -1) { + assert("Logic error for test vector generation"); + } + + loop_ret = asprintf(&test_tuple_string, "session name %s, channel name %s, threshold ratio %s, threshold byte %s, domain type %s", + session_name_set ? "set" : "unset", + channel_name_set ? "set" : "unset", + threshold_ratio_set ? "set" : "unset", + threshold_byte_set ? "set" : "unset", + domain_type_set? "set" : "unset"); + if (!test_tuple_string || loop_ret < 0) { + loop_ret = 1; + goto loop_end; + } + + /* Create trigger */ + trigger = lttng_trigger_create(condition, action); + if (!trigger) { + loop_ret = 1; + goto loop_end; + } + + loop_ret = lttng_register_trigger(trigger); + +loop_end: + if (loop_ret == 1) { + fail("Setup error occurred for tuple: %s", test_tuple_string); + goto loop_cleanup; + } + + /* This combination happens three times */ + if (session_name_set && channel_name_set + && (threshold_ratio_set || threshold_byte_set) + && domain_type_set) { + ok(loop_ret == 0, "Trigger is registered: %s", test_tuple_string); + + /* + * Test that a trigger cannot be registered + * multiple time. + */ + loop_ret = lttng_register_trigger(trigger); + ok(loop_ret == -LTTNG_ERR_TRIGGER_EXISTS, "Re-register trigger fails as expected: %s", test_tuple_string); + + /* Test that a trigger can be unregistered */ + loop_ret = lttng_unregister_trigger(trigger); + ok(loop_ret == 0, "Unregister trigger: %s", test_tuple_string); + + /* + * Test that unregistration of a non-previously + * registered trigger fail. + */ + loop_ret = lttng_unregister_trigger(trigger); + ok(loop_ret == -LTTNG_ERR_TRIGGER_NOT_FOUND, "Unregister of a non-registerd trigger fails as expected: %s", test_tuple_string); + } else { + ok(loop_ret == -LTTNG_ERR_INVALID_TRIGGER, "Trigger is invalid as expected and cannot be registered: %s", test_tuple_string); + } + +loop_cleanup: + free(test_tuple_string); + lttng_trigger_destroy(trigger); + lttng_condition_destroy(condition); + } + +end: + lttng_action_destroy(action); +} + +static +void wait_data_pending(const char *session_name) +{ + int ret; + + do { + ret = lttng_data_pending(session_name); + assert(ret >= 0); + } while (ret != 0); +} + +void test_notification_channel(const char *session_name, const char *channel_name, const enum lttng_domain_type domain_type, const char **argv) +{ + int ret = 0; + enum lttng_condition_status condition_status; + enum lttng_notification_channel_status nc_status; + + struct lttng_action *action = NULL; + struct lttng_notification *notification = NULL; + struct lttng_notification_channel *notification_channel = NULL; + struct lttng_trigger *trigger = NULL; + + struct lttng_condition *low_condition = NULL; + struct lttng_condition *high_condition = NULL; + struct lttng_condition *dummy_invalid_condition = NULL; + struct lttng_condition *dummy_condition = NULL; + + double low_ratio = 0.0; + double high_ratio = 0.99; + + /* Set-up */ + action = lttng_action_notify_create(); + if (!action) { + fail("Setup error on action creation"); + goto end; + } + + /* Create a dummy, empty condition for later test */ + dummy_invalid_condition = lttng_condition_buffer_usage_low_create(); + if (!dummy_invalid_condition) { + fail("Setup error on condition creation"); + goto end; + } + + /* Create a valid dummy condition with a ratio of 0.5 */ + dummy_condition = lttng_condition_buffer_usage_low_create(); + if (!dummy_condition) { + fail("Setup error on dummy_condition creation"); + goto end; + + } + condition_status = lttng_condition_buffer_usage_set_threshold_ratio( + dummy_condition, 0.5); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on condition creation"); + goto end; + } + + condition_status = lttng_condition_buffer_usage_set_session_name( + dummy_condition, session_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on dummy_condition creation"); + goto end; + } + condition_status = lttng_condition_buffer_usage_set_channel_name( + dummy_condition, channel_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on dummy_condition creation"); + goto end; + } + condition_status = lttng_condition_buffer_usage_set_domain_type( + dummy_condition, domain_type); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on dummy_condition creation"); + goto end; + } + + /* Register a low condition with a ratio */ + low_condition = lttng_condition_buffer_usage_low_create(); + if (!low_condition) { + fail("Setup error on low_condition creation"); + goto end; + } + condition_status = lttng_condition_buffer_usage_set_threshold_ratio( + low_condition, low_ratio); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on low_condition creation"); + goto end; + } + + condition_status = lttng_condition_buffer_usage_set_session_name( + low_condition, session_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on low_condition creation"); + goto end; + } + condition_status = lttng_condition_buffer_usage_set_channel_name( + low_condition, channel_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on low_condition creation"); + goto end; + } + condition_status = lttng_condition_buffer_usage_set_domain_type( + low_condition, domain_type); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on low_condition creation"); + goto end; + + } + + /* Register a high condition with a ratio */ + high_condition = lttng_condition_buffer_usage_high_create(); + if (!high_condition) { + fail("Setup error on high_condition creation"); + goto end; + } + + condition_status = lttng_condition_buffer_usage_set_threshold_ratio( + high_condition, high_ratio); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on high_condition creation"); + goto end; + } + + condition_status = lttng_condition_buffer_usage_set_session_name( + high_condition, session_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on high_condition creation"); + goto end; + } + condition_status = lttng_condition_buffer_usage_set_channel_name( + high_condition, channel_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on high_condition creation"); + goto end; + } + condition_status = lttng_condition_buffer_usage_set_domain_type( + high_condition, domain_type); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fail("Setup error on high_condition creation"); + goto end; + } + + /* Register the triggers for low and high condition */ + trigger = lttng_trigger_create(low_condition, action); + if (!trigger) { + fail("Setup error on low trigger creation"); + goto end; + } + + ret = lttng_register_trigger(trigger); + if (ret) { + fail("Setup error on low trigger registration"); + goto end; + } + + lttng_trigger_destroy(trigger); + trigger = NULL; + + trigger = lttng_trigger_create(high_condition, action); + if (!trigger) { + fail("Setup error on high trigger creation"); + goto end; + } + + ret = lttng_register_trigger(trigger); + if (ret) { + fail("Setup error on high trigger registration"); + goto end; + } + + /* Begin testing */ + notification_channel = lttng_notification_channel_create(lttng_session_daemon_notification_endpoint); + ok(notification_channel, "Notification channel object creation"); + if (!notification_channel) { + goto end; + } + + /* Basic error path check */ + nc_status = lttng_notification_channel_subscribe(NULL, NULL); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Notification channel subscription is invalid: NULL, NULL"); + + nc_status = lttng_notification_channel_subscribe(notification_channel, NULL); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Notification channel subscription is invalid: NON-NULL, NULL"); + + nc_status = lttng_notification_channel_subscribe(NULL, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Notification channel subscription is invalid: NULL, NON-NULL"); + + nc_status = lttng_notification_channel_subscribe(notification_channel, dummy_invalid_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Subscribing to an invalid condition"); + + nc_status = lttng_notification_channel_unsubscribe(notification_channel, dummy_invalid_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_INVALID, "Unsubscribing from an invalid condition"); + + nc_status = lttng_notification_channel_unsubscribe(notification_channel, dummy_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_UNKNOWN_CONDITION, "Unsubscribing from a valid unknown condition"); + + /* Subscribe a valid low condition */ + nc_status = lttng_notification_channel_subscribe(notification_channel, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Subscribe to condition"); + + /* Subscribe a valid high condition */ + nc_status = lttng_notification_channel_subscribe(notification_channel, high_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Subscribe to condition"); + + nc_status = lttng_notification_channel_subscribe(notification_channel, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_ALREADY_SUBSCRIBED, "Subscribe to a condition for which subscription was already done"); + + nc_status = lttng_notification_channel_subscribe(notification_channel, high_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_ALREADY_SUBSCRIBED, "Subscribe to a condition for which subscription was already done"); + + /* Wait for notification to happen */ + stop_consumer(argv); + lttng_start_tracing(session_name); + + /* Wait for high notification */ + nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK + && notification + && lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH, + "High notification received after intermediary communication"); + lttng_notification_destroy(notification); + notification = NULL; + + suspend_application(); + lttng_stop_tracing_no_wait(session_name); + resume_consumer(argv); + wait_data_pending(session_name); + + /* + * Test that communication still work even if there is notification + * waiting for consumption. + */ + + nc_status = lttng_notification_channel_unsubscribe(notification_channel, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Unsubscribe with pending notification"); + + nc_status = lttng_notification_channel_subscribe(notification_channel, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "subscribe with pending notification"); + + nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK + && notification + && lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW, + "Low notification received after intermediary communication"); + lttng_notification_destroy(notification); + notification = NULL; + + /* Stop consumer to force a high notification */ + stop_consumer(argv); + resume_application(); + lttng_start_tracing(session_name); + + nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && + lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH, + "High notification received after intermediary communication"); + lttng_notification_destroy(notification); + notification = NULL; + + suspend_application(); + lttng_stop_tracing_no_wait(session_name); + resume_consumer(argv); + wait_data_pending(session_name); + + nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && + lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW, + "Low notification received after re-subscription"); + lttng_notification_destroy(notification); + notification = NULL; + + stop_consumer(argv); + resume_application(); + /* Stop consumer to force a high notification */ + lttng_start_tracing(session_name); + + nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK && notification && + lttng_condition_get_type(lttng_notification_get_condition(notification)) == LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH, + "High notification"); + lttng_notification_destroy(notification); + notification = NULL; + + /* Resume consumer to allow event consumption */ + suspend_application(); + lttng_stop_tracing_no_wait(session_name); + resume_consumer(argv); + wait_data_pending(session_name); + + nc_status = lttng_notification_channel_unsubscribe(notification_channel, low_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Unsubscribe low condition with pending notification"); + nc_status = lttng_notification_channel_unsubscribe(notification_channel, high_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, "Unsubscribe high condition with pending notification"); + +end: + lttng_notification_channel_destroy(notification_channel); + lttng_trigger_destroy(trigger); + lttng_action_destroy(action); + lttng_condition_destroy(low_condition); + lttng_condition_destroy(high_condition); + lttng_condition_destroy(dummy_invalid_condition); + lttng_condition_destroy(dummy_condition); +} + +int main(int argc, const char *argv[]) +{ + const char *session_name = NULL; + const char *channel_name = NULL; + const char *domain_type_string = NULL; + enum lttng_domain_type domain_type = LTTNG_DOMAIN_NONE; + + plan_tests(NUM_TESTS); + + /* Argument 6 and upward are named pipe location for consumerd control */ + named_pipe_args_start = 6; + + if (argc < 7) { + fail("Missing parameter for tests to run %d", argc); + goto error; + } + + nb_args = argc; + + domain_type_string = argv[1]; + session_name = argv[2]; + channel_name = argv[3]; + app_pid = (pid_t) atoi(argv[4]); + app_state_file = argv[5]; + + if (!strcmp("LTTNG_DOMAIN_UST", domain_type_string)) { + domain_type = LTTNG_DOMAIN_UST; + } + if (!strcmp("LTTNG_DOMAIN_KERNEL", domain_type_string)) { + domain_type = LTTNG_DOMAIN_KERNEL; + } + if (domain_type == LTTNG_DOMAIN_NONE) { + fail("Unknown domain type"); + goto error; + } + + diag("Test trigger for domain %s with buffer_usage_low condition", domain_type_string); + test_triggers_buffer_usage_condition(session_name, channel_name, domain_type, LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW); + diag("Test trigger for domain %s with buffer_usage_high condition", domain_type_string); + test_triggers_buffer_usage_condition(session_name, channel_name, domain_type, LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH); + + diag("Test notification channel api for domain %s", domain_type_string); + test_notification_channel(session_name, channel_name, domain_type, argv); +error: + return exit_status(); +} + diff --git a/tests/regression/tools/trigger/utils/Makefile.am b/tests/regression/tools/trigger/utils/Makefile.am new file mode 100644 index 000000000..c27953043 --- /dev/null +++ b/tests/regression/tools/trigger/utils/Makefile.am @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only + +AM_CPPFLAGS += -I$(srcdir) -I$(top_srcdir)/tests/utils +LIBLTTNG_CTL=$(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la + +noinst_PROGRAMS = notification-client +notification_client_SOURCES = notification-client.c +notification_client_LDADD = $(UST_LIBS) $(DL_LIBS) $(LIBLTTNG_CTL) \ + $(top_builddir)/tests/utils/libtestutils.la diff --git a/tests/regression/tools/trigger/utils/notification-client.c b/tests/regression/tools/trigger/utils/notification-client.c new file mode 100644 index 000000000..e976aac3b --- /dev/null +++ b/tests/regression/tools/trigger/utils/notification-client.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "utils.h" + +static struct option long_options[] = +{ + /* These options set a flag. */ + {"trigger", required_argument, 0, 'i'}, + {"sync-after-notif-register", required_argument, 0, 'a'}, + {0, 0, 0, 0} +}; + +static bool action_group_contains_notify( + const struct lttng_action *action_group) +{ + unsigned int i, count; + enum lttng_action_status status = + lttng_action_group_get_count(action_group, &count); + + if (status != LTTNG_ACTION_STATUS_OK) { + printf("Failed to get action count from action group\n"); + exit(1); + } + + for (i = 0; i < count; i++) { + const struct lttng_action *action = + lttng_action_group_get_at_index_const( + action_group, i); + const enum lttng_action_type action_type = + lttng_action_get_type(action); + + if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { + return true; + } + } + return false; +} + +static bool is_expected_trigger_name(const char *expected_trigger_name, + struct lttng_notification *notification) +{ + int ret = false; + const struct lttng_evaluation *evaluation = + lttng_notification_get_evaluation(notification); + const enum lttng_condition_type type = + lttng_evaluation_get_type(evaluation); + + switch (type) { + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: + break; + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + { + const char *trigger_name; + enum lttng_evaluation_status evaluation_status; + + evaluation_status = + lttng_evaluation_event_rule_get_trigger_name( + evaluation, &trigger_name); + if (evaluation_status != LTTNG_EVALUATION_STATUS_OK) { + fprintf(stderr, "Failed to get trigger name of event rule notification\n"); + ret = -1; + break; + } + + ret = true; + break; + } + default: + fprintf(stderr, "Unknown notification type (%d)\n", type); + } + + return ret; +} + +int main(int argc, char **argv) +{ + int ret; + int option; + int option_index; + const char *expected_trigger_name = NULL; + struct lttng_triggers *triggers = NULL; + unsigned int count, i, subcription_count = 0; + enum lttng_trigger_status trigger_status; + char *after_notif_register_file_path = NULL; + struct lttng_notification_channel *notification_channel = NULL; + + while ((option = getopt_long(argc, argv, "a:t:", + long_options, &option_index)) != -1) { + switch (option) { + case 'a': + after_notif_register_file_path = strdup(optarg); + break; + case 't': + expected_trigger_name = strdup(optarg); + break; + case '?': + /* getopt_long already printed an error message. */ + default: + ret = -1; + goto end; + } + } + + if (optind != argc) { + ret = -1; + goto end; + } + + + notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + if (!notification_channel) { + fprintf(stderr, "Failed to create notification channel\n"); + ret = -1; + goto end; + } + + ret = lttng_list_triggers(&triggers); + if (ret) { + fprintf(stderr, "Failed to list triggers\n"); + goto end; + } + + trigger_status = lttng_triggers_get_count(triggers, &count); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + fprintf(stderr, "Failed to get trigger count\n"); + ret = -1; + goto end; + } + + for (i = 0; i < count; i++) { + const struct lttng_trigger *trigger = + lttng_triggers_get_at_index(triggers, i); + const struct lttng_condition *condition = + lttng_trigger_get_const_condition(trigger); + const struct lttng_action *action = + lttng_trigger_get_const_action(trigger); + const enum lttng_action_type action_type = + lttng_action_get_type(action); + enum lttng_notification_channel_status channel_status; + const char *trigger_name = NULL; + + lttng_trigger_get_name(trigger, &trigger_name); + if (strcmp(trigger_name, expected_trigger_name)) { + continue; + } + + if (!((action_type == LTTNG_ACTION_TYPE_GROUP && + action_group_contains_notify(action)) || + action_type == LTTNG_ACTION_TYPE_NOTIFY)) { + /* "The action of trigger is not notify, skipping. */ + continue; + } + + channel_status = lttng_notification_channel_subscribe( + notification_channel, condition); + if (channel_status) { + fprintf(stderr, "Failed to subscribe to notifications of trigger \"%s\"\n", + trigger_name); + ret = -1; + goto end; + } + + subcription_count++; + } + + if (subcription_count == 0) { + printf("No matching trigger with a notify action found.\n"); + ret = 0; + goto end; + } + + + /* + * We registered to the notification of our target trigger. We can now + * create the sync file to signify that we are ready. + */ + ret = create_file(after_notif_register_file_path); + if (ret != 0) { + goto end; + } + + for (;;) { + struct lttng_notification *notification; + enum lttng_notification_channel_status channel_status; + + channel_status = + lttng_notification_channel_get_next_notification( + notification_channel, + ¬ification); + switch (channel_status) { + case LTTNG_NOTIFICATION_CHANNEL_STATUS_NOTIFICATIONS_DROPPED: + printf("Dropped notification\n"); + break; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_INTERRUPTED: + ret = 0; + goto end; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_OK: + break; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_CLOSED: + printf("Notification channel was closed by peer.\n"); + break; + default: + fprintf(stderr, "A communication error occurred on the notification channel.\n"); + ret = -1; + goto end; + } + + ret = is_expected_trigger_name(expected_trigger_name, + notification); + lttng_notification_destroy(notification); + if (ret) { + ret = 0; + goto end; + } + } +end: + lttng_triggers_destroy(triggers); + lttng_notification_channel_destroy(notification_channel); + return !!ret; +} diff --git a/tests/regression/tools/working-directory/test_relayd_working_directory b/tests/regression/tools/working-directory/test_relayd_working_directory index 0a5bad027..24b87a9cc 100755 --- a/tests/regression/tools/working-directory/test_relayd_working_directory +++ b/tests/regression/tools/working-directory/test_relayd_working_directory @@ -67,7 +67,7 @@ function test_relayd_daemon() start_lttng_relayd_opt 1 "-d" "--working-directory $working_dir" - pid=$(pgrep "$RELAYD_MATCH") + pid=$(lttng_pgrep "$RELAYD_MATCH") ok $? "Found lttng-relayd" cwd=$(readlink "/proc/${pid}/cwd") @@ -88,7 +88,7 @@ function test_relayd_daemon_no_working_dir() start_lttng_relayd_opt 1 "-d" "" - pid=$(pgrep "$RELAYD_MATCH") + pid=$(lttng_pgrep "$RELAYD_MATCH") ok $? "Found lttng-relayd" cwd=$(readlink "/proc/${pid}/cwd") @@ -111,7 +111,7 @@ function test_relayd_background() start_lttng_relayd_opt 1 "-b" "--working-directory $working_dir" - pid=$(pgrep "$RELAYD_MATCH") + pid=$(lttng_pgrep "$RELAYD_MATCH") ok $? "Found lttng-relayd" cwd=$(readlink "/proc/${pid}/cwd") @@ -132,7 +132,7 @@ function test_relayd_background_no_working_dir() start_lttng_relayd_opt 1 "-b" "" - pid=$(pgrep "$RELAYD_MATCH") + pid=$(lttng_pgrep "$RELAYD_MATCH") ok $? "Found lttng-relayd" cwd=$(readlink "/proc/${pid}/cwd") @@ -172,7 +172,7 @@ function test_relayd_debug_permission() ERROR_OUTPUT_DEST=$(mktemp) start_lttng_relayd_opt 1 "-b" "-v --working-dir $working_dir" - pid=$(pgrep "$RELAYD_MATCH") + pid=$(lttng_pgrep "$RELAYD_MATCH") ok $? "Found lttng-relayd" cwd=$(readlink "/proc/${pid}/cwd") @@ -208,7 +208,7 @@ function test_relayd_failure() test $? -eq "1" ok $? "Expect failure to start lttng-relayd for non-existent working directory" - pid=$(pgrep "$RELAYD_MATCH") + pid=$(lttng_pgrep "$RELAYD_MATCH") if [ -z "$pid" ]; then pass "No lttng-relayd present" else @@ -237,7 +237,7 @@ function test_relayd_env() export LTTNG_RELAYD_WORKING_DIRECTORY=${working_dir} start_lttng_relayd_opt 1 "-b" "" - pid=$(pgrep "$RELAYD_MATCH") + pid=$(lttng_pgrep "$RELAYD_MATCH") ok $? "Found lttng-relayd" cwd=$(readlink "/proc/$pid/cwd") @@ -264,7 +264,7 @@ function test_relayd_cmdline_overwrite_env() export LTTNG_RELAYD_WORKING_DIRECTORY=${working_dir_env} start_lttng_relayd_opt 1 "-b" "--working-dir ${working_dir_cmdline}" - pid=$(pgrep "$RELAYD_MATCH") + pid=$(lttng_pgrep "$RELAYD_MATCH") ok $? "Found lttng-relayd" cwd=$(readlink "/proc/$pid/cwd") diff --git a/tests/stress/test_multi_sessions_per_uid_10app b/tests/stress/test_multi_sessions_per_uid_10app index 4d4a78241..4aaf1d91a 100755 --- a/tests/stress/test_multi_sessions_per_uid_10app +++ b/tests/stress/test_multi_sessions_per_uid_10app @@ -36,7 +36,7 @@ function enable_channel_per_uid() function check_sessiond() { - if [ -z "$(pgrep --full lt-lttng-sessiond)" ]; then + if [ -z "$(lttng_pgrep lt-lttng-sessiond)" ]; then local str_date=$(date +%H%M%S-%d%m%Y) diag "!!!The session daemon died unexpectedly!!!" @@ -56,7 +56,7 @@ function start_sessiond() BAIL_OUT "*** Kernel too old for session daemon tests ***" fi - if [ -z $(pgrep --full lt-$SESSIOND_BIN) ]; then + if [ -z $(lttng_pgrep lt-$SESSIOND_BIN) ]; then # We have to start it like this so the ulimit -c is used by this # process. Also, we collect any error message printed out. $TESTDIR/../src/bin/lttng-sessiond/$SESSIOND_BIN --quiet --background --consumerd32-path="$TESTDIR/../src/bin/lttng-consumerd/lttng-consumerd" --consumerd64-path="$TESTDIR/../src/bin/lttng-consumerd/lttng-consumerd" >$LOG_FILE 2>&1 diff --git a/tests/stress/test_multi_sessions_per_uid_5app_streaming b/tests/stress/test_multi_sessions_per_uid_5app_streaming index f8a0a4755..a0f1898b6 100755 --- a/tests/stress/test_multi_sessions_per_uid_5app_streaming +++ b/tests/stress/test_multi_sessions_per_uid_5app_streaming @@ -37,7 +37,7 @@ function enable_channel_per_uid() function check_sessiond() { - if [ -z "$(pgrep --full lt-lttng-sessiond)" ]; then + if [ -z "$(lttng_pgrep lt-lttng-sessiond)" ]; then local str_date=$(date +%H%M%S-%d%m%Y) diag "!!!The session daemon died unexpectedly!!!" @@ -51,7 +51,7 @@ function check_sessiond() function check_relayd() { - if [ -z "$(pgrep --full lt-lttng-relayd)" ]; then + if [ -z "$(lttng_pgrep lt-lttng-relayd)" ]; then local str_date=$(date +%H%M%S-%d%m%Y) diag "!!!The relay daemon died unexpectedly!!!" @@ -71,7 +71,7 @@ function start_sessiond() BAIL_OUT "*** Kernel too old for session daemon tests ***" fi - if [ -z $(pgrep --full lt-$SESSIOND_BIN) ]; then + if [ -z $(lttng_pgrep lt-$SESSIOND_BIN) ]; then # We have to start it like this so the ulimit -c is used by this # process. Also, we collect any error message printed out. $TESTDIR/../src/bin/lttng-sessiond/$SESSIOND_BIN --quiet --background --consumerd32-path="$TESTDIR/../src/bin/lttng-consumerd/lttng-consumerd" --consumerd64-path="$TESTDIR/../src/bin/lttng-consumerd/lttng-consumerd" >$LOG_FILE_SESSIOND 2>&1 @@ -84,7 +84,7 @@ function start_relayd { local opt=$1 - if [ -z $(pgrep --full lt-$RELAYD_BIN) ]; then + if [ -z $(lttng_pgrep lt-$RELAYD_BIN) ]; then $TESTDIR/../src/bin/lttng-relayd/$RELAYD_BIN $opt >$LOG_FILE_RELAYD 2>&1 & ok $? "Start lttng-relayd (opt: \"$opt\")" fi diff --git a/tests/stress/test_multi_sessions_per_uid_5app_streaming_kill_relayd b/tests/stress/test_multi_sessions_per_uid_5app_streaming_kill_relayd index e6ee7df7d..c9a4576b6 100755 --- a/tests/stress/test_multi_sessions_per_uid_5app_streaming_kill_relayd +++ b/tests/stress/test_multi_sessions_per_uid_5app_streaming_kill_relayd @@ -38,7 +38,7 @@ function enable_channel_per_uid() function check_sessiond() { - if [ -z "$(pgrep --full lt-lttng-sessiond)" ]; then + if [ -z "$(lttng_pgrep lt-lttng-sessiond)" ]; then local str_date=$(date +%H%M%S-%d%m%Y) diag "!!!The session daemon died unexpectedly!!!" @@ -58,7 +58,7 @@ function start_sessiond() BAIL_OUT "*** Kernel too old for session daemon tests ***" fi - if [ -z $(pgrep --full lt-$SESSIOND_BIN) ]; then + if [ -z $(lttng_pgrep lt-$SESSIOND_BIN) ]; then # We have to start it like this so the ulimit -c is used by this # process. Also, we collect any error message printed out. #$TESTDIR/../src/bin/lttng-sessiond/$SESSIOND_BIN --quiet --background --consumerd32-path="$TESTDIR/../src/bin/lttng-consumerd/lttng-consumerd" --consumerd64-path="$TESTDIR/../src/bin/lttng-consumerd/lttng-consumerd" >$LOG_FILE_SESSIOND 2>&1 @@ -73,7 +73,7 @@ function start_relayd { local opt=$1 - if [ -z $(pgrep --full lt-$RELAYD_BIN) ]; then + if [ -z $(lttng_pgrep lt-$RELAYD_BIN) ]; then $TESTDIR/../src/bin/lttng-relayd/$RELAYD_BIN $opt >$LOG_FILE_RELAYD 2>&1 & ok $? "Start lttng-relayd (opt: \"$opt\")" fi @@ -81,7 +81,7 @@ function start_relayd function check_relayd() { - if [ -z "$(pgrep --full lt-lttng-relayd)" ]; then + if [ -z "$(lttng_pgrep lt-lttng-relayd)" ]; then local str_date=$(date +%H%M%S-%d%m%Y) #diag "Relay daemon died. Starting it again" diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index e2c57bc8e..322bdfa7c 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -17,12 +17,14 @@ TESTS = test_kernel_data \ test_utils_compat_poll \ test_string_utils \ test_notification \ + test_event_rule \ test_directory_handle \ test_relayd_backward_compat_group_by_session \ ini_config/test_ini_config \ test_fd_tracker \ test_uuid \ - test_buffer_view + test_buffer_view \ + test_event_expr_to_bytecode LIBTAP=$(top_builddir)/tests/utils/tap/libtap.la @@ -41,7 +43,8 @@ noinst_PROGRAMS = test_uri test_session test_kernel_data \ test_string_utils test_notification test_directory_handle \ test_relayd_backward_compat_group_by_session \ test_fd_tracker test_uuid \ - test_buffer_view + test_buffer_view test_fd_tracker test_event_rule test_condition \ + test_event_expr_to_bytecode if HAVE_LIBLTTNG_UST_CTL noinst_PROGRAMS += test_ust_data @@ -61,6 +64,7 @@ SESSIOND_OBJS = $(top_builddir)/src/bin/lttng-sessiond/buffer-registry.$(OBJEXT) $(top_builddir)/src/bin/lttng-sessiond/kernel.$(OBJEXT) \ $(top_builddir)/src/bin/lttng-sessiond/ht-cleanup.$(OBJEXT) \ $(top_builddir)/src/bin/lttng-sessiond/notification-thread.$(OBJEXT) \ + $(top_builddir)/src/bin/lttng-sessiond/action-executor.$(OBJEXT) \ $(top_builddir)/src/bin/lttng-sessiond/lttng-syscall.$(OBJEXT) \ $(top_builddir)/src/bin/lttng-sessiond/channel.$(OBJEXT) \ $(top_builddir)/src/bin/lttng-sessiond/agent.$(OBJEXT) \ @@ -107,6 +111,7 @@ test_session_SOURCES = test_session.c test_session_LDADD = $(LIBTAP) $(LIBCOMMON) $(LIBRELAYD) $(LIBSESSIOND_COMM) \ $(LIBHASHTABLE) $(DL_LIBS) -lrt -lurcu-common -lurcu \ $(KMOD_LIBS) \ + -lmsgpackc \ $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la \ $(top_builddir)/src/common/kernel-ctl/libkernel-ctl.la \ $(top_builddir)/src/common/compat/libcompat.la \ @@ -128,6 +133,7 @@ test_ust_data_LDADD = $(LIBTAP) $(LIBCOMMON) $(LIBRELAYD) $(LIBSESSIOND_COMM) \ $(LIBHASHTABLE) $(DL_LIBS) -lrt -lurcu-common -lurcu \ $(UST_CTL_LIBS) \ $(KMOD_LIBS) \ + -lmsgpackc \ $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la \ $(top_builddir)/src/common/kernel-ctl/libkernel-ctl.la \ $(top_builddir)/src/common/compat/libcompat.la \ @@ -184,6 +190,14 @@ test_string_utils_LDADD = $(LIBTAP) $(LIBCOMMON) $(LIBSTRINGUTILS) $(DL_LIBS) # Notification api test_notification_SOURCES = test_notification.c test_notification_LDADD = $(LIBTAP) $(LIBLTTNG_CTL) $(DL_LIBS) +# +# Event rule api +test_event_rule_SOURCES = test_event_rule.c +test_event_rule_LDADD = $(LIBTAP) $(LIBCOMMON) $(LIBLTTNG_CTL) $(DL_LIBS) + +# Condition api +test_condition_SOURCES = test_condition.c +test_condition_LDADD = $(LIBTAP) $(LIBCOMMON) $(LIBLTTNG_CTL) $(DL_LIBS) # relayd backward compat for groou-by-session utilities test_relayd_backward_compat_group_by_session_SOURCES = test_relayd_backward_compat_group_by_session.c @@ -201,3 +215,7 @@ test_uuid_LDADD = $(LIBTAP) $(LIBCOMMON) # buffer view unit test test_buffer_view_SOURCES = test_buffer_view.c test_buffer_view_LDADD = $(LIBTAP) $(LIBCOMMON) + +# Event expression to bytecode test +test_event_expr_to_bytecode_SOURCES = test_event_expr_to_bytecode.c +test_event_expr_to_bytecode_LDADD = $(LIBTAP) $(LIBLTTNG_CTL) $(LIBCOMMON) diff --git a/tests/unit/test_condition.c b/tests/unit/test_condition.c new file mode 100644 index 000000000..376603b58 --- /dev/null +++ b/tests/unit/test_condition.c @@ -0,0 +1,94 @@ +/* + * test_condition.c + * + * Unit tests for the condition API. + * + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +/* For error.h */ +int lttng_opt_quiet = 1; +int lttng_opt_verbose; +int lttng_opt_mi; + +#define NUM_TESTS 3 + +static +void test_condition_event_rule(void) +{ + int ret; + struct lttng_event_rule *tracepoint = NULL; + const struct lttng_event_rule *tracepoint_tmp = NULL; + enum lttng_event_rule_status status; + struct lttng_condition *condition = NULL; + struct lttng_condition *condition_from_buffer = NULL; + enum lttng_condition_status condition_status; + const char *pattern="my_event_*"; + const char *filter="msg_id == 23 && size >= 2048"; + const char *exclusions[] = {"my_event_test1", "my_event_test2" ,"my_event_test3"}; + struct lttng_dynamic_buffer buffer; + struct lttng_buffer_view view; + + lttng_dynamic_buffer_init(&buffer); + + tracepoint = lttng_event_rule_tracepoint_create(LTTNG_DOMAIN_UST); + ok(tracepoint, "tracepoint UST_DOMAIN"); + + status = lttng_event_rule_tracepoint_set_pattern(tracepoint, pattern); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting pattern"); + + status = lttng_event_rule_tracepoint_set_filter(tracepoint, filter); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting filter"); + + status = lttng_event_rule_tracepoint_set_loglevel_range(tracepoint, LTTNG_LOGLEVEL_WARNING); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting range loglevel"); + + status = lttng_event_rule_tracepoint_set_exclusions(tracepoint, 3, exclusions); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting exclusions"); + + condition = lttng_condition_event_rule_create(tracepoint); + ok(condition, "created condition"); + + condition_status = lttng_condition_event_rule_get_rule(condition, &tracepoint_tmp); + ok(condition_status == LTTNG_CONDITION_STATUS_OK, "getting event rule"); + ok(tracepoint == tracepoint_tmp, "Ownership transfer is good"); + + ret = lttng_condition_serialize(condition, &buffer, NULL); + ok(ret == 0, "Condition serialized"); + + view = lttng_buffer_view_from_dynamic_buffer(&buffer, 0, -1); + (void) lttng_condition_create_from_buffer(&view, &condition_from_buffer); + ok(condition_from_buffer, "condition from buffer is non null"); + + ok(lttng_condition_is_equal(condition, condition_from_buffer), "serialized and from buffer are equal"); + + lttng_dynamic_buffer_reset(&buffer); + lttng_condition_destroy(condition); + lttng_condition_destroy(condition_from_buffer); +} + +int main(int argc, const char *argv[]) +{ + plan_tests(NUM_TESTS); + test_condition_event_rule(); + return exit_status(); +} diff --git a/tests/unit/test_event_expr_to_bytecode.c b/tests/unit/test_event_expr_to_bytecode.c new file mode 100644 index 000000000..dd26e66e6 --- /dev/null +++ b/tests/unit/test_event_expr_to_bytecode.c @@ -0,0 +1,90 @@ +/* + * Copyright 2020 EfficiOS, Inc. + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include + +#define NR_TESTS 4 + +static +void test_event_payload_field(void) +{ + struct lttng_event_expr *event_expr; + struct lttng_bytecode *bytecode = NULL; + int ret; + + event_expr = lttng_event_expr_event_payload_field_create("tourlou"); + ret = lttng_event_expr_to_bytecode(event_expr, &bytecode); + + ok(ret == 0, "event payload field"); + + lttng_event_expr_destroy(event_expr); + free(bytecode); +} + +static +void test_channel_context_field(void) +{ + struct lttng_event_expr *event_expr; + struct lttng_bytecode *bytecode = NULL; + int ret; + + event_expr = lttng_event_expr_channel_context_field_create("tourlou"); + ret = lttng_event_expr_to_bytecode(event_expr, &bytecode); + + ok(ret == 0, "channel context field"); + + lttng_event_expr_destroy(event_expr); + free(bytecode); +} + +static +void test_app_specific_context_field(void) +{ + struct lttng_event_expr *event_expr; + struct lttng_bytecode *bytecode = NULL; + int ret; + + event_expr = lttng_event_expr_app_specific_context_field_create("Bob", "Leponge"); + ret = lttng_event_expr_to_bytecode(event_expr, &bytecode); + + ok(ret == 0, "app-specific context field"); + + lttng_event_expr_destroy(event_expr); + free(bytecode); +} + +static +void test_array_field_element(void) +{ + struct lttng_event_expr *event_expr; + struct lttng_bytecode *bytecode = NULL; + int ret; + + event_expr = lttng_event_expr_event_payload_field_create("allo"); + event_expr = lttng_event_expr_array_field_element_create(event_expr, 168); + ret = lttng_event_expr_to_bytecode(event_expr, &bytecode); + + ok(ret == 0, "array field element"); + + lttng_event_expr_destroy(event_expr); + free(bytecode); +} + +int main(void) +{ + plan_tests(NR_TESTS); + + test_event_payload_field(); + test_channel_context_field(); + test_app_specific_context_field(); + test_array_field_element(); + + return exit_status(); +} diff --git a/tests/unit/test_event_rule.c b/tests/unit/test_event_rule.c new file mode 100644 index 000000000..7cc56c053 --- /dev/null +++ b/tests/unit/test_event_rule.c @@ -0,0 +1,154 @@ +/* + * test_event_rule.c + * + * Unit tests for the notification API. + * + * Copyright (C) 2019 Jonathan Rajotte + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +/* For error.h */ +int lttng_opt_quiet = 1; +int lttng_opt_verbose; +int lttng_opt_mi; + +#define NUM_TESTS 31 + +static +void test_event_rule_tracepoint_ust(void) +{ + int ret; + unsigned int count; + struct lttng_event_rule *tracepoint = NULL; + struct lttng_event_rule *tracepoint_from_buffer = NULL; + enum lttng_event_rule_status status; + enum lttng_domain_type domain_type; + enum lttng_loglevel_type loglevel_type; + const char *pattern="my_event_*"; + const char *filter="msg_id == 23 && size >= 2048"; + const char *tmp; + const char *exclusions[] = {"my_event_test1", "my_event_test2" ,"my_event_test3"}; + struct lttng_dynamic_buffer buffer; + struct lttng_buffer_view view; + + lttng_dynamic_buffer_init(&buffer); + + tracepoint = lttng_event_rule_tracepoint_create(LTTNG_DOMAIN_UST); + ok(tracepoint, "tracepoint UST_DOMAIN"); + + status = lttng_event_rule_tracepoint_get_domain_type(tracepoint, &domain_type); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "get tracepoint domain"); + ok(domain_type == LTTNG_DOMAIN_UST, "domain is UST"); + + status = lttng_event_rule_tracepoint_set_pattern(tracepoint, pattern); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting pattern"); + status = lttng_event_rule_tracepoint_get_pattern(tracepoint, &tmp); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "getting pattern"); + ok(!strncmp(pattern, tmp, strlen(pattern)), "pattern is equal"); + + status = lttng_event_rule_tracepoint_set_filter(tracepoint, filter); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting filter"); + status = lttng_event_rule_tracepoint_get_filter(tracepoint, &tmp); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "getting filter"); + ok(!strncmp(filter, tmp, strlen(filter)), "filter is equal"); + + status = lttng_event_rule_tracepoint_set_loglevel_all(tracepoint); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting all loglevel"); + status = lttng_event_rule_tracepoint_get_loglevel_type(tracepoint, &loglevel_type); + ok(loglevel_type == LTTNG_EVENT_LOGLEVEL_ALL, "getting loglevel type all"); + status = lttng_event_rule_tracepoint_get_loglevel(tracepoint, &ret); + ok(status == LTTNG_EVENT_RULE_STATUS_UNSET, "get unset loglevel value"); + + status = lttng_event_rule_tracepoint_set_loglevel(tracepoint, LTTNG_LOGLEVEL_INFO); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting single loglevel"); + status = lttng_event_rule_tracepoint_get_loglevel_type(tracepoint, &loglevel_type); + ok(loglevel_type == LTTNG_EVENT_LOGLEVEL_SINGLE, "getting loglevel type single"); + status = lttng_event_rule_tracepoint_get_loglevel(tracepoint, &ret); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "get loglevel value"); + ok(ret == LTTNG_LOGLEVEL_INFO, "loglevel value is equal"); + + status = lttng_event_rule_tracepoint_set_loglevel_range(tracepoint, LTTNG_LOGLEVEL_WARNING); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting range loglevel"); + status = lttng_event_rule_tracepoint_get_loglevel_type(tracepoint, &loglevel_type); + ok(loglevel_type == LTTNG_EVENT_LOGLEVEL_RANGE, "getting loglevel type range"); + status = lttng_event_rule_tracepoint_get_loglevel(tracepoint, &ret); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "get loglevel value"); + ok(ret == LTTNG_LOGLEVEL_WARNING, "loglevel valuei is equal"); + + status = lttng_event_rule_tracepoint_set_exclusions(tracepoint, 3, exclusions); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "setting exclusions"); + + status = lttng_event_rule_tracepoint_get_exclusions_count(tracepoint, &count); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "getting exclusion count"); + ok(count == 3, "count is %d/3", count); + + for (int i = 0; i < count; i++) { + status = lttng_event_rule_tracepoint_get_exclusion_at_index(tracepoint, i, &tmp); + ok(status == LTTNG_EVENT_RULE_STATUS_OK, "getting exclusion at index %d", i); + ok(!strncmp(exclusions[i], tmp, strlen(exclusions[i])), "%s == %s", tmp, exclusions[i]); + } + + lttng_event_rule_serialize(tracepoint, &buffer, NULL); + view = lttng_buffer_view_from_dynamic_buffer(&buffer, 0, -1); + lttng_event_rule_create_from_buffer(&view, &tracepoint_from_buffer); + + ok(lttng_event_rule_is_equal(tracepoint, tracepoint_from_buffer), "serialized and from buffer are equal"); + + + + + lttng_dynamic_buffer_reset(&buffer); + lttng_event_rule_destroy(tracepoint); + lttng_event_rule_destroy(tracepoint_from_buffer); +} + +static +void test_event_rule_tracepoint(void) +{ + struct lttng_event_rule *tracepoint = NULL; + + diag("Testing lttng_event_rule_tracepoint"); + tracepoint = lttng_event_rule_tracepoint_create(LTTNG_DOMAIN_NONE); + ok(!tracepoint, "Domain type restriction on create"); + + test_event_rule_tracepoint_ust(); +} + +int main(int argc, const char *argv[]) +{ + plan_tests(NUM_TESTS); + test_event_rule_tracepoint(); + return exit_status(); +} diff --git a/tests/utils/testapp/gen-syscall-events/gen-syscall-events.c b/tests/utils/testapp/gen-syscall-events/gen-syscall-events.c index ead6fe387..45390c1cc 100644 --- a/tests/utils/testapp/gen-syscall-events/gen-syscall-events.c +++ b/tests/utils/testapp/gen-syscall-events/gen-syscall-events.c @@ -13,6 +13,41 @@ #include "utils.h" #define MAX_LEN 16 + +static +int open_read_close(const char *path) +{ + int fd, ret; + char buf[MAX_LEN]; + /* + * Start generating syscalls. We use syscall(2) to prevent libc to change + * the underlying syscall. e.g. calling openat(2) instead of open(2). + */ + fd = syscall(SYS_openat, AT_FDCWD, path, O_RDONLY); + if (fd < 0) { + perror("open"); + ret = -1; + goto error; + } + + ret = syscall(SYS_read, fd, buf, MAX_LEN); + if (ret < 0) { + perror("read"); + ret = -1; + goto error; + } + + ret = syscall(SYS_close, fd); + if (ret == -1) { + perror("close"); + ret = -1; + goto error; + } + +error: + return ret; +} + /* * The process waits for the creation of a file passed as argument from an * external processes to execute a syscall and exiting. This is useful for tests @@ -21,18 +56,20 @@ */ int main(int argc, char **argv) { - int fd, ret; - char buf[MAX_LEN]; - char *start_file; + int ret; + const char *start_file, *path1, *path2; - if (argc != 2) { + if (argc != 4) { fprintf(stderr, "Error: Missing argument\n"); + fprintf(stderr, "USAGE: %s PATH_WAIT_FILE PATH1_TO_OPEN PATH2_TO_OPEN\n", argv[0]); fprintf(stderr, "USAGE: %s PATH_WAIT_FILE\n", argv[0]); ret = -1; goto error; } start_file = argv[1]; + path1 = argv[2]; + path2 = argv[3]; /* * Wait for the start_file to be created by an external process @@ -47,23 +84,14 @@ int main(int argc, char **argv) * Start generating syscalls. We use syscall(2) to prevent libc to change * the underlying syscall. e.g. calling openat(2) instead of open(2). */ - fd = syscall(SYS_openat, AT_FDCWD, "/proc/cpuinfo", O_RDONLY); - if (fd < 0) { - perror("open"); - ret = -1; - goto error; - } - - ret = syscall(SYS_read, fd, buf, MAX_LEN); - if (ret < 0) { - perror("read"); + ret = open_read_close(path1); + if (ret == -1) { ret = -1; goto error; } - ret = syscall(SYS_close, fd); + ret = open_read_close(path2); if (ret == -1) { - perror("close"); ret = -1; goto error; } diff --git a/tests/utils/testapp/gen-ust-events/gen-ust-events.c b/tests/utils/testapp/gen-ust-events/gen-ust-events.c index 7ce86bcae..df1e58e41 100644 --- a/tests/utils/testapp/gen-ust-events/gen-ust-events.c +++ b/tests/utils/testapp/gen-ust-events/gen-ust-events.c @@ -48,8 +48,10 @@ int main(int argc, char **argv) int option; long values[] = { 1, 2, 3 }; char text[10] = "test"; + char escape[10] = "\\*"; double dbl = 2.0; float flt = 2222.0; + uint32_t net_values[] = { 1, 2, 3 }; int nr_iter = 100, ret = 0, first_event_file_created = 0; useconds_t nr_usec = 0; char *after_first_event_file_path = NULL; @@ -64,6 +66,10 @@ int main(int argc, char **argv) /* Wait on file before exiting */ char *before_exit_file_path = NULL; + for (i = 0; i < 3; i++) { + net_values[i] = htonl(net_values[i]); + } + while ((option = getopt_long(argc, argv, "i:w:a:b:c:d:", long_options, &option_index)) != -1) { switch (option) { @@ -141,7 +147,7 @@ int main(int argc, char **argv) } netint = htonl(i); tracepoint(tp, tptest, i, netint, values, text, - strlen(text), dbl, flt); + strlen(text), escape, net_values, dbl, flt); /* * First loop we create the file if asked to indicate diff --git a/tests/utils/testapp/gen-ust-events/tp.h b/tests/utils/testapp/gen-ust-events/tp.h index bc1949417..132f9ba4e 100644 --- a/tests/utils/testapp/gen-ust-events/tp.h +++ b/tests/utils/testapp/gen-ust-events/tp.h @@ -12,24 +12,51 @@ #define _TRACEPOINT_TP_H #include +#include + +TRACEPOINT_ENUM( + tp, tptest_enum, + TP_ENUM_VALUES( + ctf_enum_auto("AUTO: EXPECT 0") + ctf_enum_value("VALUE: 23", 23) + ctf_enum_value("VALUE: 27", 27) + ctf_enum_auto("AUTO: EXPECT 28") + ctf_enum_range("RANGE: 101 TO 303", 101, 303) + ctf_enum_auto("AUTO: EXPECT 304") + ctf_enum_value("VALUE: -1", -1) + ) +) TRACEPOINT_EVENT(tp, tptest, TP_ARGS(int, anint, int, netint, long *, values, char *, text, size_t, textlen, + char *, etext, uint32_t * , net_values, double, doublearg, float, floatarg), TP_FIELDS( ctf_integer(int, intfield, anint) ctf_integer_hex(int, intfield2, anint) ctf_integer(long, longfield, anint) + ctf_integer(int, signedfield, -1) ctf_integer_network(int, netintfield, netint) ctf_integer_network_hex(int, netintfieldhex, netint) ctf_array(long, arrfield1, values, 3) ctf_array_text(char, arrfield2, text, 10) + ctf_array_network(uint32_t, arrfield3, net_values, 3) ctf_sequence(char, seqfield1, text, size_t, textlen) ctf_sequence_text(char, seqfield2, text, size_t, textlen) + ctf_sequence_network(uint32_t, seqfield3, net_values, size_t, 3) + ctf_sequence(long, seqfield4, values, size_t, 3) ctf_string(stringfield, text) + ctf_string(stringfield2, etext) ctf_float(float, floatfield, floatarg) ctf_float(double, doublefield, doublearg) + ctf_enum(tp, tptest_enum, int, enum0, 0) + ctf_enum(tp, tptest_enum, int, enum23, 23) + ctf_enum(tp, tptest_enum, int, enum27, 27) + ctf_enum(tp, tptest_enum, int, enum28, 28) + ctf_enum(tp, tptest_enum, int, enum202, 202) + ctf_enum(tp, tptest_enum, int, enum304, 304) + ctf_enum(tp, tptest_enum, int, enumnegative, -1) ) ) diff --git a/tests/utils/utils.sh b/tests/utils/utils.sh index 0583b5c19..1dfc2d2f8 100644 --- a/tests/utils/utils.sh +++ b/tests/utils/utils.sh @@ -74,6 +74,42 @@ trap full_cleanup SIGINT SIGTERM trap null_pipes SIGPIPE +# Check pgrep from env, default to pgrep if none +if [ -z "$PGREP" ]; then + PGREP=pgrep +fi + +# Due to the renaming of threads we need to use the full command (pgrep -f) to +# identify the pids for multiple lttng related processes. The problem with "pgrep +# -f" is that it ends up also looking at the arguments. We use a two stage +# lookup. The first one is using "pgrep -f" yielding potential candidate. +# The second on perform grep on the basename of the first field of the +# /proc/pid/cmdline of the previously identified pids. The first field +# correspond to the actual command. +function lttng_pgrep () +{ + local pattern=$1 + local possible_pids + local full_command_no_argument + local command_basename + + possible_pids=$($PGREP -f "$pattern") + if [ -z "$possible_pids" ]; then + return 0 + fi + + while IFS= read -r pid ; do + # /proc/pid/cmdline is null separated. + if full_command_no_argument=$(cut -d '' -f 1 < /proc/"$pid"/cmdline); then + command_basename=$(basename "$full_command_no_argument") + if grep -q "$pattern" <<< "$command_basename"; then + echo "$pid" + fi + fi + done <<< "$possible_pids" + return 0 +} + function print_ok () { # Check if we are a terminal @@ -401,7 +437,7 @@ function start_lttng_relayd_opt() DIR=$(readlink -f "$TESTDIR") - if [ -z $(pgrep $RELAYD_MATCH) ]; then + if [ -z $(lttng_pgrep "$RELAYD_MATCH") ]; then # shellcheck disable=SC2086 $DIR/../src/bin/lttng-relayd/$RELAYD_BIN $process_mode $opt 1> $OUTPUT_DEST 2> $ERROR_OUTPUT_DEST #$DIR/../src/bin/lttng-relayd/$RELAYD_BIN $opt -vvv >>/tmp/relayd.log 2>&1 & @@ -450,7 +486,7 @@ function stop_lttng_relayd_opt() local retval=0 local pids= - pids=$(pgrep "$RELAYD_MATCH") + pids=$(lttng_pgrep "$RELAYD_MATCH") if [ -z "$pids" ]; then if [ "$withtap" -eq "1" ]; then pass "No relay daemon to kill" @@ -469,7 +505,7 @@ function stop_lttng_relayd_opt() else out=1 while [ -n "$out" ]; do - out=$(pgrep "$RELAYD_MATCH") + out=$(lttng_pgrep "$RELAYD_MATCH") if [ -n "$dtimeleft_s" ]; then if [ $dtimeleft_s -lt 0 ]; then out= @@ -547,7 +583,7 @@ function start_lttng_sessiond_opt() : "${LTTNG_SESSION_CONFIG_XSD_PATH="${DIR}/../src/common/config/"}" export LTTNG_SESSION_CONFIG_XSD_PATH - if [ -z "$(pgrep "${SESSIOND_MATCH}")" ]; then + if [ -z "$(lttng_pgrep "${SESSIOND_MATCH}")" ]; then # Have a load path ? if [ -n "$load_path" ]; then # shellcheck disable=SC2086 @@ -599,10 +635,10 @@ function stop_lttng_sessiond_opt() local retval=0 local runas_pids= - runas_pids=$(pgrep "$RUNAS_MATCH") + runas_pids=$(lttng_pgrep "$RUNAS_MATCH") local pids= - pids=$(pgrep "$SESSIOND_MATCH") + pids=$(lttng_pgrep "$SESSIOND_MATCH") if [ -n "$runas_pids" ]; then pids="$pids $runas_pids" @@ -626,7 +662,7 @@ function stop_lttng_sessiond_opt() else out=1 while [ -n "$out" ]; do - out=$(pgrep "${SESSIOND_MATCH}") + out=$(lttng_pgrep "${SESSIOND_MATCH}") if [ -n "$dtimeleft_s" ]; then if [ $dtimeleft_s -lt 0 ]; then out= @@ -638,7 +674,7 @@ function stop_lttng_sessiond_opt() done out=1 while [ -n "$out" ]; do - out=$(pgrep "$CONSUMERD_MATCH") + out=$(lttng_pgrep "$CONSUMERD_MATCH") if [ -n "$dtimeleft_s" ]; then if [ $dtimeleft_s -lt 0 ]; then out= @@ -692,7 +728,7 @@ function sigstop_lttng_sessiond_opt() return fi - PID_SESSIOND="$(pgrep "${SESSIOND_MATCH}") $(pgrep "$RUNAS_MATCH")" + PID_SESSIOND="$(lttng_pgrep "${SESSIOND_MATCH}") $(lttng_pgrep "$RUNAS_MATCH")" if [ "$withtap" -eq "1" ]; then diag "Sending SIGSTOP to lt-$SESSIOND_BIN and $SESSIOND_BIN pids: $(echo "$PID_SESSIOND" | tr '\n' ' ')" @@ -706,7 +742,7 @@ function sigstop_lttng_sessiond_opt() else out=1 while [ $out -ne 0 ]; do - pid="$(pgrep "$SESSIOND_MATCH")" + pid="$(lttng_pgrep "$SESSIOND_MATCH")" # Wait until state becomes stopped for session # daemon(s). @@ -754,7 +790,7 @@ function stop_lttng_consumerd_opt() local retval=0 - PID_CONSUMERD="$(pgrep "$CONSUMERD_MATCH")" + PID_CONSUMERD="$(lttng_pgrep "$CONSUMERD_MATCH")" if [ -z "$PID_CONSUMERD" ]; then if [ "$withtap" -eq "1" ]; then @@ -774,7 +810,7 @@ function stop_lttng_consumerd_opt() else out=1 while [ $out -ne 0 ]; do - pid="$(pgrep "$CONSUMERD_MATCH")" + pid="$(lttng_pgrep "$CONSUMERD_MATCH")" # If consumerds are still present check their status. # A zombie status qualifies the consumerd as *killed* @@ -821,7 +857,7 @@ function sigstop_lttng_consumerd_opt() local withtap=$1 local signal=SIGSTOP - PID_CONSUMERD="$(pgrep "$CONSUMERD_MATCH")" + PID_CONSUMERD="$(lttng_pgrep "$CONSUMERD_MATCH")" diag "Sending SIGSTOP to $CONSUMERD_BIN pids: $(echo "$PID_CONSUMERD" | tr '\n' ' ')" @@ -837,7 +873,7 @@ function sigstop_lttng_consumerd_opt() else out=1 while [ $out -ne 0 ]; do - pid="$(pgrep "$CONSUMERD_MATCH")" + pid="$(lttng_pgrep "$CONSUMERD_MATCH")" # Wait until state becomes stopped for all # consumers. -- 2.34.1