From 64e2c0ed5db412b9b3ecced98c7875bf3da0c08f Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 17 Feb 2020 19:35:52 -0500 Subject: [PATCH 01/16] Fix: relayd: unchecked poll set creation return value MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The fd_tracker_util_poll_create function can fail because of fd exhaustion or because the underlying epoll call fails. In both cases, report and handle the error. Signed-off-by: Jérémie Galarneau Change-Id: Id1e35d43442e74dd6784a9a4e235576a5bf135e2 --- src/bin/lttng-relayd/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bin/lttng-relayd/main.c b/src/bin/lttng-relayd/main.c index 3aef0fabc..cbd9e9cc7 100644 --- a/src/bin/lttng-relayd/main.c +++ b/src/bin/lttng-relayd/main.c @@ -918,6 +918,10 @@ static int create_named_thread_poll_set(struct lttng_poll_event *events, ret = fd_tracker_util_poll_create(the_fd_tracker, name, events, 1, LTTNG_CLOEXEC); + if (ret) { + PERROR("Failed to create \"%s\" poll file descriptor", name); + goto error; + } /* Add quit pipe */ ret = lttng_poll_add(events, thread_quit_pipe[0], LPOLLIN | LPOLLERR); -- 2.34.1 From d546eeec57f3d23255e55d6d7384ab825bae8218 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 17 Feb 2020 19:40:47 -0500 Subject: [PATCH 02/16] Fix: relayd: live: unchecked return value when opening relay socket MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit fd_tracker_open_unsuspendable_fd may fail because the underlying socket() call fails or there may be too many open file descriptors at the time of the call. In both cases, these errors must be logged and handled. Signed-off-by: Jérémie Galarneau Change-Id: I3205896a5e8c83ceba02005a2b73f9466d26427c --- src/bin/lttng-relayd/live.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bin/lttng-relayd/live.c b/src/bin/lttng-relayd/live.c index f28b9c66f..3ddafe2a1 100644 --- a/src/bin/lttng-relayd/live.c +++ b/src/bin/lttng-relayd/live.c @@ -561,7 +561,11 @@ struct lttcomm_sock *init_socket(struct lttng_uri *uri, const char *name) ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &sock_fd, (const char **) (formated_name ? &formated_name : NULL), 1, create_sock, sock); - free(formated_name); + if (ret) { + PERROR("Failed to create \"%s\" socket", + formated_name ?: "Unknown"); + goto error; + } DBG("Listening on %s socket %d", name, sock->fd); ret = sock->ops->bind(sock); @@ -576,12 +580,14 @@ struct lttcomm_sock *init_socket(struct lttng_uri *uri, const char *name) } + free(formated_name); return sock; error: if (sock) { lttcomm_destroy_sock(sock); } + free(formated_name); return NULL; } -- 2.34.1 From ad36f3a7d46a3b0a49ff271e1c8fe3a9a8e9042e Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 17 Feb 2020 19:46:18 -0500 Subject: [PATCH 03/16] Fix: relayd: live: unchecked poll set creation return value MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The fd_tracker_util_poll_create function can fail because of fd exhaustion or because the underlying epoll call fails. In both cases, report and handle the error. Signed-off-by: Jérémie Galarneau Change-Id: Ie79fdc011afda43395ac883c6648f983118cfddb --- src/bin/lttng-relayd/live.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bin/lttng-relayd/live.c b/src/bin/lttng-relayd/live.c index 3ddafe2a1..06cfa5c7b 100644 --- a/src/bin/lttng-relayd/live.c +++ b/src/bin/lttng-relayd/live.c @@ -439,6 +439,10 @@ int create_named_thread_poll_set(struct lttng_poll_event *events, ret = fd_tracker_util_poll_create(the_fd_tracker, name, events, 1, LTTNG_CLOEXEC); + if (ret) { + PERROR("Failed to create \"%s\" poll file descriptor", name); + goto error; + } /* Add quit pipe */ ret = lttng_poll_add(events, thread_quit_pipe[0], LPOLLIN | LPOLLERR); -- 2.34.1 From a28dd43d8f6781ceadc49d2ac7c245f367d84511 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 17 Feb 2020 19:49:04 -0500 Subject: [PATCH 04/16] Fix: lttng: track-untrack: error assigned to wrong variable MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit CMD_ERROR is assigned to 'ret' rather than 'command_ret' when an unknown left-over argument is passed to the track/untrack command. Signed-off-by: Jérémie Galarneau Change-Id: I5889a934d9859d0499eb26555658bf15af73f927 --- src/bin/lttng/commands/track-untrack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/lttng/commands/track-untrack.c b/src/bin/lttng/commands/track-untrack.c index 7f8007dab..06fd528ab 100644 --- a/src/bin/lttng/commands/track-untrack.c +++ b/src/bin/lttng/commands/track-untrack.c @@ -719,7 +719,7 @@ int cmd_track_untrack(enum cmd_type cmd_type, const char *cmd_str, leftover = poptGetArg(pc); if (leftover) { ERR("Unknown argument: %s", leftover); - ret = CMD_ERROR; + command_ret = CMD_ERROR; goto end; } -- 2.34.1 From 3dde0a9310a828bb49caefad56a76833860668d0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 17 Feb 2020 19:51:54 -0500 Subject: [PATCH 05/16] Fix: trace-chunk: useless assignment to 'ret' MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 'ret' is not used to report error in lttng_trace_chunk_rename_path_no_lock(); status is used. Therefore, this assignment is useless. Signed-off-by: Jérémie Galarneau Change-Id: I1804d616a24c41d3956b021f90bb77d0e6efef1a --- src/common/trace-chunk.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/trace-chunk.c b/src/common/trace-chunk.c index ea952220b..7b60d9197 100644 --- a/src/common/trace-chunk.c +++ b/src/common/trace-chunk.c @@ -905,7 +905,6 @@ enum lttng_trace_chunk_status lttng_trace_chunk_rename_path_no_lock( if (status != LTTNG_TRACE_CHUNK_STATUS_OK) { ERR("Error removing subdirectory '%s' file when deleting chunk", old_path); - ret = -1; goto end; } } else { -- 2.34.1 From 9479b7a777d49a3d6a30af20693173ab0c4b00d7 Mon Sep 17 00:00:00 2001 From: Francis Deslauriers Date: Tue, 11 Feb 2020 15:51:52 -0500 Subject: [PATCH 06/16] Tests: notification.c: remove extra space MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Francis Deslauriers Change-Id: Iee893ab9d4aeb59e335af83b068adf716ac869f8 Signed-off-by: Jérémie Galarneau --- tests/regression/tools/notification/notification.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regression/tools/notification/notification.c b/tests/regression/tools/notification/notification.c index f076bbd0e..68ec64d01 100644 --- a/tests/regression/tools/notification/notification.c +++ b/tests/regression/tools/notification/notification.c @@ -315,7 +315,7 @@ void test_triggers_buffer_usage_condition(const char *session_name, 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", + 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", -- 2.34.1 From afa7d3f1c0fae5ab6acb5e2fff3eaaa5558d6743 Mon Sep 17 00:00:00 2001 From: Francis Deslauriers Date: Tue, 11 Feb 2020 18:27:35 -0500 Subject: [PATCH 07/16] Fix: Tests: `test_exclusion` passing for the wrong reason MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Issue ===== The following commit added `-i` and `-w` flags to the test app arguments of the `test_exclusion` tests: commit 6c4a91d639747f260ab46decebc50998ef063712 Author: Mathieu Desnoyers Date: Mon Aug 26 14:22:06 2019 -0400 tests: gen-ust-events: use options instead of arguments Remove argument dependency and ease usage of features individually. The `gen-ust-nevents` was not modified to support those flags. I suspect this mistake was caused by the name similarity of the `gen-ust-nevents` and the `gen-ust-events` test applications. We ended up calling the following command: ./gen-ust-nevents -i 100 -w -1 When called with such arguments the `gen-ust-nevents` parsed the first argument (`-i`) using `atoi()` which retuned 0. This was interpreted as the number of iterations requested by the user so the app immediately exited without generating any events. So, the test was not seeing any of the excluded events in the trace which was then considered as a successful result but no events were ever excluded because none were generated in the first place. Solution ======== Remove the use of `-i` and `-w` flags. I also added a `dry_run` test to confirm that we do indeed get events when exclusions are not used to prevent this error from happening in the future. Notes ===== - I changed the wildcard used in the enable-event command so to only enable events from the testapp and not the `lttng_ust_statedump:` events as those are generated even if we didnt' ask for them. - I add a stderr redirection to `/dev/null` in the trace reading pipeline because we now end up with traces with no events. This has changed because we now only enable events from the application (see previous note). - In a future commit, I will change the `gen-ust-nevents` application to take those `-i` and `-w` flags. Signed-off-by: Francis Deslauriers Change-Id: Id37dcd59a18b3401d97439bce1191a8c5cac87d5 Signed-off-by: Jérémie Galarneau --- .../regression/tools/exclusion/test_exclusion | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/tests/regression/tools/exclusion/test_exclusion b/tests/regression/tools/exclusion/test_exclusion index 6cd808c6d..d29553582 100755 --- a/tests/regression/tools/exclusion/test_exclusion +++ b/tests/regression/tools/exclusion/test_exclusion @@ -16,7 +16,7 @@ TESTAPP_NAME="gen-ust-nevents" TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME" NR_ITER=100 NR_USEC_WAIT=1 -NUM_TESTS=149 +NUM_TESTS=178 source $TESTDIR/utils/utils.sh @@ -25,12 +25,45 @@ function enable_ust_lttng_all_event_exclusion() sess_name="$1" exclusion="$2" - $TESTDIR/../src/bin/lttng/$LTTNG_BIN enable-event -a -s $sess_name -u -x "$exclusion" + $TESTDIR/../src/bin/lttng/$LTTNG_BIN enable-event -u "tp:*" -s $sess_name -x "$exclusion" } function run_apps { - $TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT >/dev/null 2>&1 + $TESTAPP_BIN $NR_ITER $NR_USEC_WAIT >/dev/null 2>&1 + ok $? "Running test application" +} + +# Testing for the absence of an event when testing exclusion is tricky. An +# event could be absent because our exclusion mechanism works but also because +# the event was not generate in the first place. This function test the ability +# of our test suite to generate events. +function dry_run +{ + trace_path=$(mktemp -d) + + # Create session + create_lttng_session_ok $SESSION_NAME $trace_path + + $TESTDIR/../src/bin/lttng/$LTTNG_BIN enable-event -u "tp:*" -s $SESSION_NAME > /dev/null + ok $? "Enabling events without exclusion" + + # Trace apps + start_lttng_tracing_ok $SESSION_NAME + run_apps + stop_lttng_tracing_ok $SESSION_NAME + + nb_events=$(babeltrace $trace_path | wc -l) + if [ "$nb_events" -ne "0" ]; then + ok 0 "Events were found during the dry run without exclusion" + else + fail "No events were found during the dry run without exclusion" + fi + + rm -rf $trace_path + + # Destroy session + destroy_lttng_session_ok $SESSION_NAME } function test_exclusion @@ -53,7 +86,7 @@ function test_exclusion # Destroy session destroy_lttng_session_ok $SESSION_NAME - stats=`babeltrace $trace_path | $STATS_BIN --tracepoint "$event_name_expected_to_be_missing" | grep -v index` + stats=`babeltrace $trace_path | $STATS_BIN --tracepoint "$event_name_expected_to_be_missing" | grep -v index 2> /dev/null` if [ ! -z "$stats" ]; then fail "Excluded event \"$event_name_expected_to_be_missing\" was found in trace!" else @@ -87,6 +120,8 @@ print_test_banner $TEST_DESC start_lttng_sessiond +dry_run + test_exclusion 'tp:tptest2' 'tp:tptest2' test_exclusion 'tp:tptest3' 'tp:tptest3' test_exclusion 'tp:tptest*' 'tp:tptest1' -- 2.34.1 From a0e115d5d7da3603a66d1da958ad19f72e2bb5b2 Mon Sep 17 00:00:00 2001 From: Francis Deslauriers Date: Tue, 11 Feb 2020 19:17:36 -0500 Subject: [PATCH 08/16] Tests: Cleanup: test_exclusion: more detailed output MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Francis Deslauriers Change-Id: I97baaf30385b644766f2e825d8884ea75770b330 Signed-off-by: Jérémie Galarneau --- tests/regression/tools/exclusion/test_exclusion | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/regression/tools/exclusion/test_exclusion b/tests/regression/tools/exclusion/test_exclusion index d29553582..e41925fa5 100755 --- a/tests/regression/tools/exclusion/test_exclusion +++ b/tests/regression/tools/exclusion/test_exclusion @@ -25,7 +25,7 @@ function enable_ust_lttng_all_event_exclusion() sess_name="$1" exclusion="$2" - $TESTDIR/../src/bin/lttng/$LTTNG_BIN enable-event -u "tp:*" -s $sess_name -x "$exclusion" + $TESTDIR/../src/bin/lttng/$LTTNG_BIN enable-event -u "tp:*" -s $sess_name -x "$exclusion" > /dev/null } function run_apps @@ -116,12 +116,14 @@ function test_exclusion_fail plan_tests $NUM_TESTS -print_test_banner $TEST_DESC +print_test_banner "$TEST_DESC" start_lttng_sessiond +diag "Enable event without exclusion" dry_run +diag "Enable event with exclusion" test_exclusion 'tp:tptest2' 'tp:tptest2' test_exclusion 'tp:tptest3' 'tp:tptest3' test_exclusion 'tp:tptest*' 'tp:tptest1' @@ -145,12 +147,12 @@ test_exclusion '*3' 'tp:tptest3' test_exclusion 'tp*test3,*2' 'tp:tptest2' test_exclusion '**tp*test3,*2' 'tp:tptest3' -# Cannot use exclusions with non-globbing event name +diag "Cannot use exclusions with non-globbing event name" test_exclusion_fail "allo" "lol" test_exclusion_fail "allo" "meow,lol" test_exclusion_fail "allo" "z*em" -# Exclusion name excludes all possible event names +diag "Exclusion name excludes all possible event names" test_exclusion_fail "allo*" "all*" test_exclusion_fail "allo*" "ze,all*,yes" -- 2.34.1 From 8db430e7dcb5305c60a93ccb2fc90e8e0f006915 Mon Sep 17 00:00:00 2001 From: Francis Deslauriers Date: Tue, 11 Feb 2020 19:27:05 -0500 Subject: [PATCH 09/16] Tests: gen-ust-nevents: use options instead of arguments MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Francis Deslauriers Change-Id: I59c648c650304e12b30bf8a3eaedaf9727c48700 Signed-off-by: Jérémie Galarneau --- .../regression/tools/exclusion/test_exclusion | 2 +- tests/regression/tools/snapshots/ust_test | 2 +- .../testapp/gen-ust-nevents/gen-ust-nevents.c | 37 ++++++++++++++----- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/tests/regression/tools/exclusion/test_exclusion b/tests/regression/tools/exclusion/test_exclusion index e41925fa5..ed653b72f 100755 --- a/tests/regression/tools/exclusion/test_exclusion +++ b/tests/regression/tools/exclusion/test_exclusion @@ -30,7 +30,7 @@ function enable_ust_lttng_all_event_exclusion() function run_apps { - $TESTAPP_BIN $NR_ITER $NR_USEC_WAIT >/dev/null 2>&1 + $TESTAPP_BIN --iter $NR_ITER --wait $NR_USEC_WAIT >/dev/null 2>&1 ok $? "Running test application" } diff --git a/tests/regression/tools/snapshots/ust_test b/tests/regression/tools/snapshots/ust_test index 3f7ee2555..c2563afd8 100755 --- a/tests/regression/tools/snapshots/ust_test +++ b/tests/regression/tools/snapshots/ust_test @@ -358,7 +358,7 @@ function test_ust_local_snapshot_large_metadata () enable_ust_lttng_event_ok $SESSION_NAME $LM_EVENT $CHANNEL_NAME start_lttng_tracing_ok $SESSION_NAME lttng_snapshot_add_output_ok $SESSION_NAME file://$TRACE_PATH - $LM_BIN 1 1 + $LM_BIN --iter 1 --wait 1 ok $? "Start application to trace" lttng_snapshot_record $SESSION_NAME stop_lttng_tracing_ok $SESSION_NAME diff --git a/tests/utils/testapp/gen-ust-nevents/gen-ust-nevents.c b/tests/utils/testapp/gen-ust-nevents/gen-ust-nevents.c index 52e5f3cb9..329368731 100644 --- a/tests/utils/testapp/gen-ust-nevents/gen-ust-nevents.c +++ b/tests/utils/testapp/gen-ust-nevents/gen-ust-nevents.c @@ -7,6 +7,7 @@ #define _LGPL_SOURCE #include +#include #include #include #include @@ -21,9 +22,17 @@ #define TRACEPOINT_DEFINE #include "tp.h" +static struct option long_options[] = +{ + /* These options set a flag. */ + {"iter", required_argument, 0, 'i'}, + {"wait", required_argument, 0, 'w'}, + {0, 0, 0, 0} +}; + int main(int argc, char **argv) { - int i, netint, ret = 0; + int i, netint, ret = 0, option_index, option; long values[] = { 1, 2, 3 }; char text[10] = "test"; double dbl = 2.0; @@ -31,19 +40,29 @@ int main(int argc, char **argv) unsigned int nr_iter = 100; useconds_t nr_usec = 0; + while ((option = getopt_long(argc, argv, "i:w:", + long_options, &option_index)) != -1) { + switch (option) { + case 'i': + nr_iter = atoi(optarg); + break; + case 'w': + nr_usec = atoi(optarg); + break; + case '?': + /* getopt_long already printed an error message. */ + break; + default: + ret = -1; + goto end; + } + } + if (set_signal_handler()) { ret = -1; goto end; } - if (argc >= 2) { - nr_iter = atoi(argv[1]); - } - - if (argc == 3) { - /* By default, don't wait unless user specifies. */ - nr_usec = atoi(argv[2]); - } for (i = 0; i < nr_iter; i++) { netint = htonl(i); -- 2.34.1 From 2868dcc95f37ab202cea1e4a3a00a88f7b42ba86 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Mon, 3 Feb 2020 17:38:23 -0500 Subject: [PATCH 10/16] tests: append to AM_CFLAGS instead of overriding it MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The Makefiles modified by this patch currently override the AM_CFLAGS value, which means that anything put in AM_CFLAGS by configure (for example, the warning flags) is lost. I believe the intention is to add some flags to CFLAGS, so modify them such that they append instead of override. notification/Makefile.am overrides AM_LDFLAGS with nothing. It feels like it was not the intention to actually clear that variable, but rather that it is just the by-product of a copy paste. If it was really the intention to clear the value of AM_LDFLAGS, there would have been a comment to explain it, right? Signed-off-by: Simon Marchi Change-Id: I4ef926d9135b16200e5f17d09461506a5e955068 Signed-off-by: Jérémie Galarneau --- tests/regression/tools/notification/Makefile.am | 4 +--- tests/utils/testapp/userspace-probe-elf-binary/Makefile.am | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/regression/tools/notification/Makefile.am b/tests/regression/tools/notification/Makefile.am index 3430f450a..bc2c9123c 100644 --- a/tests/regression/tools/notification/Makefile.am +++ b/tests/regression/tools/notification/Makefile.am @@ -1,8 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only -AM_CFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src -I$(top_srcdir)/tests -I$(top_srcdir)/tests/utils/ -I$(srcdir) -AM_LDFLAGS = - +AM_CFLAGS += -I$(top_srcdir)/tests/utils LIBTAP=$(top_builddir)/tests/utils/tap/libtap.la LIB_LTTNG_CTL = $(top_builddir)/src/lib/lttng-ctl/liblttng-ctl.la diff --git a/tests/utils/testapp/userspace-probe-elf-binary/Makefile.am b/tests/utils/testapp/userspace-probe-elf-binary/Makefile.am index aa27a82ed..af76ae7fc 100644 --- a/tests/utils/testapp/userspace-probe-elf-binary/Makefile.am +++ b/tests/utils/testapp/userspace-probe-elf-binary/Makefile.am @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only # no optimization -AM_CFLAGS = -O0 +AM_CFLAGS += -O0 noinst_LTLIBRARIES = libfoo.la libfoo_la_SOURCES = foo.c foo.h -- 2.34.1 From 7f08cc82e68617aa423952b8f9cc7c74bc247f8d Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Fri, 31 Jan 2020 13:05:20 -0500 Subject: [PATCH 11/16] configure: use AX_APPEND_COMPILE_FLAGS to detect supported warning flags MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit I would eventually like to enable some additional warnings by default when building lttng-tools. However, some warnings are compiler-specific or are not present in older versions of some compilers we need to support. We can therefore not add them unconditionally to CFLAGS. This patch uses the AX_APPEND_COMPILE_FLAGS macro to address that. This macro tests each individual flag we pass it with the current compiler. If it finds that the flag is supported (the compiler exits with status 0 when compiling a file with that flag), it appends it to the given variable, WARN_CFLAGS in our case). WARN_CFLAGS is then added to AM_CFLAGS. With time, we'll be able to throw any warning flag in there that we think is useful, even if just available in a recent version of one compiler. Signed-off-by: Simon Marchi Change-Id: Id2ae4b4e8882af788c835ce89a979544531370e9 Signed-off-by: Jérémie Galarneau --- configure.ac | 12 +++++++- m4/ax_append_compile_flags.m4 | 46 ++++++++++++++++++++++++++++++ m4/ax_append_flag.m4 | 50 +++++++++++++++++++++++++++++++++ m4/ax_check_compile_flag.m4 | 53 +++++++++++++++++++++++++++++++++++ m4/ax_require_defined.m4 | 37 ++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 m4/ax_append_compile_flags.m4 create mode 100644 m4/ax_append_flag.m4 create mode 100644 m4/ax_check_compile_flag.m4 create mode 100644 m4/ax_require_defined.m4 diff --git a/configure.ac b/configure.ac index 852c61f0e..2bb30edb3 100644 --- a/configure.ac +++ b/configure.ac @@ -26,6 +26,16 @@ AC_PROG_CXX RW_PROG_CXX_WORKS AM_CONDITIONAL([CXX_WORKS], [test "x$rw_cv_prog_cxx_works" = "xyes"]) +# Detect warning flags supported by the compiler, append them to WARN_CFLAGS. +# +# Pass -Werror as an extra flag during the test: this is needed to make the +# -Wunknown-warning-option diagnostic fatal with clang. +AX_APPEND_COMPILE_FLAGS([ dnl + -Wall dnl + ], + [WARN_CFLAGS], + [-Werror]) + # Checks for programs. AC_PROG_GREP AC_PROG_MAKE_SET @@ -1038,7 +1048,7 @@ AM_CONDITIONAL([BUILD_LIB_SESSIOND_COMM], [test x$build_lib_sessiond_comm = xyes AM_CONDITIONAL([BUILD_LIB_TESTPOINT], [test x$build_lib_testpoint = xyes]) AM_CONDITIONAL([BUILD_LIB_UST_CONSUMER], [test x$build_lib_ust_consumer = xyes]) -AM_CFLAGS="-Wall -fno-strict-aliasing $PTHREAD_CFLAGS" +AM_CFLAGS="${WARN_CFLAGS} -fno-strict-aliasing $PTHREAD_CFLAGS" AC_SUBST(AM_CFLAGS) # The order in which the include folders are searched is important. diff --git a/m4/ax_append_compile_flags.m4 b/m4/ax_append_compile_flags.m4 new file mode 100644 index 000000000..9c856356c --- /dev/null +++ b/m4/ax_append_compile_flags.m4 @@ -0,0 +1,46 @@ +# ============================================================================ +# https://www.gnu.org/software/autoconf-archive/ax_append_compile_flags.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_APPEND_COMPILE_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# For every FLAG1, FLAG2 it is checked whether the compiler works with the +# flag. If it does, the flag is added FLAGS-VARIABLE +# +# If FLAGS-VARIABLE is not specified, the current language's flags (e.g. +# CFLAGS) is used. During the check the flag is always added to the +# current language's flags. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: This macro depends on the AX_APPEND_FLAG and +# AX_CHECK_COMPILE_FLAG. Please keep this macro in sync with +# AX_APPEND_LINK_FLAGS. +# +# LICENSE +# +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 7 + +AC_DEFUN([AX_APPEND_COMPILE_FLAGS], +[AX_REQUIRE_DEFINED([AX_CHECK_COMPILE_FLAG]) +AX_REQUIRE_DEFINED([AX_APPEND_FLAG]) +for flag in $1; do + AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3], [$4]) +done +])dnl AX_APPEND_COMPILE_FLAGS diff --git a/m4/ax_append_flag.m4 b/m4/ax_append_flag.m4 new file mode 100644 index 000000000..dd6d8b614 --- /dev/null +++ b/m4/ax_append_flag.m4 @@ -0,0 +1,50 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_append_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE]) +# +# DESCRIPTION +# +# FLAG is appended to the FLAGS-VARIABLE shell variable, with a space +# added in between. +# +# If FLAGS-VARIABLE is not specified, the current language's flags (e.g. +# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains +# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly +# FLAG. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 8 + +AC_DEFUN([AX_APPEND_FLAG], +[dnl +AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_SET_IF +AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])]) +AS_VAR_SET_IF(FLAGS,[ + AS_CASE([" AS_VAR_GET(FLAGS) "], + [*" $1 "*], [AC_RUN_LOG([: FLAGS already contains $1])], + [ + AS_VAR_APPEND(FLAGS,[" $1"]) + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) + ], + [ + AS_VAR_SET(FLAGS,[$1]) + AC_RUN_LOG([: FLAGS="$FLAGS"]) + ]) +AS_VAR_POPDEF([FLAGS])dnl +])dnl AX_APPEND_FLAG diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 new file mode 100644 index 000000000..bd753b34d --- /dev/null +++ b/m4/ax_check_compile_flag.m4 @@ -0,0 +1,53 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/m4/ax_require_defined.m4 b/m4/ax_require_defined.m4 new file mode 100644 index 000000000..17c3eab7d --- /dev/null +++ b/m4/ax_require_defined.m4 @@ -0,0 +1,37 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_require_defined.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_REQUIRE_DEFINED(MACRO) +# +# DESCRIPTION +# +# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have +# been defined and thus are available for use. This avoids random issues +# where a macro isn't expanded. Instead the configure script emits a +# non-fatal: +# +# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found +# +# It's like AC_REQUIRE except it doesn't expand the required macro. +# +# Here's an example: +# +# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) +# +# LICENSE +# +# Copyright (c) 2014 Mike Frysinger +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 2 + +AC_DEFUN([AX_REQUIRE_DEFINED], [dnl + m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) +])dnl AX_REQUIRE_DEFINED -- 2.34.1 From fa3f8be960428e090b9576e3c67f0db33ed369d5 Mon Sep 17 00:00:00 2001 From: Simon Marchi Date: Tue, 11 Feb 2020 17:29:09 -0500 Subject: [PATCH 12/16] configure: add --enable-Werror MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit If one wants to build with -Werror, it's not possible to simply configure with -Werror in the CFLAGS. This makes a bunch of configure checks fail, which would have otherwise passed. This patch adds an --enable-Werror option to configure, which has the effect of adding -Werror to AM_CFLAGS. It therefore ends up in the CFLAGS used to build the project, but it doesn't interfere with the configure checks. Signed-off-by: Simon Marchi Change-Id: I18c33125c717305aac8f1d8a19fee7e065d70c31 Signed-off-by: Jérémie Galarneau --- configure.ac | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/configure.ac b/configure.ac index 2bb30edb3..0614b270e 100644 --- a/configure.ac +++ b/configure.ac @@ -36,6 +36,14 @@ AX_APPEND_COMPILE_FLAGS([ dnl [WARN_CFLAGS], [-Werror]) +# When given, add -Werror to WARN_CFLAGS. +AC_ARG_ENABLE([Werror], + [AS_HELP_STRING([--enable-Werror], [Treat compiler warnings as errors.])] +) +AS_IF([test "x$enable_Werror" = "xyes"], + [WARN_CFLAGS="${WARN_CFLAGS} -Werror"] +) + # Checks for programs. AC_PROG_GREP AC_PROG_MAKE_SET -- 2.34.1 From 6016eb62c604641ad1e655373a0468256b9743ce Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 17 Feb 2020 19:32:54 -0500 Subject: [PATCH 13/16] Fix: relayd: unchecked return value when opening relay socket MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit fd_tracker_open_unsuspendable_fd may fail because the underlying socket() call fails or there may be too many open file descriptors at the time of the call. In both cases, these errors must be logged and handled. Signed-off-by: Jérémie Galarneau Change-Id: I8b8c4fcc9de08746a91778b58c74b2118e98667b --- src/bin/lttng-relayd/main.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bin/lttng-relayd/main.c b/src/bin/lttng-relayd/main.c index cbd9e9cc7..7f7c0369a 100644 --- a/src/bin/lttng-relayd/main.c +++ b/src/bin/lttng-relayd/main.c @@ -1022,7 +1022,11 @@ static struct lttcomm_sock *relay_socket_create(struct lttng_uri *uri, ret = fd_tracker_open_unsuspendable_fd(the_fd_tracker, &sock_fd, (const char **) (formated_name ? &formated_name : NULL), 1, create_sock, sock); - free(formated_name); + if (ret) { + PERROR("Failed to open \"%s\" relay socket", + formated_name ?: "Unknown"); + goto error; + } DBG("Listening on %s socket %d", name, sock->fd); ret = sock->ops->bind(sock); @@ -1037,12 +1041,14 @@ static struct lttcomm_sock *relay_socket_create(struct lttng_uri *uri, } + free(formated_name); return sock; error: if (sock) { lttcomm_destroy_sock(sock); } + free(formated_name); return NULL; } -- 2.34.1 From 19efdf659e29ec34a27c304ccbf7ad6ff8e26337 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 17 Feb 2020 20:05:22 -0500 Subject: [PATCH 14/16] Fix: relayd: use of relay_session ref count before initialization MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The relay_session's reference count is used before it is initialized on multiple code paths of session_create(). The initialization of the reference count, mutexes, and intrusive data structure nodes are initialized earlier to make their use safe in the event of an error. Signed-off-by: Jérémie Galarneau Change-Id: I1be53ad88a3e783b85b4c568527df1a75ce58d3a --- src/bin/lttng-relayd/session.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/bin/lttng-relayd/session.c b/src/bin/lttng-relayd/session.c index daae9a55b..f40f70965 100644 --- a/src/bin/lttng-relayd/session.c +++ b/src/bin/lttng-relayd/session.c @@ -316,6 +316,17 @@ struct relay_session *session_create(const char *session_name, PERROR("Failed to allocate session"); goto error; } + + pthread_mutex_lock(&last_relay_session_id_lock); + session->id = ++last_relay_session_id; + pthread_mutex_unlock(&last_relay_session_id_lock); + + lttng_ht_node_init_u64(&session->session_n, session->id); + urcu_ref_init(&session->ref); + CDS_INIT_LIST_HEAD(&session->recv_list); + pthread_mutex_init(&session->lock, NULL); + pthread_mutex_init(&session->recv_list_lock, NULL); + if (lttng_strncpy(session->session_name, session_name, sizeof(session->session_name))) { WARN("Session name exceeds maximal allowed length"); @@ -342,17 +353,8 @@ struct relay_session *session_create(const char *session_name, goto error; } - pthread_mutex_lock(&last_relay_session_id_lock); - session->id = ++last_relay_session_id; - pthread_mutex_unlock(&last_relay_session_id_lock); - session->major = major; session->minor = minor; - lttng_ht_node_init_u64(&session->session_n, session->id); - urcu_ref_init(&session->ref); - CDS_INIT_LIST_HEAD(&session->recv_list); - pthread_mutex_init(&session->lock, NULL); - pthread_mutex_init(&session->recv_list_lock, NULL); session->live_timer = live_timer; session->snapshot = snapshot; -- 2.34.1 From b2081a0f39bce45b3d1cf063308a9b77712f1a9f Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 17 Feb 2020 20:13:49 -0500 Subject: [PATCH 15/16] Fix: directory-handle: use of free'd handle on fstat() error MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit On an error to fstat a directory handle, a directory handle is released and is still initialized as if the error had not occurred. Return NULL early from lttng_directory_handle_create_from_dirfd() to prevent those erroneous accesses. Signed-off-by: Jérémie Galarneau Change-Id: Ia2a3bb1cfe8c90d3f7f87a68286a9b8524694d3c --- src/common/compat/directory-handle.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/compat/directory-handle.c b/src/common/compat/directory-handle.c index 9d2fed463..a790c5b79 100644 --- a/src/common/compat/directory-handle.c +++ b/src/common/compat/directory-handle.c @@ -164,6 +164,8 @@ struct lttng_directory_handle *lttng_directory_handle_create_from_dirfd( if (ret) { PERROR("Failed to fstat directory file descriptor %i", dirfd); lttng_directory_handle_release(&handle->ref); + handle = NULL; + goto end; } } else { handle->directory_inode = RESERVED_AT_FDCWD_INO; -- 2.34.1 From d3a684eebfe788a7527368d65d7f020bdd1e61d9 Mon Sep 17 00:00:00 2001 From: Jonathan Rajotte Date: Fri, 28 Feb 2020 15:20:10 -0500 Subject: [PATCH 16/16] SoW-2019-0002: Dynamic Snapshot Revision 2 --- .gitignore | 17 +- configure.ac | 20 +- doc/examples/trigger-on-event/README.md | 56 + doc/examples/trigger-on-event/demo.sh | 20 + .../trigger-on-event/instrumented-app.c | 36 + .../trigger-on-event/notification-client.c | 220 ++ .../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 | 107 + .../performance/perform-experience.sh | 61 + .../performance/performance.c | 10 + .../performance/performance.h | 39 + .../trigger-on-event/performance/producer.c | 57 + doc/examples/trigger-on-event/tp.c | 10 + doc/examples/trigger-on-event/tp.h | 28 + doc/man/Makefile.am | 6 +- doc/man/lttng-add-trigger.1.txt | 134 ++ doc/man/lttng-list-triggers.1.txt | 37 + doc/man/lttng-remove-trigger.1.txt | 38 + extras/bindings/swig/python/lttng.i.in | 3 + include/Makefile.am | 29 +- include/lttng/action/action-internal.h | 29 + include/lttng/action/action.h | 15 +- include/lttng/action/group-internal.h | 39 + include/lttng/action/group.h | 55 + include/lttng/action/notify-internal.h | 5 + .../lttng/action/rotate-session-internal.h | 39 + include/lttng/action/rotate-session.h | 53 + .../lttng/action/snapshot-session-internal.h | 39 + include/lttng/action/snapshot-session.h | 73 + include/lttng/action/start-session-internal.h | 39 + include/lttng/action/start-session.h | 53 + include/lttng/action/stop-session-internal.h | 39 + include/lttng/action/stop-session.h | 53 + include/lttng/condition/condition-internal.h | 9 +- include/lttng/condition/condition.h | 1 + include/lttng/condition/event-rule-internal.h | 69 + include/lttng/condition/event-rule.h | 83 + include/lttng/domain-internal.h | 35 + include/lttng/event-internal.h | 15 + .../lttng/event-rule/event-rule-internal.h | 152 ++ include/lttng/event-rule/event-rule.h | 77 + include/lttng/event-rule/kprobe-internal.h | 63 + include/lttng/event-rule/kprobe.h | 72 + include/lttng/event-rule/kretprobe-internal.h | 60 + include/lttng/event-rule/kretprobe.h | 72 + include/lttng/event-rule/syscall-internal.h | 50 + include/lttng/event-rule/syscall.h | 88 + .../lttng/event-rule/tracepoint-internal.h | 81 + include/lttng/event-rule/tracepoint.h | 199 ++ include/lttng/event-rule/uprobe-internal.h | 49 + include/lttng/event-rule/uprobe.h | 88 + include/lttng/lttng.h | 12 + include/lttng/snapshot-internal.h | 2 +- include/lttng/snapshot.h | 40 +- include/lttng/trigger/trigger-internal.h | 121 +- include/lttng/trigger/trigger.h | 110 +- include/lttng/userspace-probe-internal.h | 14 + rename.sh | 13 + src/bin/lttng-crash/lttng-crash.c | 8 +- src/bin/lttng-relayd/cmd-2-2.c | 2 + src/bin/lttng-sessiond/Makefile.am | 3 +- src/bin/lttng-sessiond/action-executor.c | 681 ++++++ src/bin/lttng-sessiond/action-executor.h | 33 + src/bin/lttng-sessiond/agent-thread.c | 10 + src/bin/lttng-sessiond/agent.c | 153 +- src/bin/lttng-sessiond/agent.h | 26 +- src/bin/lttng-sessiond/client.c | 119 +- src/bin/lttng-sessiond/cmd.c | 279 ++- src/bin/lttng-sessiond/cmd.h | 7 +- src/bin/lttng-sessiond/dispatch.c | 40 +- src/bin/lttng-sessiond/event.c | 226 +- src/bin/lttng-sessiond/event.h | 5 + src/bin/lttng-sessiond/globals.c | 4 + src/bin/lttng-sessiond/health-sessiond.h | 1 + src/bin/lttng-sessiond/kernel.c | 512 +++- src/bin/lttng-sessiond/kernel.h | 7 + src/bin/lttng-sessiond/lttng-sessiond.h | 1 + src/bin/lttng-sessiond/main.c | 12 +- src/bin/lttng-sessiond/modprobe.c | 101 +- .../notification-thread-commands.c | 190 +- .../notification-thread-commands.h | 54 + .../notification-thread-events.c | 2138 ++++++++++++----- .../notification-thread-events.h | 4 + .../notification-thread-internal.h | 163 +- src/bin/lttng-sessiond/notification-thread.c | 137 +- src/bin/lttng-sessiond/notification-thread.h | 75 +- src/bin/lttng-sessiond/rotate.c | 3 + src/bin/lttng-sessiond/sessiond-config.c | 2 +- src/bin/lttng-sessiond/thread.c | 23 +- src/bin/lttng-sessiond/trace-kernel.c | 218 +- src/bin/lttng-sessiond/trace-kernel.h | 28 + src/bin/lttng-sessiond/trace-ust.c | 2 + src/bin/lttng-sessiond/ust-abi-internal.h | 26 + src/bin/lttng-sessiond/ust-app.c | 631 ++++- src/bin/lttng-sessiond/ust-app.h | 36 + src/bin/lttng-sessiond/ust-ctl-internal.h | 8 + src/bin/lttng-sessiond/utils.c | 23 + src/bin/lttng-sessiond/utils.h | 2 + src/bin/lttng/Makefile.am | 8 +- src/bin/lttng/command.h | 3 + src/bin/lttng/commands/add_context.c | 84 +- src/bin/lttng/commands/add_trigger.c | 1727 +++++++++++++ src/bin/lttng/commands/create.c | 4 +- src/bin/lttng/commands/disable_events.c | 2 +- src/bin/lttng/commands/enable_events.c | 417 +--- src/bin/lttng/commands/list_triggers.c | 541 +++++ src/bin/lttng/commands/remove_trigger.c | 131 + src/bin/lttng/commands/track-untrack.c | 2 +- src/bin/lttng/commands/view.c | 4 +- src/bin/lttng/conf.c | 2 +- src/bin/lttng/lttng.c | 5 +- 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 | 40 +- src/common/action.c | 111 - src/common/actions/action.c | 254 ++ src/common/actions/group.c | 345 +++ src/common/actions/notify.c | 66 + .../{notify.c => actions/notify.c.orig} | 24 + src/common/actions/rotate-session.c | 265 ++ src/common/actions/snapshot-session.c | 417 ++++ src/common/actions/start-session.c | 266 ++ src/common/actions/stop-session.c | 266 ++ src/common/argpar/Makefile.am | 3 + src/common/argpar/argpar.c | 746 ++++++ src/common/argpar/argpar.h | 330 +++ src/common/buffer-view.c | 42 + src/common/buffer-view.h | 21 +- src/common/{ => conditions}/buffer-usage.c | 8 +- src/common/{ => conditions}/condition.c | 39 +- src/common/conditions/event-rule.c | 409 ++++ .../{ => 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 | 8 + src/common/event-rule-kprobe.c | 522 ++++ src/common/event-rule-kretprobe.c | 164 ++ src/common/event-rule-syscall.c | 492 ++++ src/common/event-rule-tracepoint.c | 1125 +++++++++ src/common/event-rule-uprobe.c | 420 ++++ src/common/event-rule.c | 333 +++ .../lttng-ctl => common}/filter/Makefile.am | 0 .../lttng-ctl => common}/filter/filter-ast.h | 6 +- .../filter/filter-bytecode.h | 0 .../filter/filter-grammar-test.c | 0 .../lttng-ctl => common}/filter/filter-ir.h | 0 .../filter/filter-lexer.l | 0 .../filter/filter-parser.y | 141 +- .../filter/filter-symbols.h | 0 .../filter/filter-visitor-generate-bytecode.c | 0 .../filter/filter-visitor-generate-ir.c | 4 +- ...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 | 17 +- src/common/kernel-ctl/kernel-ctl.h | 8 +- src/common/kernel-ctl/kernel-ioctl.h | 7 + src/common/lttng-elf.c | 2 +- src/common/lttng-kernel.h | 18 + src/common/notification.c | 3 +- src/common/relayd/relayd.c | 2 +- src/common/runas.c | 121 + src/common/runas.h | 6 + src/common/sessiond-comm/sessiond-comm.h | 1 + src/common/snapshot.c | 184 ++ src/common/snapshot.h | 47 + src/common/trigger.c | 608 ++++- src/common/unix.c | 18 +- src/common/userspace-probe.c | 141 ++ 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/lttng-ctl-health.c | 1 + src/lib/lttng-ctl/lttng-ctl-helper.h | 17 + src/lib/lttng-ctl/lttng-ctl.c | 291 ++- src/lib/lttng-ctl/snapshot.c | 133 +- tests/regression/Makefile.am | 15 +- tests/regression/kernel/select_poll_epoll.c | 53 +- tests/regression/tools/Makefile.am | 2 +- tests/regression/tools/health/health_exit.c | 19 + tests/regression/tools/health/health_fail.c | 19 + tests/regression/tools/health/health_stall.c | 19 + tests/regression/tools/live/live_test.c | 16 +- .../regression/tools/notification/Makefile.am | 6 +- .../tools/notification/base_client.c | 3 +- .../tools/notification/consumer_testpoints.c | 2 + .../tools/notification/notification.c | 1346 +++++++++-- .../test_notification_kernel_buffer_usage | 126 + .../test_notification_kernel_error | 115 + .../test_notification_kernel_instrumentation | 125 + .../test_notification_kernel_syscall | 125 + .../test_notification_kernel_userspace_probe | 116 + .../test_notification_ust_buffer_usage | 106 + .../notification/test_notification_ust_error | 85 + ...ication_ust_event_rule_condition_exclusion | 85 + .../regression/tools/rotation/schedule_api.c | 22 +- tests/regression/tools/trigger/Makefile.am | 55 + tests/regression/tools/trigger/base_client.c | 252 ++ .../tools/trigger/consumer_testpoints.c | 147 ++ .../tools/trigger/test_add_trigger_cli | 330 +++ .../tools/trigger/test_list_triggers_cli | 301 +++ .../tools/trigger/test_remove_trigger_cli | 110 + .../test_trigger_kernel} | 20 +- .../test_trigger_ust} | 22 +- tests/regression/tools/trigger/trigger.c | 729 ++++++ .../lttng-ust-clock-override-test.c | 1 + tests/regression/ust/fork/fork.c | 2 +- .../lttng-ust-getcpu-override-test.c | 3 +- tests/regression/ust/high-throughput/main.c | 4 +- tests/regression/ust/low-throughput/tp.h | 2 +- tests/regression/ust/multi-lib/callsites.c | 4 +- .../regression/ust/multi-lib/multi-lib-test.c | 15 +- tests/unit/Makefile.am | 20 +- tests/unit/test_buffer_view.c | 50 + tests/unit/test_condition.c | 108 + tests/unit/test_directory_handle.c | 3 +- tests/unit/test_event_rule.c | 152 ++ tests/unit/test_fd_tracker.c | 2 +- ..._relayd_backward_compat_group_by_session.c | 12 +- tests/unit/test_session.c | 4 +- tests/unit/test_utils_expand_path.c | 14 +- tests/unit/test_utils_parse_size_suffix.c | 4 +- tests/unit/test_utils_parse_time_suffix.c | 4 +- tests/utils/tap/tap.c | 10 +- tests/utils/tap/tap.h | 6 +- .../gen-syscall-events-callstack/Makefile.am | 2 +- .../gen-syscall-events/gen-syscall-events.c | 53 +- .../testapp/gen-ust-tracef/gen-ust-tracef.c | 2 +- tests/utils/utils.sh | 28 +- 245 files changed, 24668 insertions(+), 1981 deletions(-) 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/tp.c create mode 100644 doc/examples/trigger-on-event/tp.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-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 rename.sh 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 delete mode 100644 src/common/action.c create mode 100644 src/common/actions/action.c create mode 100644 src/common/actions/group.c create mode 100644 src/common/actions/notify.c rename src/common/{notify.c => actions/notify.c.orig} (59%) 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 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-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 create mode 100644 src/common/event-rule.c rename src/{lib/lttng-ctl => common}/filter/Makefile.am (100%) rename src/{lib/lttng-ctl => common}/filter/filter-ast.h (95%) rename src/{lib/lttng-ctl => common}/filter/filter-bytecode.h (100%) rename src/{lib/lttng-ctl => common}/filter/filter-grammar-test.c (100%) rename src/{lib/lttng-ctl => common}/filter/filter-ir.h (100%) rename src/{lib/lttng-ctl => common}/filter/filter-lexer.l (100%) rename src/{lib/lttng-ctl => common}/filter/filter-parser.y (82%) rename src/{lib/lttng-ctl => common}/filter/filter-symbols.h (100%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-generate-bytecode.c (100%) rename src/{lib/lttng-ctl => common}/filter/filter-visitor-generate-ir.c (99%) 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 create mode 100755 tests/regression/tools/notification/test_notification_kernel_buffer_usage 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_error create mode 100755 tests/regression/tools/notification/test_notification_ust_event_rule_condition_exclusion 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 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/unit/test_buffer_view.c create mode 100644 tests/unit/test_condition.c create mode 100644 tests/unit/test_event_rule.c diff --git a/.gitignore b/.gitignore index 34f133a25..b7a946c76 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 @@ -158,6 +162,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/configure.ac b/configure.ac index 0614b270e..2ea9571ff 100644 --- a/configure.ac +++ b/configure.ac @@ -32,6 +32,13 @@ AM_CONDITIONAL([CXX_WORKS], [test "x$rw_cv_prog_cxx_works" = "xyes"]) # -Wunknown-warning-option diagnostic fatal with clang. AX_APPEND_COMPILE_FLAGS([ dnl -Wall dnl + dnl We currently get this warning when building with Clang: + dnl + dnl /usr/include/setjmp.h:54:12: error: declaration of built-in function '__sigsetjmp' requires the declaration of the 'jmp_buf' type, commonly provided in the header . [-Werror,-Wincomplete-setjmp-declaration] + dnl extern int __sigsetjmp (struct __jmp_buf_tag __env[1], int __savemask) __THROWNL; + dnl ^ + -Wno-incomplete-setjmp-declaration dnl + -Wdiscarded-qualifiers dnl ], [WARN_CFLAGS], [-Werror]) @@ -1059,13 +1066,17 @@ AM_CONDITIONAL([BUILD_LIB_UST_CONSUMER], [test x$build_lib_ust_consumer = xyes]) AM_CFLAGS="${WARN_CFLAGS} -fno-strict-aliasing $PTHREAD_CFLAGS" AC_SUBST(AM_CFLAGS) +# This is set even though it is empty, so Makefiles can do "AM_LDFLAGS += ...". +AM_LDFLAGS="" +AC_SUBST(AM_LDFLAGS) + # The order in which the include folders are searched is important. # The top_builddir should always be searched first in the event that a build # time generated file is included. An example of this is the "version.i" file. # 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" @@ -1083,6 +1094,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) @@ -1099,6 +1113,7 @@ AC_CONFIG_FILES([ extras/core-handler/Makefile src/Makefile src/common/Makefile + src/common/argpar/Makefile src/common/kernel-ctl/Makefile src/common/kernel-consumer/Makefile src/common/consumer/Makefile @@ -1113,9 +1128,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 @@ -1150,6 +1165,7 @@ 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/ust/Makefile tests/regression/ust/nprocesses/Makefile tests/regression/ust/high-throughput/Makefile diff --git a/doc/examples/trigger-on-event/README.md b/doc/examples/trigger-on-event/README.md new file mode 100644 index 000000000..2c184f727 --- /dev/null +++ b/doc/examples/trigger-on-event/README.md @@ -0,0 +1,56 @@ +# Trigger notification exemple + +## Description +This exemple is made-up of three executables. + +### `notification-client` + +``` +Usage: notification-client TRIGGER_NAME +``` + +A simple client that subscribes to the notifications emitted by the `TRIGGER_NAME` trigger. + +### `instrumented-app` + +An application that emits the `trigger_exemple: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_exemple:my_event` event occurs. + +Once the trigger has been setup, the notification-client is launched to print all notifications emitted by the `demo_trigger` trigger. + +## Building + +Simply run the included Makefile. + +## Running the exemple + +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_exemple:my_event user-space event +Trigger registered successfully. +Subscribed to notifications of trigger "demo_trigger" +[02-14-2020] 18:13:34.779766 - Received notification of event rule trigger "demo_trigger" +[02-14-2020] 18:13:36.779888 - Received notification of event rule trigger "demo_trigger" +[02-14-2020] 18:13:38.780514 - Received notification of event rule trigger "demo_trigger" +[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_exemple:my_event" +[02-14-2020] 18:13:36.779693 - Tracing event "trigger_exemple:my_event" +[02-14-2020] 18:13:38.780010 - Tracing event "trigger_exemple:my_event" +[02-14-2020] 18:13:40.780286 - Tracing event "trigger_exemple: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..61623fbb8 --- /dev/null +++ b/doc/examples/trigger-on-event/demo.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright (C) 2020 Jérémie Galarneau +# +# SPDX-License-Identifier: MIT + +EVENT_NAME=trigger_exemple:my_event +TRIGGER_NAME=demo_trigger + +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 + +./notification-client $TRIGGER_NAME + 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..24900567d --- /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 "tp.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_exemple:my_event\"\n", time_str, tv.tv_usec); + + tracepoint(trigger_exemple, 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..217b5c7d0 --- /dev/null +++ b/doc/examples/trigger-on-event/notification-client.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#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 print_notification(struct lttng_notification *notification) +{ + int ret = 0; + const struct lttng_condition *condition = + lttng_notification_get_condition(notification); + 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); + 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, subcription_count = 0; + enum lttng_trigger_status trigger_status; + struct lttng_notification_channel *notification_channel = NULL; + + if (argc != 2) { + fprintf(stderr, "Missing trigger name\n"); + fprintf(stderr, "Usage: notification-client TRIGGER_NAME\n"); + 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; + enum lttng_trigger_status trigger_status; + + lttng_trigger_get_name(trigger, &trigger_name); + if (strcmp(trigger_name, argv[1])) { + 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 (;;) { + 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..5058af841 --- /dev/null +++ b/doc/examples/trigger-on-event/performance/generate-graph.sh @@ -0,0 +1,107 @@ +#!/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 ); + hz=${S/.000/} + + 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 ); + 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 ); + 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/tp.c b/doc/examples/trigger-on-event/tp.c new file mode 100644 index 000000000..e1745ed61 --- /dev/null +++ b/doc/examples/trigger-on-event/tp.c @@ -0,0 +1,10 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#define TRACEPOINT_DEFINE +#define TRACEPOINT_CREATE_PROBES +#include "tp.h" diff --git a/doc/examples/trigger-on-event/tp.h b/doc/examples/trigger-on-event/tp.h new file mode 100644 index 000000000..ae4593a46 --- /dev/null +++ b/doc/examples/trigger-on-event/tp.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: MIT + * + */ + +#undef TRACEPOINT_PROVIDER +#define TRACEPOINT_PROVIDER trigger_exemple + +#undef TRACEPOINT_INCLUDE +#define TRACEPOINT_INCLUDE "./tp.h" + +#if !defined(_TRACEPOINT_TRIGGER_EXEMPLE_H) || defined(TRACEPOINT_HEADER_MULTI_READ) +#define _TRACEPOINT_TRIGGER_EXEMPLE_H + +#include + +TRACEPOINT_EVENT(trigger_exemple, my_event, + TP_ARGS(int, iteration), + TP_FIELDS( + ctf_integer(uint64_t, iteration, iteration) + ) +) + +#endif /* _TRACEPOINT_TRIGGER_EXEMPLE_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..393e8c920 --- /dev/null +++ b/doc/man/lttng-add-trigger.1.txt @@ -0,0 +1,134 @@ +lttng-add-trigger(1) +===================== +:revdate: 17 January 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. + +[[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. + +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/extras/bindings/swig/python/lttng.i.in b/extras/bindings/swig/python/lttng.i.in index e99c9aecf..b6b6f960e 100644 --- a/extras/bindings/swig/python/lttng.i.in +++ b/extras/bindings/swig/python/lttng.i.in @@ -29,6 +29,9 @@ multiple concurrent processes and threads. Tracing across multiple systems is al // This makes the typemap code useable with both Python 2 and 3. #define PyInt_AsSsize_t PyLong_AsSsize_t #endif + +// Avoid -Wmissing-declarations warning. +PyObject *SWIG_init(void); %} typedef unsigned int uint32_t; diff --git a/include/Makefile.am b/include/Makefile.am index ccd0d87d6..78f5467e3 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -121,11 +121,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 +143,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/tracepoint.h \ + lttng/event-rule/uprobe.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,6 +174,7 @@ noinst_HEADERS = \ lttng/endpoint-internal.h \ lttng/notification/channel-internal.h \ lttng/channel-internal.h \ + lttng/domain-internal.h \ lttng/event-internal.h \ lttng/rotate-internal.h \ lttng/ref-internal.h \ @@ -162,5 +183,11 @@ noinst_HEADERS = \ lttng/session-internal.h \ lttng/session-descriptor-internal.h \ lttng/tracker-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 787e9087a..af52a07ff 100644 --- a/include/lttng/action/action-internal.h +++ b/include/lttng/action/action-internal.h @@ -14,16 +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; }; @@ -32,6 +40,14 @@ struct lttng_action_comm { int8_t action_type; } LTTNG_PACKED; +LTTNG_HIDDEN +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 bool lttng_action_validate(struct lttng_action *action); @@ -47,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..42847953f --- /dev/null +++ b/include/lttng/action/group-internal.h @@ -0,0 +1,39 @@ +/* + * 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..a49660d27 --- /dev/null +++ b/include/lttng/action/group.h @@ -0,0 +1,55 @@ +/* + * 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_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/notify-internal.h b/include/lttng/action/notify-internal.h index e6cb174ce..a9d50612d 100644 --- a/include/lttng/action/notify-internal.h +++ b/include/lttng/action/notify-internal.h @@ -15,4 +15,9 @@ struct lttng_action_notify { struct lttng_action parent; }; +LTTNG_HIDDEN +ssize_t lttng_action_notify_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **action); + #endif /* LTTNG_ACTION_NOTIFY_INTERNAL_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..b3389f729 --- /dev/null +++ b/include/lttng/action/rotate-session-internal.h @@ -0,0 +1,39 @@ +/* + * 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_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..c7112bba8 --- /dev/null +++ b/include/lttng/action/rotate-session.h @@ -0,0 +1,53 @@ +/* + * 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_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. + * + * 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..0a3e0e96c --- /dev/null +++ b/include/lttng/action/snapshot-session-internal.h @@ -0,0 +1,39 @@ +/* + * 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_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..eeb75e6e3 --- /dev/null +++ b/include/lttng/action/snapshot-session.h @@ -0,0 +1,73 @@ +/* + * 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_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. + * + * 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..496bee28c --- /dev/null +++ b/include/lttng/action/start-session-internal.h @@ -0,0 +1,39 @@ +/* + * 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_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..b9eda708c --- /dev/null +++ b/include/lttng/action/start-session.h @@ -0,0 +1,53 @@ +/* + * 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_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. + * + * 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..7a453ea06 --- /dev/null +++ b/include/lttng/action/stop-session-internal.h @@ -0,0 +1,39 @@ +/* + * 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_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..cf9eaab83 --- /dev/null +++ b/include/lttng/action/stop-session.h @@ -0,0 +1,53 @@ +/* + * 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_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. + * + * 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..78a206df3 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 { diff --git a/include/lttng/condition/event-rule-internal.h b/include/lttng/condition/event-rule-internal.h new file mode 100644 index 000000000..ce1004e61 --- /dev/null +++ b/include/lttng/condition/event-rule-internal.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte + * + * 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_CONDITION_event_rule_INTERNAL_H +#define LTTNG_CONDITION_event_rule_INTERNAL_H + +#include +#include +#include +#include + +struct lttng_condition_event_rule { + struct lttng_condition parent; + struct lttng_event_rule *rule; +}; + +struct lttng_condition_event_rule_comm { + /* length excludes its own length */ + uint32_t length; + /* rule */ + char payload[]; +} LTTNG_PACKED; + +struct lttng_evaluation_event_rule { + struct lttng_evaluation parent; + char *name; +}; + +struct lttng_evaluation_event_rule_comm { + uint32_t trigger_name_length; + /* Trigger name */ + char payload[]; +} LTTNG_PACKED; + + +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 char* trigger_name); + +LTTNG_HIDDEN +ssize_t lttng_evaluation_event_rule_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_evaluation **_evaluation); + +#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..2a536d34d --- /dev/null +++ b/include/lttng/condition/event-rule.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte + * + * 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_CONDITION_EVENT_RULE_H +#define LTTNG_CONDITION_EVENT_RULE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * 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); + +#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-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..265286ab1 --- /dev/null +++ b/include/lttng/event-rule/event-rule-internal.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * + * 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_EVENT_RULE_INTERNAL_H +#define LTTNG_EVENT_RULE_INTERNAL_H + +#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_filter_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; + /* Optional */ + 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 return 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 return NULL + * Caller DO NOT own the returned object + */ +LTTNG_HIDDEN +const struct lttng_filter_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); + +/* + * 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); + +LTTNG_HIDDEN +const char *lttng_event_rule_type_str(enum lttng_event_rule_type type); + +#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..71678b70b --- /dev/null +++ b/include/lttng/event-rule/event-rule.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_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, +}; + +/** + * Event rule describe a set of criteria to be used as a discriminant in regards + * to a set of events. + * + * Event rule have the following base properties: + * - the instrumentation type for the event rule, + * - tracepoint + * - syscall + * - probe + * - userspace-probe + * - the domain the event rule covers. + */ + +/* + * 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 (release) a 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..f3df8e495 --- /dev/null +++ b/include/lttng/event-rule/kprobe-internal.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_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..16f748821 --- /dev/null +++ b/include/lttng/event-rule/kprobe.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_EVENT_RULE_KPROBE_H +#define LTTNG_EVENT_RULE_KPROBE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * TODO: + */ +extern struct lttng_event_rule *lttng_event_rule_kprobe_create(void); + +/* + * Set the source of a kprobe 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_kprobe_set_source( + struct lttng_event_rule *rule, const char *source); + +/* + * Set the name of a kprobe 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_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..bf90906ca --- /dev/null +++ b/include/lttng/event-rule/kretprobe-internal.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_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..5ed08529a --- /dev/null +++ b/include/lttng/event-rule/kretprobe.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_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..47e8e0f70 --- /dev/null +++ b/include/lttng/event-rule/syscall-internal.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_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_filter_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..7d3bd1764 --- /dev/null +++ b/include/lttng/event-rule/syscall.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_EVENT_RULE_SYSCALL_H +#define LTTNG_EVENT_RULE_SYSCALL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * TODO: + */ +extern struct lttng_event_rule *lttng_event_rule_syscall_create(void); + +/* + * Set the pattern of a syscall event rule. + * + * Pattern can contains 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_syscall_set_pattern( + struct lttng_event_rule *rule, const char *pattern); + +/* + * Get the pattern of a tracecpoint 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. + * + * 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_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..e2432dde5 --- /dev/null +++ b/include/lttng/event-rule/tracepoint-internal.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_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_filter_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..8868828a3 --- /dev/null +++ b/include/lttng/event-rule/tracepoint.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_EVENT_RULE_TRACEPOINT_H +#define LTTNG_EVENT_RULE_TRACEPOINT_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * TODO: + */ +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 contains 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); + +/* + * Set the domain type 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_filter( + struct lttng_event_rule *rule, const char *expression); + +/* + * Get the domain type of a tracecpoint 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. + * + * 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, or LTTNG_EVENT_RULE_STATUS_UNSET if a domain type was not set prior + * to this call. + */ +extern enum lttng_event_rule_status +lttng_event_rule_tracepoint_get_exclusions_count( + const struct lttng_event_rule *rule, unsigned int *count); + +/* + * TODO: + * */ +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..c26c8c725 --- /dev/null +++ b/include/lttng/event-rule/uprobe-internal.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_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..dbe07bb8b --- /dev/null +++ b/include/lttng/event-rule/uprobe.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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_EVENT_RULE_UPROBE_H +#define LTTNG_EVENT_RULE_UPROBE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * TODO: + */ +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. + * + * 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 4557cf0d7..a3d518944 100644 --- a/include/lttng/lttng.h +++ b/include/lttng/lttng.h @@ -28,12 +28,24 @@ #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..64937661f 100644 --- a/include/lttng/trigger/trigger-internal.h +++ b/include/lttng/trigger/trigger-internal.h @@ -12,39 +12,146 @@ #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; +}; + +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 -const struct lttng_condition *lttng_trigger_get_const_condition( - const struct lttng_trigger *trigger); +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 +bool lttng_trigger_is_equal(const struct lttng_trigger *a, + const struct lttng_trigger *b); + +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); + +/* + * 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_action *lttng_trigger_get_const_action( +const struct lttng_credentials *lttng_trigger_get_credentials( const struct lttng_trigger *trigger); LTTNG_HIDDEN -bool lttng_trigger_validate(struct lttng_trigger *trigger); +void lttng_trigger_set_credentials( + struct lttng_trigger *trigger, uid_t uid, gid_t git); + +LTTNG_HIDDEN +void lttng_trigger_get(struct lttng_trigger *trigger); + +LTTNG_HIDDEN +void lttng_trigger_put(struct lttng_trigger *trigger); + +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); #endif /* LTTNG_TRIGGER_INTERNAL_H */ diff --git a/include/lttng/trigger/trigger.h b/include/lttng/trigger/trigger.h index feffc6a8f..823a02651 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. * @@ -52,6 +68,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 +81,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 +154,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..ef6a5726e 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,13 @@ 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_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/rename.sh b/rename.sh new file mode 100644 index 000000000..ded105d2a --- /dev/null +++ b/rename.sh @@ -0,0 +1,13 @@ +ag --ignore rename.sh -l "event-rule-uprobe-internal\.h" | xargs sed -i 's/event-rule-uprobe-internal\.h/uprobe-internal.h/g' +ag --ignore rename.sh -l "event-rule-kprobe-internal\.h" | xargs sed -i 's/event-rule-kprobe-internal\.h/kprobe-internal.h/g' +ag --ignore rename.sh -l "event-rule-kretprobe-internal\.h" | xargs sed -i 's/event-rule-kretprobe-internal\.h/kretprobe-internal.h/g' +ag --ignore rename.sh -l "event-rule-syscall-internal\.h" | xargs sed -i 's/event-rule-syscall-internal\.h/syscall-internal.h/g' +ag --ignore rename.sh -l "event-rule-tracepoint-internal\.h" | xargs sed -i 's/event-rule-tracepoint-internal\.h/tracepoint-internal.h/g' + +ag --ignore rename.sh -l "event-rule-uprobe\.h" | xargs sed -i 's/event-rule-uprobe\.h/uprobe.h/g' +ag --ignore rename.sh -l "event-rule-kprobe\.h" | xargs sed -i 's/event-rule-kprobe\.h/kprobe.h/g' +ag --ignore rename.sh -l "event-rule-kretprobe\.h" | xargs sed -i 's/event-rule-kretprobe\.h/kretprobe.h/g' +ag --ignore rename.sh -l "event-rule-syscall\.h" | xargs sed -i 's/event-rule-syscall\.h/syscall.h/g' +ag --ignore rename.sh -l "event-rule-tracepoint\.h" | xargs sed -i 's/event-rule-tracepoint\.h/tracepoint.h/g' +git add . +git commit --amend --no-edit diff --git a/src/bin/lttng-crash/lttng-crash.c b/src/bin/lttng-crash/lttng-crash.c index c0bf42c5a..98c52c59d 100644 --- a/src/bin/lttng-crash/lttng-crash.c +++ b/src/bin/lttng-crash/lttng-crash.c @@ -180,9 +180,9 @@ struct lttng_crash_layout { }; /* Variables */ -static char *progname, - *opt_viewer_path = NULL, - *opt_output_path = NULL; +static const char *progname; +static char *opt_viewer_path = NULL; +static char *opt_output_path = NULL; static char *input_path; @@ -296,7 +296,7 @@ static int parse_args(int argc, char **argv) } if (!opt_viewer_path) { - opt_viewer_path = DEFAULT_VIEWER; + opt_viewer_path = (char *) DEFAULT_VIEWER; } /* No leftovers, or more than one input path, print usage and quit */ diff --git a/src/bin/lttng-relayd/cmd-2-2.c b/src/bin/lttng-relayd/cmd-2-2.c index 09014be0f..8566ecfa8 100644 --- a/src/bin/lttng-relayd/cmd-2-2.c +++ b/src/bin/lttng-relayd/cmd-2-2.c @@ -10,6 +10,8 @@ #define _LGPL_SOURCE #include +#include "cmd-2-2.h" + #include #include diff --git a/src/bin/lttng-sessiond/Makefile.am b/src/bin/lttng-sessiond/Makefile.am index ee53655be..dd807125b 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 \ diff --git a/src/bin/lttng-sessiond/action-executor.c b/src/bin/lttng-sessiond/action-executor.c new file mode 100644 index 000000000..1c41e154f --- /dev/null +++ b/src/bin/lttng-sessiond/action-executor.c @@ -0,0 +1,681 @@ +/* + * 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 + +#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 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; + + assert(work_item->client_list); + + evaluation = lttng_evaluation_event_rule_create( + get_trigger_name(work_item->trigger)); + 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); + 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 listwith 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) +{ + 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), + }; + 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; + +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..60a740def --- /dev/null +++ b/src/bin/lttng-sessiond/action-executor.h @@ -0,0 +1,33 @@ +/* + * 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; + +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); + +#endif /* ACTION_EXECUTOR_H */ diff --git a/src/bin/lttng-sessiond/agent-thread.c b/src/bin/lttng-sessiond/agent-thread.c index c278ce95e..3587fd471 100644 --- a/src/bin/lttng-sessiond/agent-thread.c +++ b/src/bin/lttng-sessiond/agent-thread.c @@ -49,6 +49,8 @@ static void update_agent_app(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); @@ -74,6 +76,14 @@ static void update_agent_app(struct agent_app *app) session_put(session); } session_unlock_list(); + + /* 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->sock->fd); + } + rcu_read_unlock(); } /* diff --git a/src/bin/lttng-sessiond/agent.c b/src/bin/lttng-sessiond/agent.c index 1926b0820..0b95c4d1d 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: @@ -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. * @@ -1449,3 +1572,27 @@ void agent_update(struct agent *agt, int sock) 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 2d2d64255..ebb12628e 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; }; /* @@ -87,6 +91,12 @@ struct agent_event { struct lttng_filter_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; }; /* @@ -133,8 +143,10 @@ struct agent_event *agent_create_event(const char *name, 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(struct agent *agt, int sock); 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 ae0e02092..e381514e0 100644 --- a/src/bin/lttng-sessiond/client.c +++ b/src/bin/lttng-sessiond/client.c @@ -30,6 +30,7 @@ #include "utils.h" #include "manage-consumer.h" #include "clear.h" +#include "agent-thread.h" static bool is_root; @@ -182,7 +183,7 @@ static pid_t spawn_consumerd(struct consumer_data *consumer_data) case LTTNG_CONSUMER64_UST: { if (config.consumerd64_lib_dir.value) { - char *tmp; + const char *tmp; size_t tmplen; char *tmpnew; @@ -219,7 +220,7 @@ static pid_t spawn_consumerd(struct consumer_data *consumer_data) case LTTNG_CONSUMER32_UST: { if (config.consumerd32_lib_dir.value) { - char *tmp; + const char *tmp; size_t tmplen; char *tmpnew; @@ -532,7 +533,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; @@ -592,30 +592,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; @@ -710,6 +687,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); @@ -733,19 +711,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) { @@ -785,6 +773,8 @@ static int process_client_msg(struct command_ctx *cmd_ctx, int *sock, case LTTNG_ROTATE_SESSION: case LTTNG_ROTATION_GET_INFO: case LTTNG_SESSION_LIST_ROTATION_SCHEDULES: + case LTTNG_REGISTER_TRIGGER: + case LTTNG_LIST_TRIGGERS: break; default: /* Setup lttng message with no payload */ @@ -805,6 +795,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: @@ -883,7 +874,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; } @@ -928,14 +920,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; } @@ -1957,8 +1956,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: @@ -2071,6 +2095,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 5e522d6fe..f4c7390ef 100644 --- a/src/bin/lttng-sessiond/cmd.c +++ b/src/bin/lttng-sessiond/cmd.c @@ -27,6 +27,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -4283,8 +4290,94 @@ 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; + } + ret = LTTNG_OK; + 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; @@ -4318,9 +4411,95 @@ int cmd_register_trigger(struct command_ctx *cmd_ctx, int sock, goto end; } + /* 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; + } + + /* + * 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); + + /* 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); @@ -4363,14 +4542,110 @@ 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); + + /* TODO: forgotting this bited me for agent since the filter is + * genereted on this side. Wonder if we could find a way to detect when + * do it or not. + */ + + /* 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, -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 23d9d5e02..76052100d 100644 --- a/src/bin/lttng-sessiond/cmd.h +++ b/src/bin/lttng-sessiond/cmd.h @@ -125,10 +125,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..ea147ddf8 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,14 @@ 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 +390,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..6a7545dc0 100644 --- a/src/bin/lttng-sessiond/event.c +++ b/src/bin/lttng-sessiond/event.c @@ -12,6 +12,10 @@ #include #include +#include +#include +#include +#include #include #include #include @@ -27,6 +31,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. @@ -366,6 +371,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. * @@ -376,8 +395,6 @@ int event_agent_enable_all(struct ltt_ust_session *usess, struct lttng_filter_bytecode *filter ,char *filter_expression) { int ret; - struct agent_event *aevent; - struct lttng_ht_iter iter; assert(usess); @@ -389,13 +406,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; @@ -467,28 +478,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, +static int agent_enable(struct agent *agt, + struct lttng_event *event, struct lttng_filter_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 +542,113 @@ 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, + 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_filter_bytecode *filter_bytecode; + struct lttng_filter_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 = + copy_filter_bytecode(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..c7a849c0b 100644 --- a/src/bin/lttng-sessiond/event.h +++ b/src/bin/lttng-sessiond/event.h @@ -42,6 +42,11 @@ 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..6e111b63b 100644 --- a/src/bin/lttng-sessiond/globals.c +++ b/src/bin/lttng-sessiond/globals.c @@ -20,8 +20,12 @@ 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_ht *registered_ust_event_rule = 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 4ee4bea64..646da3ba8 100644 --- a/src/bin/lttng-sessiond/kernel.c +++ b/src/bin/lttng-sessiond/kernel.c @@ -15,11 +15,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" @@ -29,6 +38,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 @@ -39,9 +49,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. * @@ -213,6 +224,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. @@ -224,7 +273,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; @@ -261,8 +310,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); @@ -286,7 +334,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; @@ -332,7 +380,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); @@ -353,29 +401,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; @@ -388,7 +423,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; @@ -397,8 +433,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; } @@ -415,8 +450,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; @@ -425,8 +460,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; @@ -443,6 +478,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. @@ -512,7 +612,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; } @@ -669,6 +770,36 @@ error: return ret; } +/* + * Disable a kernel trigger. + */ +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 lttng_tracker_list *get_id_tracker_list( struct ltt_kernel_session *session, enum lttng_tracker_type tracker_type) @@ -1732,20 +1863,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: @@ -1781,6 +1929,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); @@ -1789,6 +1962,8 @@ void cleanup_kernel_tracer(void) } kernel_tracer_fd = -1; } + + DBG("Unloading kernel modules"); modprobe_remove_lttng_all(); free(syscall_table); @@ -1879,3 +2054,274 @@ 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_event_rule *rule, + 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 trigger; + + assert(rule); + + ret = trace_kernel_create_token_event_rule(rule, token, &event); + if (ret != LTTNG_OK) { + goto error; + } + + trace_kernel_init_trigger_from_event_rule(event->event_rule, &trigger); + trigger.id = event->token; + + fd = kernctl_create_trigger(kernel_tracer_trigger_group_fd, &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!", 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( + rule, creds, event->fd); + if (ret) { + goto add_callsite_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)", 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(event_rule, 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: + 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 4f3bedea5..d22a5b7a0 100644 --- a/src/bin/lttng-sessiond/kernel.h +++ b/src/bin/lttng-sessiond/kernel.h @@ -74,4 +74,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 66e80e75b..7bee25f88 100644 --- a/src/bin/lttng-sessiond/modprobe.c +++ b/src/bin/lttng-sessiond/modprobe.c @@ -30,59 +30,64 @@ #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[] = { - { "lttng-ring-buffer-client-discard" }, - { "lttng-ring-buffer-client-overwrite" }, - { "lttng-ring-buffer-metadata-client" }, - { "lttng-ring-buffer-client-mmap-discard" }, - { "lttng-ring-buffer-client-mmap-overwrite" }, - { "lttng-ring-buffer-metadata-mmap-client" }, + { (char *) "lttng-ring-buffer-client-discard" }, + { (char *) "lttng-ring-buffer-client-overwrite" }, + { (char *) "lttng-ring-buffer-metadata-client" }, + { (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 */ struct kern_modules_param kern_modules_probes_default[] = { - { "lttng-probe-asoc" }, - { "lttng-probe-block" }, - { "lttng-probe-btrfs" }, - { "lttng-probe-compaction" }, - { "lttng-probe-ext3" }, - { "lttng-probe-ext4" }, - { "lttng-probe-gpio" }, - { "lttng-probe-i2c" }, - { "lttng-probe-irq" }, - { "lttng-probe-jbd" }, - { "lttng-probe-jbd2" }, - { "lttng-probe-kmem" }, - { "lttng-probe-kvm" }, - { "lttng-probe-kvm-x86" }, - { "lttng-probe-kvm-x86-mmu" }, - { "lttng-probe-lock" }, - { "lttng-probe-module" }, - { "lttng-probe-napi" }, - { "lttng-probe-net" }, - { "lttng-probe-power" }, - { "lttng-probe-preemptirq" }, - { "lttng-probe-printk" }, - { "lttng-probe-random" }, - { "lttng-probe-rcu" }, - { "lttng-probe-regmap" }, - { "lttng-probe-regulator" }, - { "lttng-probe-rpm" }, - { "lttng-probe-sched" }, - { "lttng-probe-scsi" }, - { "lttng-probe-signal" }, - { "lttng-probe-skb" }, - { "lttng-probe-sock" }, - { "lttng-probe-statedump" }, - { "lttng-probe-sunrpc" }, - { "lttng-probe-timer" }, - { "lttng-probe-udp" }, - { "lttng-probe-vmscan" }, - { "lttng-probe-v4l2" }, - { "lttng-probe-workqueue" }, - { "lttng-probe-writeback" }, - { "lttng-probe-x86-irq-vectors" }, - { "lttng-probe-x86-exceptions" }, + { (char *) "lttng-probe-asoc" }, + { (char *) "lttng-probe-block" }, + { (char *) "lttng-probe-btrfs" }, + { (char *) "lttng-probe-compaction" }, + { (char *) "lttng-probe-ext3" }, + { (char *) "lttng-probe-ext4" }, + { (char *) "lttng-probe-gpio" }, + { (char *) "lttng-probe-i2c" }, + { (char *) "lttng-probe-irq" }, + { (char *) "lttng-probe-jbd" }, + { (char *) "lttng-probe-jbd2" }, + { (char *) "lttng-probe-kmem" }, + { (char *) "lttng-probe-kvm" }, + { (char *) "lttng-probe-kvm-x86" }, + { (char *) "lttng-probe-kvm-x86-mmu" }, + { (char *) "lttng-probe-lock" }, + { (char *) "lttng-probe-module" }, + { (char *) "lttng-probe-napi" }, + { (char *) "lttng-probe-net" }, + { (char *) "lttng-probe-power" }, + { (char *) "lttng-probe-preemptirq" }, + { (char *) "lttng-probe-printk" }, + { (char *) "lttng-probe-random" }, + { (char *) "lttng-probe-rcu" }, + { (char *) "lttng-probe-regmap" }, + { (char *) "lttng-probe-regulator" }, + { (char *) "lttng-probe-rpm" }, + { (char *) "lttng-probe-sched" }, + { (char *) "lttng-probe-scsi" }, + { (char *) "lttng-probe-signal" }, + { (char *) "lttng-probe-skb" }, + { (char *) "lttng-probe-sock" }, + { (char *) "lttng-probe-statedump" }, + { (char *) "lttng-probe-sunrpc" }, + { (char *) "lttng-probe-timer" }, + { (char *) "lttng-probe-udp" }, + { (char *) "lttng-probe-vmscan" }, + { (char *) "lttng-probe-v4l2" }, + { (char *) "lttng-probe-workqueue" }, + { (char *) "lttng-probe-writeback" }, + { (char *) "lttng-probe-x86-irq-vectors" }, + { (char *) "lttng-probe-x86-exceptions" }, }; /* dynamic 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..7a0b11ba8 100644 --- a/src/bin/lttng-sessiond/notification-thread-commands.c +++ b/src/bin/lttng-sessiond/notification-thread-commands.c @@ -17,7 +17,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 +53,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 +138,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 +163,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 +192,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 +217,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 +246,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 +268,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 +384,18 @@ 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); +} 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 73ec72f89..6e7ef34dd 100644 --- a/src/bin/lttng-sessiond/notification-thread-events.c +++ b/src/bin/lttng-sessiond/notification-thread-events.c @@ -19,12 +19,15 @@ #include #include #include +#include #include #include #include #include #include +#include #include +#include #include #include @@ -50,7 +53,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 +111,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,85 +121,17 @@ 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; +/* + * Facilities to carry the different notifications type in the action processing + * code path. + */ +struct lttng_trigger_notification { + union { + struct lttng_ust_trigger_notification *ust; + uint64_t *kernel; + } u; + uint64_t id; + enum lttng_domain_type type; }; struct channel_state_sample { @@ -258,20 +194,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 +276,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 +324,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 +340,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 +433,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 +466,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 +484,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 +516,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; } @@ -666,7 +689,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 +780,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 +813,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 +889,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 +925,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 +938,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 +992,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 +1004,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 +1026,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 +1049,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 +1073,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 +1102,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 +1117,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 +1144,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 +1210,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 +1246,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 +1278,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 +1293,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 +1524,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 = @@ -1762,6 +1911,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 +1935,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 +1958,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 +1967,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 +1981,292 @@ 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); + 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 +2301,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 +2345,26 @@ 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( +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 +2376,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 +2398,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 +2406,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 +2434,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 +2450,252 @@ 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 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 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); } - free(client_list); + 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; + } + } + 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); + + /* + * This element own the trigger object from now own, this is why there + * is no lttng_trigger_get here. + * This thread is now the owner of the trigger object. + */ + 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 +2703,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 +2726,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,47 +2758,56 @@ 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); + if (!lttng_trigger_is_equal(trigger, trigger_element->trigger)) { + continue; + } - assert(current_condition); - if (!lttng_condition_is_equal(condition, - current_condition)) { + DBG("[notification-thread] Removed trigger from channel_triggers_ht"); + cds_list_del(&trigger_element->node); + /* A trigger can only appear once per channel */ + break; + } + } + + 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; } - DBG("[notification-thread] Removed trigger from channel_triggers_ht"); - cds_list_del(&trigger_element->node); - /* A trigger can only appear once per channel */ + /* 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; } } - /* - * Remove and release the client list from - * notification_trigger_clients_ht. - */ - client_list = get_client_list_from_condition(state, condition); - assert(client_list); + 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); - cds_list_for_each_entry_safe(client_list_element, tmp, - &client_list->list, node) { - free(client_list_element); + /* 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: @@ -2327,11 +2885,82 @@ 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; + + cmd->reply_code = LTTNG_OK; + 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; @@ -2342,7 +2971,12 @@ int handle_notification_thread_command( } end: cds_list_del(&cmd->cmd_list_node); - lttng_waiter_wake_up(&cmd->reply_waiter); + if (cmd->is_async) { + free(cmd); + cmd = NULL; + } else { + lttng_waiter_wake_up(&cmd->reply_waiter); + } pthread_mutex_unlock(&handle->cmd_queue.lock); return ret; error_unlock: @@ -2355,12 +2989,6 @@ error: 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 +3012,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 +3050,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 +3103,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 +3114,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 +3168,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 +3186,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 +3222,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 +3309,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)", + ERR("[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,66 +3322,256 @@ 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; - } + status = CLIENT_TRANSMISSION_STATUS_COMPLETE; + } +end: + return status; +error: + 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, + enum lttng_notification_channel_status status) +{ + int ret; + struct lttng_notification_channel_command_reply reply = { + .status = (int8_t) status, + }; + struct lttng_notification_channel_message msg = { + .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_COMMAND_REPLY, + .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; - client->communication.outbound.queued_command_reply = false; - client->communication.outbound.dropped_notification = false; + transmission_status = client_flush_outgoing_queue(client); + ret = client_handle_transmission_status( + client, transmission_status, state); + if (ret) { + goto end; } - return 0; -error: - return -1; + 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_send_command_reply(struct notification_client *client, - struct notification_thread_state *state, - enum lttng_notification_channel_status status) +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_notification_channel_command_reply reply = { - .status = (int8_t) status, - }; - struct lttng_notification_channel_message msg = { - .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_COMMAND_REPLY, - .size = sizeof(reply), - }; - char buffer[sizeof(msg) + sizeof(reply)]; + 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 +3593,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 +3623,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 +3632,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 +3649,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 +3668,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 +3684,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 +3693,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 +3868,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 +3964,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 +4010,204 @@ 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; } +int handle_notification_thread_event(struct notification_thread_state *state, + int pipe, + enum lttng_domain_type domain) +{ + int ret; + struct lttng_ust_trigger_notification ust_notification; + uint64_t kernel_notification; + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + struct notification_trigger_tokens_ht_element *element; + struct lttng_trigger_notification notification; + void *reception_buffer; + size_t reception_size; + enum action_executor_status executor_status; + struct notification_client_list *client_list = NULL; + + notification.type = domain; + + switch(domain) { + case LTTNG_DOMAIN_UST: + reception_buffer = (void *) &ust_notification; + reception_size = sizeof(ust_notification); + notification.u.ust = &ust_notification; + break; + case LTTNG_DOMAIN_KERNEL: + reception_buffer = (void *) &kernel_notification; + reception_size = sizeof(kernel_notification); + notification.u.kernel = &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: + notification.id = ust_notification.id; + break; + case LTTNG_DOMAIN_KERNEL: + notification.id = kernel_notification; + break; + default: + assert(0); + } + + /* 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); + 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: + 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) @@ -3332,16 +4346,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 +4372,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 +4388,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 +4401,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..21f225525 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,156 @@ struct channel_info { struct rcu_head rcu_node; }; +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); + #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..a52756c5a 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->trigger_tokens_ht) { + ret = cds_lfht_destroy(state->trigger_tokens_ht, NULL); + assert(!ret); + } + if (state->triggers_by_name_ht) { + ret = cds_lfht_destroy(state->triggers_by_name_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->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->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->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..e23cc1a32 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,8 +55,20 @@ 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; + + pthread_mutex_t trigger_lock; }; /** @@ -49,9 +77,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 +132,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 +182,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 +192,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 +207,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 +232,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 +240,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/sessiond-config.c b/src/bin/lttng-sessiond/sessiond-config.c index d908da972..f0ab4ce04 100644 --- a/src/bin/lttng-sessiond/sessiond-config.c +++ b/src/bin/lttng-sessiond/sessiond-config.c @@ -31,7 +31,7 @@ struct sessiond_config sessiond_config_build_defaults = { .daemonize = false, .sig_parent = false, - .tracing_group_name.value = DEFAULT_TRACING_GROUP, + .tracing_group_name.value = (char *) DEFAULT_TRACING_GROUP, .kmod_probes_list.value = NULL, .kmod_extra_probes_list.value = NULL, 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 52f819b30..7ad8f2320 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 @@ -130,6 +140,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. * @@ -478,6 +511,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. * @@ -607,6 +796,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 b290ce492..88e356854 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; @@ -52,6 +57,18 @@ struct ltt_kernel_event { 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_filter_bytecode *filter; + struct lttng_userspace_probe_location *userspace_probe_location; +}; + /* Kernel channel */ struct ltt_kernel_channel { int fd; @@ -135,6 +152,10 @@ struct ltt_kernel_event *trace_kernel_find_event( 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. */ @@ -149,8 +170,14 @@ 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 @@ -162,6 +189,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 92fd0de81..ebb9ca474 100644 --- a/src/bin/lttng-sessiond/trace-ust.c +++ b/src/bin/lttng-sessiond/trace-ust.c @@ -981,6 +981,7 @@ int trace_ust_track_id(enum lttng_tracker_type tracker_type, } } if (should_update_apps && session->active) { + /* TODO: should tokens be affected by tracking? */ ust_app_global_update_all(session); } goto end; @@ -1081,6 +1082,7 @@ int trace_ust_untrack_id(enum lttng_tracker_type tracker_type, } } if (should_update_apps && session->active) { + /* TODO should tokens be affected by tracker */ ust_app_global_update_all(session); } goto end; diff --git a/src/bin/lttng-sessiond/ust-abi-internal.h b/src/bin/lttng-sessiond/ust-abi-internal.h index 99da583d5..ea33cd176 100644 --- a/src/bin/lttng-sessiond/ust-abi-internal.h +++ b/src/bin/lttng-sessiond/ust-abi-internal.h @@ -87,6 +87,29 @@ 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; + 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 { @@ -305,6 +328,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 cf74703c3..8fc0efdea 100644 --- a/src/bin/lttng-sessiond/ust-app.c +++ b/src/bin/lttng-sessiond/ust-app.c @@ -20,6 +20,13 @@ #include #include +#include +#include +#include +#include +#include +#include +#include #include #include "buffer-registry.h" @@ -34,6 +41,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 +321,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_event_rule_put(ua_token->event_rule); + free(ua_token); +} + /* * Release ust data object of the given stream. * @@ -921,6 +957,10 @@ void delete_ust_app(struct ust_app *app) ht_cleanup_push(app->ust_sessions_objd); ht_cleanup_push(app->ust_objd); + ustctl_release_object(sock, app->token_communication.handle); + + lttng_pipe_close(app->token_communication.trigger_event_pipe); + /* * Wait until we have deleted the application from the sock hash table * before closing this socket, otherwise an application could re-use the @@ -1019,7 +1059,7 @@ error_free: * Alloc new UST app channel. */ static -struct ust_app_channel *alloc_ust_app_channel(char *name, +struct ust_app_channel *alloc_ust_app_channel(const char *name, struct ust_app_session *ua_sess, struct lttng_ust_channel_attr *attr) { @@ -1125,6 +1165,41 @@ 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_event_rule *event_rule, uint64_t token) +{ + struct ust_app_token_event_rule *ua_token; + + 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; + } + + ua_token->enabled = 1; + ua_token->token = token; + lttng_ht_node_init_u64(&ua_token->node, token); + + /* Get reference of the event_rule */ + if (!lttng_event_rule_get(event_rule)) { + assert(0); + } + + ua_token->event_rule = event_rule; + ua_token->filter = lttng_event_rule_get_filter_bytecode(event_rule); + ua_token->exclusion = lttng_event_rule_generate_exclusions(event_rule); + + DBG3("UST app token event rule %" PRIu64 " allocated", ua_token->token); + + return ua_token; + +error: + return NULL; +} + /* * Alloc new UST app context. */ @@ -1165,36 +1240,13 @@ error: return NULL; } -/* - * Allocate a filter and copy the given original filter. - * - * Return allocated filter or NULL on error. - */ -static struct lttng_filter_bytecode *copy_filter_bytecode( - struct lttng_filter_bytecode *orig_f) -{ - struct lttng_filter_bytecode *filter = NULL; - - /* Copy filter bytecode */ - filter = zmalloc(sizeof(*filter) + orig_f->len); - if (!filter) { - PERROR("zmalloc alloc filter bytecode"); - goto error; - } - - memcpy(filter, orig_f, sizeof(*filter) + orig_f->len); - -error: - return filter; -} - /* * Create a liblttng-ust filter 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) + const struct lttng_filter_bytecode *orig_f) { struct lttng_ust_filter_bytecode *filter = NULL; @@ -1297,6 +1349,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 +1421,28 @@ 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_filter_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; - goto error; - } - - ust_bytecode = create_ust_bytecode_from_bytecode(ua_event->filter); + ust_bytecode = create_ust_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, - ua_event->obj); + 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 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 @@ -1377,12 +1450,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 filter. Application is dead."); } goto error; } - DBG2("UST filter set successfully for event %s", ua_event->name); + DBG2("UST filter set for object %p successfully", ust_object); error: health_code_update(); @@ -1414,33 +1487,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 +1518,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 +1560,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 +1649,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 +1669,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 +1771,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 +1786,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 +1798,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 +1825,159 @@ 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; + + health_code_update(); + + init_ust_trigger_from_event_rule(ua_token->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; + } + } + + /* + * 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. */ @@ -2341,7 +2561,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 +2580,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 +3395,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_event_rule *rule, + struct ust_app *app, uint64_t token) +{ + int ret = 0; + struct ust_app_token_event_rule *ua_token; + + ua_token = alloc_ust_app_token_event_rule(rule, token); + 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", + token, + 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", token, + 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 +3590,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 +3607,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 +3639,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 +3726,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 +3775,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 +3881,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 +5324,123 @@ 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(event_rule, app, 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_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: + 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 PID tracker. @@ -5132,6 +5579,16 @@ 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; + } + ust_app_synchronize_tokens(app); +} + /* * Called with session lock held. */ @@ -5147,6 +5604,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..a24b42ffd 100644 --- a/src/bin/lttng-sessiond/ust-app.h +++ b/src/bin/lttng-sessiond/ust-app.h @@ -112,6 +112,19 @@ struct ust_app_event { struct lttng_event_exclusion *exclusion; }; +struct ust_app_token_event_rule { + int enabled; + int handle; + struct lttng_ust_object_data *obj; + struct lttng_event_rule *event_rule; + uint64_t token; + struct lttng_ht_node_u64 node; + /* The event_rule object own this pointer */ + const struct lttng_filter_bytecode *filter; + /* The event_rule object own this pointer */ + struct lttng_event_exclusion *exclusion; +}; + struct ust_app_stream { int handle; char pathname[PATH_MAX]; @@ -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 96099f8aa..acb061382 100644 --- a/src/bin/lttng-sessiond/ust-ctl-internal.h +++ b/src/bin/lttng-sessiond/ust-ctl-internal.h @@ -85,6 +85,14 @@ 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-sessiond/utils.c b/src/bin/lttng-sessiond/utils.c index f84859fc6..2ae586b51 100644 --- a/src/bin/lttng-sessiond/utils.c +++ b/src/bin/lttng-sessiond/utils.c @@ -98,3 +98,26 @@ const char *consumer_output_get_base_path(const struct consumer_output *output) output->dst.session_root_path : output->dst.net.base_dir; } + +/* + * Allocate a filter and copy the given original filter. + * + * Return allocated filter or NULL on error. + */ +struct lttng_filter_bytecode *copy_filter_bytecode( + const struct lttng_filter_bytecode *orig_f) +{ + struct lttng_filter_bytecode *filter = NULL; + + /* Copy filter bytecode */ + filter = zmalloc(sizeof(*filter) + orig_f->len); + if (!filter) { + PERROR("zmalloc alloc filter bytecode"); + goto error; + } + + memcpy(filter, orig_f, sizeof(*filter) + orig_f->len); + +error: + return filter; +} diff --git a/src/bin/lttng-sessiond/utils.h b/src/bin/lttng-sessiond/utils.h index ff8deaec4..d5e506610 100644 --- a/src/bin/lttng-sessiond/utils.h +++ b/src/bin/lttng-sessiond/utils.h @@ -19,5 +19,7 @@ int loglevels_match(int a_loglevel_type, int a_loglevel_value, int b_loglevel_type, int b_loglevel_value, int loglevel_all_type); const char *session_get_base_path(const struct ltt_session *session); const char *consumer_output_get_base_path(const struct consumer_output *output); +struct lttng_filter_bytecode *copy_filter_bytecode( + const struct lttng_filter_bytecode *orig_f); #endif /* _LTT_UTILS_H */ 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_context.c b/src/bin/lttng/commands/add_context.c index 412202572..994cd49eb 100644 --- a/src/bin/lttng/commands/add_context.c +++ b/src/bin/lttng/commands/add_context.c @@ -184,19 +184,19 @@ static struct poptOption long_options[] = { */ #define PERF_HW(optstr, name, type, hide) \ { \ - optstr, type, hide, \ + (char *) optstr, type, hide, \ .u.perf = { PERF_TYPE_HARDWARE, PERF_COUNT_HW_##name, },\ } #define PERF_SW(optstr, name, type, hide) \ { \ - optstr, type, hide, \ + (char *) optstr, type, hide, \ .u.perf = { PERF_TYPE_SOFTWARE, PERF_COUNT_SW_##name, },\ } #define _PERF_HW_CACHE(optstr, name, type, op, result, hide) \ { \ - optstr, type, hide, \ + (char *) optstr, type, hide, \ .u.perf = { \ PERF_TYPE_HW_CACHE, \ (uint64_t) PERF_COUNT_HW_CACHE_##name \ @@ -235,45 +235,49 @@ const struct ctx_opts { } app_ctx; } u; } ctx_opts[] = { - { "pid", CONTEXT_PID }, - { "procname", CONTEXT_PROCNAME }, - { "prio", CONTEXT_PRIO }, - { "nice", CONTEXT_NICE }, - { "vpid", CONTEXT_VPID }, - { "tid", CONTEXT_TID }, - { "pthread_id", CONTEXT_PTHREAD_ID }, - { "vtid", CONTEXT_VTID }, - { "ppid", CONTEXT_PPID }, - { "vppid", CONTEXT_VPPID }, - { "hostname", CONTEXT_HOSTNAME }, - { "ip", CONTEXT_IP }, - { "interruptible", CONTEXT_INTERRUPTIBLE }, - { "preemptible", CONTEXT_PREEMPTIBLE }, - { "need_reschedule", CONTEXT_NEED_RESCHEDULE }, - { "migratable", CONTEXT_MIGRATABLE }, - { "callstack-kernel", CONTEXT_CALLSTACK_KERNEL }, + /* + * These (char *) casts (as well as those in the PERF_* macros) are + * safe because we never free these instances of `struct ctx_opts`. + */ + { (char *) "pid", CONTEXT_PID }, + { (char *) "procname", CONTEXT_PROCNAME }, + { (char *) "prio", CONTEXT_PRIO }, + { (char *) "nice", CONTEXT_NICE }, + { (char *) "vpid", CONTEXT_VPID }, + { (char *) "tid", CONTEXT_TID }, + { (char *) "pthread_id", CONTEXT_PTHREAD_ID }, + { (char *) "vtid", CONTEXT_VTID }, + { (char *) "ppid", CONTEXT_PPID }, + { (char *) "vppid", CONTEXT_VPPID }, + { (char *) "hostname", CONTEXT_HOSTNAME }, + { (char *) "ip", CONTEXT_IP }, + { (char *) "interruptible", CONTEXT_INTERRUPTIBLE }, + { (char *) "preemptible", CONTEXT_PREEMPTIBLE }, + { (char *) "need_reschedule", CONTEXT_NEED_RESCHEDULE }, + { (char *) "migratable", CONTEXT_MIGRATABLE }, + { (char *) "callstack-kernel", CONTEXT_CALLSTACK_KERNEL }, #if HAVE_MODULES_USERSPACE_CALLSTACK_CONTEXT - { "callstack-user", CONTEXT_CALLSTACK_USER }, + { (char *) "callstack-user", CONTEXT_CALLSTACK_USER }, #endif - { "cgroup_ns", CONTEXT_CGROUP_NS }, - { "ipc_ns", CONTEXT_IPC_NS }, - { "mnt_ns", CONTEXT_MNT_NS }, - { "net_ns", CONTEXT_NET_NS }, - { "pid_ns", CONTEXT_PID_NS }, - { "user_ns", CONTEXT_USER_NS }, - { "uts_ns", CONTEXT_UTS_NS }, - { "uid", CONTEXT_UID }, - { "euid", CONTEXT_EUID }, - { "suid", CONTEXT_SUID }, - { "gid", CONTEXT_GID }, - { "egid", CONTEXT_EGID }, - { "sgid", CONTEXT_SGID }, - { "vuid", CONTEXT_VUID }, - { "veuid", CONTEXT_VEUID }, - { "vsuid", CONTEXT_VSUID }, - { "vgid", CONTEXT_VGID }, - { "vegid", CONTEXT_VEGID }, - { "vsgid", CONTEXT_VSGID }, + { (char *) "cgroup_ns", CONTEXT_CGROUP_NS }, + { (char *) "ipc_ns", CONTEXT_IPC_NS }, + { (char *) "mnt_ns", CONTEXT_MNT_NS }, + { (char *) "net_ns", CONTEXT_NET_NS }, + { (char *) "pid_ns", CONTEXT_PID_NS }, + { (char *) "user_ns", CONTEXT_USER_NS }, + { (char *) "uts_ns", CONTEXT_UTS_NS }, + { (char *) "uid", CONTEXT_UID }, + { (char *) "euid", CONTEXT_EUID }, + { (char *) "suid", CONTEXT_SUID }, + { (char *) "gid", CONTEXT_GID }, + { (char *) "egid", CONTEXT_EGID }, + { (char *) "sgid", CONTEXT_SGID }, + { (char *) "vuid", CONTEXT_VUID }, + { (char *) "veuid", CONTEXT_VEUID }, + { (char *) "vsuid", CONTEXT_VSUID }, + { (char *) "vgid", CONTEXT_VGID }, + { (char *) "vegid", CONTEXT_VEGID }, + { (char *) "vsgid", CONTEXT_VSGID }, /* Perf options */ diff --git a/src/bin/lttng/commands/add_trigger.c b/src/bin/lttng/commands/add_trigger.c new file mode 100644 index 000000000..e0546e17e --- /dev/null +++ b/src/bin/lttng/commands/add_trigger.c @@ -0,0 +1,1727 @@ +#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 +#include "lttng/event-rule/kprobe.h" +#include "lttng/event-rule/syscall.h" +#include +#include "lttng/event-rule/uprobe.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, +}; + +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" }, + + 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 { + fprintf(stderr, "Error: Multiple domains specified.\n"); + 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 { + fprintf(stderr, "Error: Multiple event type not supported.\n"); + ret = false; + } + + return ret; +} + +static +bool assign_string(char **dest, const char *src, const char *opt_name) +{ + bool ret; + + if (*dest) { + fprintf(stderr, + "Duplicate %s given.\n", opt_name); + goto error; + } + + *dest = strdup(src); + if (!*dest) { + fprintf(stderr, "Failed to allocate %s string.\n", 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_rule *parse_event_rule(int *argc, const char ***argv) +{ + struct lttng_event_rule *er = NULL; + 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; + + /* 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; + + state = argpar_state_create(*argc, *argv, event_rule_opt_descrs); + if (!state) { + fprintf(stderr, "Failed to allocate an argpar state.\n"); + 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) { + fprintf(stderr, "Error: %s\n", 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; + + 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) { + fprintf(stderr, + "Error: Unexpected argument: %s\n", + 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: + fprintf(stderr, "Error: Can't use -a/--all with event rule of type %s.\n", + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + + if (tracepoint_name) { + fprintf(stderr, "Error: Can't provide a tracepoint name with -a/--all.\n"); + 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) { + fprintf(stderr, "Error: Need to provide either a tracepoint name or -a/--all.\n"); + goto error; + } + + /* + * We don't support multiple tracepoint names for now. + */ + if (strchr(tracepoint_name, ',')) { + fprintf(stderr, "Error: multiple tracepoint names are not supported at the moment.\n"); + 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) { + fprintf(stderr, "Error: Please specify a domain (-k/-u/-j).\n"); + 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) { + fprintf(stderr, "Error: Event type not available for user-space tracing\n"); + 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) { + fprintf(stderr, "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) { + fprintf(stderr, "Failed to create exclusion list.\n"); + goto error; + } + } + + if (loglevel_str && event_rule_type != LTTNG_EVENT_RULE_TYPE_TRACEPOINT) { + fprintf(stderr, "Log levels are only application to tracepoint event rules.\n"); + 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; + + er = lttng_event_rule_tracepoint_create(domain_type); + if (!er) { + fprintf(stderr, "Failed to create tracepoint event rule.\n"); + goto error; + } + + /* Set pattern. */ + event_rule_status = + lttng_event_rule_tracepoint_set_pattern(er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set tracepoint pattern.\n"); + goto error; + } + + /* Set filter. */ + if (filter) { + event_rule_status = + lttng_event_rule_tracepoint_set_filter( + er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set tracepoint filter expression.\n"); + 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(er, + n, (const char **) exclusion_list); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set tracepoint exclusion list.\n"); + goto error; + } + } + + if (loglevel_str) { + int loglevel; + + if (domain_type == LTTNG_DOMAIN_KERNEL) { + fprintf(stderr, "Log levels are not supported by the kernel tracer.\n"); + goto error; + } + + loglevel = parse_loglevel_string( + loglevel_str, domain_type); + if (loglevel < 0) { + fprintf(stderr, "Failed to parse `%s` as a log level.\n", loglevel_str); + goto error; + } + + if (loglevel_only) { + event_rule_status = + lttng_event_rule_tracepoint_set_loglevel( + er, loglevel); + } else { + event_rule_status = + lttng_event_rule_tracepoint_set_loglevel_range( + er, loglevel); + } + + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set log level.\n"); + goto error; + } + } + + break; + } + + case LTTNG_EVENT_RULE_TYPE_KPROBE: + { + enum lttng_event_rule_status event_rule_status; + + er = lttng_event_rule_kprobe_create(); + if (!er) { + fprintf(stderr, "Failed to create kprobe event rule.\n"); + goto error; + } + + event_rule_status = lttng_event_rule_kprobe_set_name(er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set kprobe event rule's name.\n"); + goto error; + } + + assert(source); + event_rule_status = lttng_event_rule_kprobe_set_source(er, source); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set kprobe event rule's source.\n"); + 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) { + fprintf(stderr, "Failed to parse userspace probe location.\n"); + goto error; + } + + er = lttng_event_rule_uprobe_create(); + if (!er) { + fprintf(stderr, "Failed to create userspace probe event rule.\n"); + goto error; + } + + event_rule_status = lttng_event_rule_uprobe_set_location(er, userspace_probe_location); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set userspace probe event rule's location.\n"); + goto error; + } + + event_rule_status = lttng_event_rule_uprobe_set_name(er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set userspace probe event rule's name.\n"); + goto error; + } + + break; + } + + case LTTNG_EVENT_RULE_TYPE_SYSCALL: + { + enum lttng_event_rule_status event_rule_status; + + er = lttng_event_rule_syscall_create(); + if (!er) { + fprintf(stderr, "Failed to create syscall event rule.\n"); + goto error; + } + + event_rule_status = lttng_event_rule_syscall_set_pattern(er, tracepoint_name); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set syscall event rule's pattern.\n"); + goto error; + } + + if (filter) { + event_rule_status = lttng_event_rule_syscall_set_filter( + er, filter); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to set syscall event rule's filter expression.\n"); + goto error; + } + } + + break; + } + + default: + fprintf(stderr, "%s: I don't support event rules of type `%s` at the moment.\n", __func__, + lttng_event_rule_type_str(event_rule_type)); + goto error; + } + + goto end; + +error: + lttng_event_rule_destroy(er); + er = NULL; + +end: + 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 er; +} + +static +struct lttng_condition *handle_condition_event(int *argc, const char ***argv) +{ + struct lttng_event_rule *er; + struct lttng_condition *c; + + er = parse_event_rule(argc, argv); + if (!er) { + c = NULL; + goto end; + } + + c = lttng_condition_event_rule_create(er); + if (!c) { + goto end; + } + +end: + 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) { + fprintf(stderr, "Failed to allocate an argpar state.\n"); + 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) { + fprintf(stderr, "Error: %s\n", 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: + fprintf(stderr, "Unexpected argument `%s`.\n", + 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) { + fprintf(stderr, "Missing session name argument.\n"); + goto error; + } + + if (!threshold_arg) { + fprintf(stderr, "Missing threshold argument.\n"); + goto error; + } + + if (utils_parse_size_suffix(threshold_arg, &threshold) != 0) { + fprintf(stderr, "Failed to parse `%s` as a size.\n", threshold_arg); + goto error; + } + + cond = lttng_condition_session_consumed_size_create(); + if (!cond) { + fprintf(stderr, "Failed to allocate a session consumed size condition.\n"); + goto error; + } + + condition_status = lttng_condition_session_consumed_size_set_session_name( + cond, session_name_arg); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fprintf(stderr, "Failed to set session consumed size condition session name.\n"); + goto error; + } + + + condition_status = lttng_condition_session_consumed_size_set_threshold( + cond, threshold); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fprintf(stderr, "Failed to set session consumed size condition threshold.\n"); + 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) { + fprintf(stderr, "Failed to allocate an argpar state.\n"); + 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) { + fprintf(stderr, "Error: %s\n", 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: + fprintf(stderr, "Unexpected argument `%s`.\n", + 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) { + fprintf(stderr, "Missing session name argument.\n"); + goto error; + } + + if (!threshold_arg) { + fprintf(stderr, "Missing threshold argument.\n"); + goto error; + } + + if (utils_parse_size_suffix(threshold_arg, &threshold) != 0) { + fprintf(stderr, "Failed to parse `%s` as a size.\n", threshold_arg); + goto error; + } + + cond = lttng_condition_session_consumed_size_create(); + if (!cond) { + fprintf(stderr, "Failed to allocate a session consumed size condition.\n"); + goto error; + } + + condition_status = lttng_condition_session_consumed_size_set_session_name( + cond, session_name_arg); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fprintf(stderr, "Failed to set session consumed size condition session name.\n"); + goto error; + } + + condition_status = lttng_condition_session_consumed_size_set_threshold( + cond, threshold); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + fprintf(stderr, "Failed to set session consumed size condition threshold.\n"); + 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) { + fprintf(stderr, "Missing condition name.\n"); + 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) { + fprintf(stderr, "Unknown condition name: %s\n", 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) { + fprintf(stderr, "Failed to allocate an argpar state.\n"); + 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) { + fprintf(stderr, "Error: %s\n", 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: + fprintf(stderr, "Unexpected argument `%s`.\n", + 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) { + fprintf(stderr, "Missing session name.\n"); + goto error; + } + + action = create_action_cb(); + if (!action) { + fprintf(stderr, + "Failed to allocate %s session action.\n", action_name); + goto error; + } + + action_status = set_session_name_cb(action, session_name_arg); + if (action_status != LTTNG_ACTION_STATUS_OK) { + fprintf(stderr, + "Failed to set action %s session's session name.\n", + 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) { + fprintf(stderr, "Failed to allocate an argpar state.\n"); + 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) { + fprintf(stderr, "Error: %s\n", 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: + fprintf(stderr, "Unexpected argument `%s`.\n", + 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) { + fprintf(stderr, "Missing session name.\n"); + goto error; + } + + /* --ctrl-url and --data-url must come in pair. */ + if (ctrl_url_arg && !data_url_arg) { + fprintf(stderr, "--ctrl-url is specified, but --data-url is missing.\n"); + goto error; + } + + if (!ctrl_url_arg && data_url_arg) { + fprintf(stderr, "--data-url is specified, but --ctrl-url is missing.\n"); + goto error; + } + + /* --ctrl-url/--data-url and the non-option URL are mutually exclusive. */ + if (ctrl_url_arg && url_arg) { + fprintf(stderr, "Both --ctrl-url/--data-url and the non-option URL argument " + "can't be used together.\n"); + 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) { + fprintf(stderr, "Failed to allocate a snapshot output.\n"); + goto error; + } + } + + action = lttng_action_snapshot_session_create(); + if (!action) { + fprintf(stderr, + "Failed to allocate snapshot session action.\n"); + goto error; + } + + action_status = lttng_action_snapshot_session_set_session_name( + action, session_name_arg); + if (action_status != LTTNG_ACTION_STATUS_OK) { + fprintf(stderr, + "Failed to set action snapshot session's session name.\n"); + goto error; + } + + if (snapshot_name_arg) { + if (!snapshot_output) { + fprintf(stderr, "Can't provide a snapshot output name without a snapshot output destination.\n"); + goto error; + } + + ret = lttng_snapshot_output_set_name(snapshot_name_arg, snapshot_output); + if (ret != 0) { + fprintf(stderr, "Failed to set name of snapshot output.\n"); + goto error; + } + } + + if (max_size_arg) { + uint64_t max_size; + + if (!snapshot_output) { + fprintf(stderr, "Can't provide a snapshot output max size without a snapshot output destination.\n"); + goto error; + } + + ret = utils_parse_size_suffix(max_size_arg, &max_size); + if (ret != 0) { + fprintf(stderr, "Failed to parse `%s` as a size.\n", max_size_arg); + goto error; + } + + ret = lttng_snapshot_output_set_size(max_size, snapshot_output); + if (ret != 0) { + fprintf(stderr, "Failed to set snapshot output's max size.\n"); + 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) { + fprintf(stderr, "Failed to parse %s as a network URL.\n", url_arg); + goto error; + } + } else { + ret = lttng_snapshot_output_set_local_path( + url_arg, snapshot_output); + if (ret != 0) { + fprintf(stderr, "Failed to parse %s as a local path.\n", 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) { + fprintf(stderr, "Failed to parse `%s` and `%s` as control and data URLs.\n", + 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) { + fprintf(stderr, "Failed to set snapshot session action's output.\n"); + 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) { + fprintf(stderr, "Missing action name.\n"); + 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) { + fprintf(stderr, "Unknown action name: %s\n", 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) { + fprintf(stderr, "Failed to create argpar state.\n"); + goto error; + } + + status = argpar_state_parse_next(argpar_state, &argpar_item, &error); + + if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR) { + fprintf(stderr, "Error: %s\n", error); + goto error; + } else if (status == ARGPAR_STATE_PARSE_NEXT_STATUS_ERROR_UNKNOWN_OPT) { + fprintf(stderr, "%s\n", 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; + + fprintf(stderr, "Unexpected argument `%s`.\n", + 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) { + fprintf(stderr, "A --condition was already given.\n"); + 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) { + fprintf(stderr, "Failed to add pointer to pointer array.\n"); + 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) { + fprintf(stderr, "Missing --condition.\n"); + goto error; + } + + if (lttng_dynamic_pointer_array_get_count(&actions) == 0) { + fprintf(stderr, "Need at least one --action.\n"); + goto error; + } + + if (fire_every_str && fire_once_after_str) { + fprintf(stderr, "Can't specify both --fire-once-after and --fire-every.\n"); + 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; + } + + /* + * Ownership of the condition and action group was transferred to the + * trigger. + */ + condition = NULL; + action_group = NULL; + + if (id) { + enum lttng_trigger_status trigger_status = + lttng_trigger_set_name(trigger, id); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + fprintf(stderr, "Failed to set trigger id.\n"); + 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) { + fprintf(stderr, "Failed to parse `%s` as an integer.\n", 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) { + fprintf(stderr, "Failed to set trigger's firing policy.\n"); + 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) { + fprintf(stderr, "Failed to parse `%s` as an integer.\n", 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) { + fprintf(stderr, "Failed to set trigger's firing policy.\n"); + goto error; + } + } + + ret = lttng_register_trigger(trigger); + if (ret) { + fprintf(stderr, "Failed to register trigger: %s.\n", + lttng_strerror(ret)); + goto error; + } + + printf("Trigger registered successfully.\n"); + + 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/create.c b/src/bin/lttng/commands/create.c index 406bfb62a..379ca0a66 100644 --- a/src/bin/lttng/commands/create.c +++ b/src/bin/lttng/commands/create.c @@ -501,7 +501,7 @@ error: * * Spawn a session daemon by forking and execv. */ -static int spawn_sessiond(char *pathname) +static int spawn_sessiond(const char *pathname) { int ret = 0; pid_t pid; @@ -580,7 +580,7 @@ end: static int launch_sessiond(void) { int ret; - char *pathname = NULL; + const char *pathname = NULL; ret = lttng_session_daemon_alive(); if (ret) { diff --git a/src/bin/lttng/commands/disable_events.c b/src/bin/lttng/commands/disable_events.c index 9909fec39..501c3cf3a 100644 --- a/src/bin/lttng/commands/disable_events.c +++ b/src/bin/lttng/commands/disable_events.c @@ -106,7 +106,7 @@ const char *print_event_type(const enum lttng_event_type ev_type) * enabled is 0 or 1 * success is 0 or 1 */ -static int mi_print_event(char *event_name, int enabled, int success) +static int mi_print_event(const char *event_name, int enabled, int success) { int ret; diff --git a/src/bin/lttng/commands/enable_events.c b/src/bin/lttng/commands/enable_events.c index 0de6a0cd4..570294e8f 100644 --- a/src/bin/lttng/commands/enable_events.c +++ b/src/bin/lttng/commands/enable_events.c @@ -27,6 +27,7 @@ #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 +175,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 +222,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 +268,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 +308,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 +508,7 @@ end: return ret; } -static +LTTNG_HIDDEN int create_exclusion_list_and_validate(const char *event_name, const char *exclusions_arg, char ***exclusion_list) @@ -969,6 +586,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 +974,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 +993,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 +1319,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..89296c704 --- /dev/null +++ b/src/bin/lttng/commands/list_triggers.c @@ -0,0 +1,541 @@ +#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); + + printf(" 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) { + printf(", 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 ? "<=" : "=="); + + printf(", 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) { + printf(", 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); + + printf("%s%s", i > 0 ? "," : "", exclusion); + } + } + + + printf(")\n"); +} + +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) { + fprintf(stderr, "Failed to get kprobe event rule's name.\n"); + goto end; + } + + assert(lttng_event_rule_get_type(event_rule) == LTTNG_EVENT_RULE_TYPE_KPROBE); + + printf(" 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); + printf("%s", symbol_name); + + offset = lttng_event_rule_kprobe_get_offset(event_rule); + if (offset > 0) { + printf("+0x%" PRIx64, offset); + } + + printf(")\n"); + +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) { + fprintf(stderr, "Failed to get uprobe event rule's name.\n"); + goto end; + } + + event_rule_status = lttng_event_rule_uprobe_get_location(event_rule, &location); + if (event_rule_status != LTTNG_EVENT_RULE_STATUS_OK) { + fprintf(stderr, "Failed to get uprobe event rule's location.\n"); + goto end; + } + + printf(" 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); + + printf("%s:%s", binary_path, function_name); + break; + } + + case LTTNG_USERSPACE_PROBE_LOCATION_TYPE_TRACEPOINT: + printf("SDT not implemented yet"); + break; + + default: + abort(); + } + + printf(")\n"); + +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); + + printf(" - 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) { + printf(", filter: %s", filter); + } else { + assert(event_rule_status == LTTNG_EVENT_RULE_STATUS_UNSET); + } + + printf(")\n"); +} + +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_condition_event_rule_hit(const struct lttng_condition *condition) +{ + const struct lttng_event_rule *event_rule; + enum lttng_condition_status condition_status; + + condition_status = + lttng_condition_event_rule_get_rule(condition, &event_rule); + assert(condition_status == LTTNG_CONDITION_STATUS_OK); + + print_event_rule(event_rule); +} + +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: + printf("notify\n"); + 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); + printf("start session `%s`\n", 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); + printf("stop session `%s`\n", 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); + printf("rotate session `%s`\n", 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); + printf("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://"); + } + + printf(", path: %s", ctrl_url); + } else if (starts_with_net || starts_with_net6) { + printf(", url: %s", ctrl_url); + } else { + assert(strlen(data_url) > 0); + + printf(", control url: %s, data url: %s", ctrl_url, data_url); + } + + name = lttng_snapshot_output_get_name(output); + assert(name); + if (strlen(name) > 0) { + printf(", name: %s", name); + } + + max_size = lttng_snapshot_output_get_maxsize(output); + if (max_size != -1ULL) { + printf(", max size: %" PRIu64, max_size); + } + } + + printf("\n"); + 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); + printf("- id: %s\n", name); + + trigger_status = lttng_trigger_get_firing_policy(trigger, + &policy_type, &threshold); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + fprintf(stderr, "Failed to get trigger's firing policy.\n"); + goto end; + } + + switch (policy_type) { + case LTTNG_TRIGGER_FIRE_EVERY_N: + if (threshold > 1) { + printf(" firing policy: after every %llu occurences\n", threshold); + } + break; + + case LTTNG_TRIGGER_FIRE_ONCE_AFTER_N: + printf(" firing policy: once after %llu occurences\n", threshold); + break; + + default: + abort(); + } + + condition = lttng_trigger_get_const_condition(trigger); + condition_type = lttng_condition_get_type(condition); + printf(" condition: %s\n", + lttng_condition_type_str(condition_type)); + + switch (condition_type) { + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + print_condition_event_rule_hit(condition); + break; + + default: + printf(" (condition type not handled in %s)\n", __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; + + printf(" actions:\n"); + + 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); + + printf(" "); + print_one_action(subaction); + } + } else { + printf(" 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) { + fprintf(stderr, "Error: %s\n", 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; + + fprintf(stderr, "Unexpected argument: %s\n", item_non_opt->arg); + } + } + + ret = lttng_list_triggers(&triggers); + if (ret != 0) { + fprintf(stderr, "Error listing triggers: %s.\n", + lttng_strerror(ret)); + goto error; + } + + trigger_status = lttng_triggers_get_count(triggers, &num_triggers); + if (trigger_status != LTTNG_TRIGGER_STATUS_OK) { + fprintf(stderr, "Failed to get trigger count.\n"); + goto error; + } + + sorted_triggers = calloc(num_triggers, sizeof(struct lttng_trigger *)); + if (!sorted_triggers) { + fprintf(stderr, "Failed to allocate array of struct lttng_trigger *.\n"); + 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..6d34c00f4 --- /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) { + fprintf(stderr, "Error: %s\n", 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) { + fprintf(stderr, "Unexpected argument: %s\n", item_non_opt->arg); + goto error; + } + + id = item_non_opt->arg; + } + } + + if (!id) { + fprintf(stderr, "Missing `id` argument.\n"); + goto error; + } + + ret = lttng_list_triggers(&triggers); + if (ret != 0) { + fprintf(stderr, "Failed to get the list of triggers.\n"); + 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) { + fprintf(stderr, "Couldn't find trigger with id `%s`.\n", id); + goto error; + } + + ret = lttng_unregister_trigger(trigger_to_remove); + if (ret != 0) { + fprintf(stderr, "Failed to unregister trigger `%s`.\n", id); + goto error; + } + + printf("Removed trigger `%s`.\n", 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/commands/track-untrack.c b/src/bin/lttng/commands/track-untrack.c index 06fd528ab..c674faefa 100644 --- a/src/bin/lttng/commands/track-untrack.c +++ b/src/bin/lttng/commands/track-untrack.c @@ -451,7 +451,7 @@ static enum cmd_error_code track_untrack_id(enum cmd_type cmd_type, ret = cmd_func(handle, tracker_type, item); if (ret) { - char *msg = NULL; + const char *msg = NULL; switch (-ret) { case LTTNG_ERR_ID_TRACKED: diff --git a/src/bin/lttng/commands/view.c b/src/bin/lttng/commands/view.c index 0dd1623ea..b9f3553b3 100644 --- a/src/bin/lttng/commands/view.c +++ b/src/bin/lttng/commands/view.c @@ -180,8 +180,8 @@ static char **alloc_argv_from_local_opts(const char **opts, size_t opts_len, memcpy(argv, opts, sizeof(char *) * opts_len); if (session_live_mode) { - argv[opts_len] = "-i"; - argv[opts_len + 1] = "lttng-live"; + argv[opts_len] = (char *) "-i"; + argv[opts_len + 1] = (char *) "lttng-live"; argv[opts_len + 2] = (char *) trace_path; argv[opts_len + 3] = NULL; } else { diff --git a/src/bin/lttng/conf.c b/src/bin/lttng/conf.c index 9e1773bb4..8e703b058 100644 --- a/src/bin/lttng/conf.c +++ b/src/bin/lttng/conf.c @@ -266,7 +266,7 @@ char *config_read_session_name_quiet(const char *path) int config_add_session_name(const char *path, const char *name) { int ret; - char *attr = "session="; + const char *attr = "session="; /* Max name len accepted plus attribute's len and the NULL byte. */ char session_name[NAME_MAX + strlen(attr) + 1]; diff --git a/src/bin/lttng/lttng.c b/src/bin/lttng/lttng.c index 84dc3a748..437a9cc4c 100644 --- a/src/bin/lttng/lttng.c +++ b/src/bin/lttng/lttng.c @@ -33,7 +33,7 @@ NULL ; /* Variables */ -static char *progname; +static const char *progname; int opt_no_sessiond; char *opt_sessiond_path; @@ -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 e88166a43..680d24134 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 fe82364a0..ff5206397 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 a50123989..0f38e6c74 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -2,7 +2,10 @@ AUTOMAKE_OPTIONS = subdir-objects -SUBDIRS = string-utils +SUBDIRS = \ + string-utils \ + filter \ + argpar # Make sure to always distribute all folders # since SUBDIRS is decided at configure time. @@ -20,41 +23,57 @@ DIST_SUBDIRS = \ config \ consumer \ string-utils \ - fd-tracker + fd-tracker \ + filter \ + argpar # Common library noinst_LTLIBRARIES = libcommon.la EXTRA_DIST = mi-lttng-4.0.xsd libcommon_la_SOURCES = \ - action.c \ - buffer-usage.c \ + actions/action.c \ + actions/group.c \ + actions/notify.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-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 \ futex.c futex.h \ location.c \ mi-lttng.c mi-lttng.h \ notification.c \ - notify.c \ optional.h \ 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 \ time.c \ trace-chunk.c trace-chunk.h \ trace-chunk-registry.h \ @@ -77,7 +96,8 @@ libcommon_la_LIBADD = \ $(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 if BUILD_LIB_COMPAT SUBDIRS += compat diff --git a/src/common/action.c b/src/common/action.c deleted file mode 100644 index dc72d37b4..000000000 --- a/src/common/action.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2017 Jérémie Galarneau - * - * SPDX-License-Identifier: LGPL-2.1-only - * - */ - -#include -#include -#include -#include - -enum lttng_action_type lttng_action_get_type(struct lttng_action *action) -{ - return action ? action->type : LTTNG_ACTION_TYPE_UNKNOWN; -} - -LTTNG_HIDDEN -enum lttng_action_type lttng_action_get_type_const( - const struct lttng_action *action) -{ - return action->type; -} - -void lttng_action_destroy(struct lttng_action *action) -{ - if (!action) { - return; - } - - assert(action->destroy); - action->destroy(action); -} - -LTTNG_HIDDEN -bool lttng_action_validate(struct lttng_action *action) -{ - bool valid; - - if (!action) { - valid = false; - goto end; - } - - if (!action->validate) { - /* Sub-class guarantees that it can never be invalid. */ - valid = true; - goto end; - } - - valid = action->validate(action); -end: - return valid; -} - -LTTNG_HIDDEN -int lttng_action_serialize(struct lttng_action *action, - struct lttng_dynamic_buffer *buf) -{ - int ret; - struct lttng_action_comm action_comm = { - .action_type = (int8_t) action->type, - }; - - ret = lttng_dynamic_buffer_append(buf, &action_comm, - sizeof(action_comm)); - if (ret) { - goto end; - } - - ret = action->serialize(action, buf); - if (ret) { - goto end; - } -end: - return ret; -} - -LTTNG_HIDDEN -ssize_t lttng_action_create_from_buffer(const struct lttng_buffer_view *view, - struct lttng_action **_action) -{ - ssize_t ret, action_size = sizeof(struct lttng_action_comm); - struct lttng_action *action; - const struct lttng_action_comm *action_comm; - - if (!view || !_action) { - ret = -1; - goto end; - } - - action_comm = (const struct lttng_action_comm *) view->data; - DBG("Deserializing action from buffer"); - switch (action_comm->action_type) { - case LTTNG_ACTION_TYPE_NOTIFY: - action = lttng_action_notify_create(); - break; - default: - ret = -1; - goto end; - } - - if (!action) { - ret = -1; - goto end; - } - ret = action_size; - *_action = action; -end: - return ret; -} diff --git a/src/common/actions/action.c b/src/common/actions/action.c new file mode 100644 index 000000000..cab49ae0d --- /dev/null +++ b/src/common/actions/action.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2017 Jérémie Galarneau + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(const struct lttng_action *action) +{ + return action ? action->type : LTTNG_ACTION_TYPE_UNKNOWN; +} + +LTTNG_HIDDEN +enum lttng_action_type lttng_action_get_type_const( + const struct lttng_action *action) +{ + return action->type; +} + +LTTNG_HIDDEN +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) +{ + urcu_ref_init(&action->ref); + action->type = type; + action->validate = validate; + action->serialize = serialize; + action->equal = equal; + action->destroy = destroy; +} + +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); + urcu_ref_put(&action->ref, action_destroy_ref); +} + +void lttng_action_destroy(struct lttng_action *action) +{ + lttng_action_put(action); +} + +LTTNG_HIDDEN +bool lttng_action_validate(struct lttng_action *action) +{ + bool valid; + + if (!action) { + valid = false; + goto end; + } + + if (!action->validate) { + /* Sub-class guarantees that it can never be invalid. */ + valid = true; + goto end; + } + + valid = action->validate(action); +end: + return valid; +} + +LTTNG_HIDDEN +int lttng_action_serialize(struct lttng_action *action, + struct lttng_dynamic_buffer *buf) +{ + int ret; + struct lttng_action_comm action_comm = { + .action_type = (int8_t) action->type, + }; + + ret = lttng_dynamic_buffer_append(buf, &action_comm, + sizeof(action_comm)); + if (ret) { + goto end; + } + + ret = action->serialize(action, buf); + if (ret) { + goto end; + } +end: + return ret; +} + +LTTNG_HIDDEN +ssize_t lttng_action_create_from_buffer(const struct lttng_buffer_view *view, + struct lttng_action **action) +{ + ssize_t consumed_len, specific_action_consumed_len; + const struct lttng_action_comm *action_comm; + action_create_from_buffer_cb create_from_buffer_cb; + struct lttng_buffer_view specific_action_view; + + if (!view || !action) { + consumed_len = -1; + goto end; + } + + action_comm = (const struct lttng_action_comm *) view->data; + + DBG("Create action from buffer: action-type=%s", + lttng_action_type_string(action_comm->action_type)); + + switch (action_comm->action_type) { + 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, + lttng_action_type_string( + action_comm->action_type)); + consumed_len = -1; + goto end; + } + + /* Create buffer view for the action-type-specific data. */ + specific_action_view = lttng_buffer_view_from_view(view, + sizeof(struct lttng_action_comm), + view->size - sizeof(struct lttng_action_comm)); + + specific_action_consumed_len = + create_from_buffer_cb(&specific_action_view, action); + if (specific_action_consumed_len < 0) { + ERR("Failed to create specific action from buffer."); + consumed_len = -1; + goto end; + } + + assert(*action); + + consumed_len = sizeof(struct lttng_action_comm) + + specific_action_consumed_len; + +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..055bb5e5c --- /dev/null +++ b/src/common/actions/group.c @@ -0,0 +1,345 @@ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include + +struct lttng_action_group { + struct lttng_action parent; + + /* Owned by this. */ + struct lttng_action **elements; + size_t n_elements; + size_t n_elements_allocated; +}; + +struct lttng_action_group_comm { + uint32_t n_elements; + + /* + * Variable data: each element serialized sequentially. + */ + char data[]; +} LTTNG_PACKED; + +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 bool lttng_action_group_validate(struct lttng_action *action) +{ + size_t i; + 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); + + for (i = 0; i < action_group->n_elements; i++) { + struct lttng_action *child = action_group->elements[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; + struct lttng_action_group *a, *b; + size_t i; + + assert(lttng_action_get_type_const(_a) == LTTNG_ACTION_TYPE_GROUP); + assert(lttng_action_get_type_const(_b) == LTTNG_ACTION_TYPE_GROUP); + + a = action_group_from_action(_a); + b = action_group_from_action(_b); + + if (a->n_elements != b->n_elements) { + goto end; + } + + for (i = 0; i < a->n_elements; i++) { + struct lttng_action *child_a = a->elements[i]; + struct lttng_action *child_b = b->elements[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; + size_t i; + + 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"); + + comm.n_elements = action_group->n_elements; + + ret = lttng_dynamic_buffer_append(buf, &comm, sizeof(comm)); + if (ret) { + ret = -1; + goto end; + } + + for (i = 0; i < action_group->n_elements; i++) { + struct lttng_action *child = action_group->elements[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; + size_t i; + + if (!action) { + goto end; + } + + action_group = action_group_from_action(action); + + for (i = 0; i < action_group->n_elements; i++) { + struct lttng_action *child = action_group->elements[i]; + + assert(child); + + lttng_action_destroy(child); + } + + free(action_group->elements); + 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->n_elements; 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); + + action_group->n_elements = 0; + action_group->n_elements_allocated = 10; + action_group->elements = zmalloc(10 * sizeof(struct lttng_action *)); + if (!action_group->elements) { + goto error; + } + + goto end; + +error: + lttng_action_group_destroy(action); + action = NULL; + +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; + + 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); + + if (action_group->n_elements == action_group->n_elements_allocated) { + // TODO: handle resize. + status = LTTNG_ACTION_STATUS_ERROR; + goto end; + } + + action_group->elements[action_group->n_elements] = action; + action_group->n_elements++; + + 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) +{ + 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(group); + + + *count = action_group->n_elements; +end: + return status; +} + +const struct lttng_action *lttng_action_group_get_at_index_const( + const struct lttng_action *group, + unsigned int index) +{ + struct lttng_action_group *action_group; + const struct lttng_action *action = NULL; + + if (!group || (lttng_action_get_type_const(group) != + LTTNG_ACTION_TYPE_GROUP)) { + goto end; + } + + action_group = action_group_from_action(group); + + if (index >= action_group->n_elements) { + goto end; + } + + action = action_group->elements[index]; +end: + return action; +} diff --git a/src/common/actions/notify.c b/src/common/actions/notify.c new file mode 100644 index 000000000..03dcce73c --- /dev/null +++ b/src/common/actions/notify.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 Jérémie Galarneau + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include +#include +#include + +static +void lttng_action_notify_destroy(struct lttng_action *action) +{ + free(action); +} + +static +int lttng_action_notify_serialize(struct lttng_action *action, + struct lttng_dynamic_buffer *buf) +{ + 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; + + notify = zmalloc(sizeof(struct lttng_action_notify)); + if (!notify) { + goto end; + } + + 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; +} + +ssize_t lttng_action_notify_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **action) +{ + ssize_t consumed_length; + + *action = lttng_action_notify_create(); + if (!*action) { + consumed_length = -1; + goto end; + } + + consumed_length = 0; +end: + return consumed_length; +} diff --git a/src/common/notify.c b/src/common/actions/notify.c.orig similarity index 59% rename from src/common/notify.c rename to src/common/actions/notify.c.orig index 00d1a0ef4..211d09595 100644 --- a/src/common/notify.c +++ b/src/common/actions/notify.c.orig @@ -32,9 +32,33 @@ struct lttng_action *lttng_action_notify_create(void) goto end; } +<<<<<<< HEAD notify->parent.type = LTTNG_ACTION_TYPE_NOTIFY; notify->parent.serialize = lttng_action_notify_serialize; notify->parent.destroy = lttng_action_notify_destroy; +======= + lttng_action_init(¬ify->parent, LTTNG_ACTION_TYPE_NOTIFY, NULL, + lttng_action_notify_serialize, + lttng_action_notify_is_equal, + lttng_action_notify_destroy); +>>>>>>> d8c05bf99... For joraj: missing lttng_action_init for notify action end: return ¬ify->parent; } + +ssize_t lttng_action_notify_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_action **action) +{ + ssize_t consumed_length; + + *action = lttng_action_notify_create(); + if (!*action) { + consumed_length = -1; + goto end; + } + + consumed_length = 0; +end: + return consumed_length; +} diff --git a/src/common/actions/rotate-session.c b/src/common/actions/rotate-session.c new file mode 100644 index 000000000..4f8199205 --- /dev/null +++ b/src/common/actions/rotate-session.c @@ -0,0 +1,265 @@ +/* + * 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 + */ + +#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_validate_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..a02cf80ca --- /dev/null +++ b/src/common/actions/snapshot-session.c @@ -0,0 +1,417 @@ +/* + * 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 + */ + +#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_validate_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..7ff9e637f --- /dev/null +++ b/src/common/actions/start-session.c @@ -0,0 +1,266 @@ +/* + * 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 + */ + +#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_validate_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..6b9489623 --- /dev/null +++ b/src/common/actions/stop-session.c @@ -0,0 +1,266 @@ +/* + * 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 + */ + +#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_validate_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/buffer-view.c b/src/common/buffer-view.c index 4bdb1eb7d..106542391 100644 --- a/src/common/buffer-view.c +++ b/src/common/buffer-view.c @@ -67,3 +67,45 @@ struct lttng_buffer_view lttng_buffer_view_from_dynamic_buffer( end: return view; } + +LTTNG_HIDDEN +bool lttng_buffer_view_validate_string(const struct lttng_buffer_view *buf, + const char *str, + size_t len_with_null_terminator) +{ + const char *past_buf_end; + size_t max_str_len_with_null_terminator; + size_t str_len; + bool ret; + + past_buf_end = buf->data + buf->size; + + /* Is the start of the string in the buffer view? */ + if (str < buf->data || str >= past_buf_end) { + ret = false; + goto end; + } + + /* + * Max length the string could have to fit in the buffer, including + * NULL terminator. + */ + max_str_len_with_null_terminator = past_buf_end - str; + + /* Could the string even fit in the buffer? */ + if (len_with_null_terminator > max_str_len_with_null_terminator) { + ret = false; + goto end; + } + + str_len = lttng_strnlen(str, max_str_len_with_null_terminator); + if (str_len != (len_with_null_terminator - 1)) { + ret = false; + goto end; + } + + ret = true; + +end: + return ret; +} diff --git a/src/common/buffer-view.h b/src/common/buffer-view.h index e8c351b3f..4486558aa 100644 --- a/src/common/buffer-view.h +++ b/src/common/buffer-view.h @@ -8,9 +8,10 @@ #ifndef LTTNG_BUFFER_VIEW_H #define LTTNG_BUFFER_VIEW_H +#include +#include #include #include -#include struct lttng_dynamic_buffer; @@ -70,4 +71,22 @@ struct lttng_buffer_view lttng_buffer_view_from_dynamic_buffer( const struct lttng_dynamic_buffer *src, size_t offset, ptrdiff_t len); +/** + * Validate that string starting at `str` of length `len_with_null_terminator` + * (including the NULL terminator) fits within the buffer view, and validate + * that it is indeed of that length. + * + * Return true if the validation passes, false otherwise. + * + * @buf The buffer view + * @str The start of the string + * @len_with_null_terminator Expected length of the string, including the + * NULL terminator. + * + */ +LTTNG_HIDDEN +bool lttng_buffer_view_validate_string(const struct lttng_buffer_view *buf, + const char *str, + size_t len_with_null_terminator); + #endif /* LTTNG_BUFFER_VIEW_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..aa1f1364a --- /dev/null +++ b/src/common/conditions/event-rule.c @@ -0,0 +1,409 @@ +/* + * 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 + +#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; +} + +static +int lttng_condition_event_rule_serialize( + const struct lttng_condition *condition, + struct lttng_dynamic_buffer *buf, + int *fd_to_send) +{ + int ret; + size_t header_offset, size_before_payload; + struct lttng_condition_event_rule *event_rule; + struct lttng_condition_event_rule_comm event_rule_comm = { 0 }; + struct lttng_condition_event_rule_comm *header; + + 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); + + header_offset = buf->size; + ret = lttng_dynamic_buffer_append(buf, &event_rule_comm, + sizeof(event_rule_comm)); + if (ret) { + goto end; + } + + size_before_payload = buf->size; + ret = lttng_event_rule_serialize(event_rule->rule, buf, fd_to_send); + if (ret) { + goto end; + } + + /* Update payload size */ + header = (struct lttng_condition_event_rule_comm *) ((char *) buf->data + header_offset); + header->length = buf->size - size_before_payload; + +end: + return ret; +} + +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); +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); + free(event_rule); +} + +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; + parent = &condition->parent; +end: + return parent; +} + +LTTNG_HIDDEN +ssize_t lttng_condition_event_rule_create_from_buffer( + const struct lttng_buffer_view *view, + struct lttng_condition **_condition) +{ + ssize_t offset, event_rule_size; + const struct lttng_condition_event_rule_comm *comm; + struct lttng_condition *condition = NULL; + struct lttng_event_rule *event_rule = NULL; + struct lttng_buffer_view event_rule_view; + + if (!view || !_condition) { + goto error; + } + + if (view->size < sizeof(*comm)) { + ERR("Failed to initialize from malformed event rule condition: buffer too short to contain header"); + goto error; + } + + comm = (const struct lttng_condition_event_rule_comm *) view->data; + offset = sizeof(*comm); + + /* Struct lttng_event_rule */ + event_rule_view = lttng_buffer_view_from_view(view, offset, -1); + event_rule_size = lttng_event_rule_create_from_buffer(&event_rule_view, &event_rule); + if (event_rule_size < 0 || !event_rule) { + goto error; + } + + if ((size_t) comm->length != event_rule_size) { + goto error; + } + + /* Move to the end */ + offset += comm->length; + + condition = lttng_condition_event_rule_create(event_rule); + if (!condition) { + goto error; + } + + /* Ownership passed on condition event rule create */ + event_rule = NULL; + + *_condition = condition; + condition = NULL; + goto end; + +error: + offset = -1; + +end: + lttng_event_rule_destroy(event_rule); + lttng_condition_destroy(condition); + return offset; +} + +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; +} + +LTTNG_HIDDEN +ssize_t lttng_evaluation_event_rule_create_from_buffer( + 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; + + 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; + + evaluation = lttng_evaluation_event_rule_create(name); + if (!evaluation) { + ret = -1; + goto error; + } + + *_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; + + 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); +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); + free(hit); +} + +LTTNG_HIDDEN +struct lttng_evaluation *lttng_evaluation_event_rule_create(const char *trigger_name) +{ + struct lttng_evaluation_event_rule *hit; + + hit = zmalloc(sizeof(struct lttng_evaluation_event_rule)); + if (!hit) { + goto end; + } + + /* TODO errir handling */ + hit->name = strdup(trigger_name); + + 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; +end: + return &hit->parent; +} + +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; +} 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..c7d621fbc --- /dev/null +++ b/src/common/credentials.c @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 - Jonathan Rajotte-Julien + * + * 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 ed81b8b45..8983652f9 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 5218869da..d3a60c582 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(); +/* 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..2e098900c 100644 --- a/src/common/evaluation.c +++ b/src/common/evaluation.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -106,6 +107,13 @@ ssize_t lttng_evaluation_create_from_buffer( } evaluation_size += ret; break; + case LTTNG_CONDITION_TYPE_EVENT_RULE_HIT: + ret = lttng_evaluation_event_rule_create_from_buffer(&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-rule-kprobe.c b/src/common/event-rule-kprobe.c new file mode 100644 index 000000000..8d4b8c474 --- /dev/null +++ b/src/common/event-rule-kprobe.c @@ -0,0 +1,522 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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 + +#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_filter_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..56916d9c9 --- /dev/null +++ b/src/common/event-rule-kretprobe.c @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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 + +#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_filter_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..97f5f86b1 --- /dev/null +++ b/src/common/event-rule-syscall.c @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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 + +#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_filter_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_filter_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..f66e64167 --- /dev/null +++ b/src/common/event-rule-tracepoint.c @@ -0,0 +1,1125 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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 + +#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_filter_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; + } + + 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_filter_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..5f9a5e348 --- /dev/null +++ b/src/common/event-rule-uprobe.c @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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 + +#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_filter_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/event-rule.c b/src/common/event-rule.c new file mode 100644 index 000000000..57bd68b16 --- /dev/null +++ b/src/common/event-rule.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2019 - Jonathan Rajotte-Julien + * + * 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 + +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_filter_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; +} + +LTTNG_HIDDEN +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/lib/lttng-ctl/filter/Makefile.am b/src/common/filter/Makefile.am similarity index 100% rename from src/lib/lttng-ctl/filter/Makefile.am rename to src/common/filter/Makefile.am diff --git a/src/lib/lttng-ctl/filter/filter-ast.h b/src/common/filter/filter-ast.h similarity index 95% rename from src/lib/lttng-ctl/filter/filter-ast.h rename to src/common/filter/filter-ast.h index fd322df3f..29fde10f8 100644 --- a/src/lib/lttng-ctl/filter/filter-ast.h +++ b/src/common/filter/filter-ast.h @@ -115,10 +115,10 @@ struct filter_node { enum ast_link_type post_op; /* reverse */ enum ast_link_type pre_op; /* forward */ union { - char *string; + const char *string; uint64_t constant; double float_constant; - char *identifier; + const char *identifier; /* * child can be nested. */ @@ -164,6 +164,8 @@ struct filter_parser_ctx { 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-bytecode.h b/src/common/filter/filter-bytecode.h similarity index 100% rename from src/lib/lttng-ctl/filter/filter-bytecode.h rename to src/common/filter/filter-bytecode.h diff --git a/src/lib/lttng-ctl/filter/filter-grammar-test.c b/src/common/filter/filter-grammar-test.c similarity index 100% rename from src/lib/lttng-ctl/filter/filter-grammar-test.c rename to src/common/filter/filter-grammar-test.c diff --git a/src/lib/lttng-ctl/filter/filter-ir.h b/src/common/filter/filter-ir.h similarity index 100% rename from src/lib/lttng-ctl/filter/filter-ir.h rename to src/common/filter/filter-ir.h 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 82% rename from src/lib/lttng-ctl/filter/filter-parser.y rename to src/common/filter/filter-parser.y index d1ea3a1a9..dfcdee362 100644 --- a/src/lib/lttng-ctl/filter/filter-parser.y +++ b/src/common/filter/filter-parser.y @@ -20,6 +20,8 @@ #include #include "filter-ast.h" #include "filter-parser.h" +#include "filter-bytecode.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 @@ -93,7 +109,7 @@ end: * gsrc will be garbage collected immediately, and gstr might be. * Should only be used to append characters to a string literal or constant. */ -LTTNG_HIDDEN +static struct gc_string *gc_string_append(struct filter_parser_ctx *parser_ctx, struct gc_string *gstr, struct gc_string *gsrc) @@ -123,6 +139,8 @@ struct gc_string *gc_string_append(struct filter_parser_ctx *parser_ctx, return gstr; } +LTTNG_HIDDEN +void setstring(struct filter_parser_ctx *parser_ctx, YYSTYPE *lvalp, const char *src); LTTNG_HIDDEN void setstring(struct filter_parser_ctx *parser_ctx, YYSTYPE *lvalp, const char *src) { @@ -185,12 +203,16 @@ static struct filter_node *make_op_node(struct filter_parser_ctx *scanner, return node; } +LTTNG_HIDDEN +void yyerror(struct filter_parser_ctx *parser_ctx, yyscan_t scanner, const char *str); LTTNG_HIDDEN void yyerror(struct filter_parser_ctx *parser_ctx, yyscan_t scanner, const char *str) { fprintf(stderr, "error %s\n", str); } +LTTNG_HIDDEN +int yywrap(void); LTTNG_HIDDEN int yywrap(void) { @@ -294,6 +316,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; +} + %} %define api.pure 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 100% rename from src/lib/lttng-ctl/filter/filter-visitor-generate-bytecode.c rename to src/common/filter/filter-visitor-generate-bytecode.c diff --git a/src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c b/src/common/filter/filter-visitor-generate-ir.c similarity index 99% rename from src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c rename to src/common/filter/filter-visitor-generate-ir.c index 024aac639..11c6b610c 100644 --- a/src/lib/lttng-ctl/filter/filter-visitor-generate-ir.c +++ b/src/common/filter/filter-visitor-generate-ir.c @@ -77,7 +77,7 @@ enum ir_load_string_type get_literal_string_type(const char *string) } static -struct ir_op *make_op_load_string(char *string, enum ir_side side) +struct ir_op *make_op_load_string(const char *string, enum ir_side side) { struct ir_op *op; @@ -187,7 +187,7 @@ struct ir_load_expression *create_load_expression(struct filter_node *node) { struct ir_load_expression *load_exp; struct ir_load_expression_op *load_exp_op, *prev_op; - char *str; + const char *str; /* Get forward chain. */ node = load_expression_get_forward_chain(node); 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 01af4a297..878929ff0 100644 --- a/src/common/kernel-ctl/kernel-ctl.c +++ b/src/common/kernel-ctl/kernel-ctl.c @@ -417,7 +417,22 @@ 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_filter(int fd, const struct lttng_filter_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 f8eac8d56..b3d798ffd 100644 --- a/src/common/kernel-ctl/kernel-ctl.h +++ b/src/common/kernel-ctl/kernel-ctl.h @@ -26,8 +26,14 @@ 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_filter_bytecode *filter); int kernctl_add_callsite(int fd, struct lttng_kernel_event_callsite *callsite); int kernctl_tracepoint_list(int fd); diff --git a/src/common/kernel-ctl/kernel-ioctl.h b/src/common/kernel-ctl/kernel-ioctl.h index 59d71191f..65ed69ecd 100644 --- a/src/common/kernel-ctl/kernel-ioctl.h +++ b/src/common/kernel-ctl/kernel-ioctl.h @@ -117,6 +117,13 @@ #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) + /* Session FD ioctl */ #define LTTNG_KERNEL_METADATA \ _IOW(0xF6, 0x54, struct lttng_kernel_channel) diff --git a/src/common/lttng-elf.c b/src/common/lttng-elf.c index 816dd0d76..bef20ffd4 100644 --- a/src/common/lttng-elf.c +++ b/src/common/lttng-elf.c @@ -750,7 +750,7 @@ int lttng_elf_get_symbol_offset(int fd, char *symbol, uint64_t *offset) char *curr_sym_str = NULL; char *symbol_table_data = NULL; char *string_table_data = NULL; - char *string_table_name = NULL; + const char *string_table_name = NULL; struct lttng_elf_shdr symtab_hdr; struct lttng_elf_shdr strtab_hdr; struct lttng_elf *elf = NULL; diff --git a/src/common/lttng-kernel.h b/src/common/lttng-kernel.h index d5904e79f..62e784eb6 100644 --- a/src/common/lttng-kernel.h +++ b/src/common/lttng-kernel.h @@ -150,6 +150,24 @@ 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; + 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..87f1a8a41 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; } diff --git a/src/common/relayd/relayd.c b/src/common/relayd/relayd.c index 7b11c81f6..dfcb337a0 100644 --- a/src/common/relayd/relayd.c +++ b/src/common/relayd/relayd.c @@ -499,7 +499,7 @@ int relayd_add_stream(struct lttcomm_relayd_sock *rsock, const char *channel_nam int ret; struct lttcomm_relayd_status_stream reply; char pathname[RELAYD_COMM_LTTNG_PATH_MAX]; - char *separator; + const char *separator; /* Code flow error. Safety net. */ assert(rsock); diff --git a/src/common/runas.c b/src/common/runas.c index 5d63f640e..da2ae71a2 100644 --- a/src/common/runas.c +++ b/src/common/runas.c @@ -28,12 +28,17 @@ #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_filter_bytecode **bytecode) +{ + int ret; + struct run_as_data data = {}; + struct run_as_ret run_as_ret = {}; + const struct lttng_filter_bytecode *view_bytecode = NULL; + struct lttng_filter_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_filter_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..398221708 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_filter_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 d89da4e32..735fa023c 100644 --- a/src/common/sessiond-comm/sessiond-comm.h +++ b/src/common/sessiond-comm/sessiond-comm.h @@ -99,6 +99,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 { 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..5c93c0ed4 100644 --- a/src/common/trigger.c +++ b/src/common/trigger.c @@ -7,12 +7,36 @@ #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; +} LTTNG_HIDDEN -bool lttng_trigger_validate(struct lttng_trigger *trigger) +bool lttng_trigger_validate(const struct lttng_trigger *trigger) { bool valid; @@ -42,8 +66,15 @@ 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; + end: return trigger; } @@ -54,11 +85,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 +97,53 @@ 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); + + 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 +154,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 +191,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 +201,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 +235,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 +263,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 +286,478 @@ 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 +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; +} + +static void delete_trigger_array_element(void *ptr) +{ + struct lttng_trigger *trigger = ptr; + lttng_trigger_destroy(trigger); +} + +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 +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); +} + +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; +} 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..4cc881025 100644 --- a/src/common/userspace-probe.c +++ b/src/common/userspace-probe.c @@ -144,6 +144,41 @@ void lttng_userspace_probe_location_destroy( } } +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); + + /* The binary_fd is not checked since it does not hold "immutable" + * information. + */ + + 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; + } + + 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 +227,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 +242,43 @@ 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); + + /* The binary_fd is not checked since it does not hold "immutable" + * information. + */ + + 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 = true; +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 +334,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 +1785,70 @@ struct lttng_userspace_probe_location *lttng_userspace_probe_location_copy( err: return new_location; } + +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->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 +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 d9bacad0e..83d4dbe45 100644 --- a/src/common/utils.c +++ b/src/common/utils.c @@ -1527,3 +1527,40 @@ int utils_change_working_directory(const char *path) end: return ret; } + +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 b6e5c97db..d5dc9d01a 100644 --- a/src/common/utils.h +++ b/src/common/utils.h @@ -55,4 +55,16 @@ int utils_get_memory_available(size_t *value); int utils_get_memory_total(size_t *value); int utils_change_working_directory(const char *path); +/* + * 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 de390175b..dcc12aa26 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) @@ -15,8 +15,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/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 8c14e1fdc..ed91148f6 100644 --- a/src/lib/lttng-ctl/lttng-ctl-helper.h +++ b/src/lib/lttng-ctl/lttng-ctl-helper.h @@ -58,6 +58,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 4e4912073..fff602305 100644 --- a/src/lib/lttng-ctl/lttng-ctl.c +++ b/src/lib/lttng-ctl/lttng-ctl.c @@ -38,26 +38,12 @@ #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 #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; \ @@ -896,7 +882,7 @@ static char *set_agent_filter(const char *filter, struct lttng_event *ev) /* Add loglevel filtering if any for the JUL domain. */ if (ev->loglevel_type != LTTNG_EVENT_LOGLEVEL_ALL) { - char *op; + const char *op; if (ev->loglevel_type == LTTNG_EVENT_LOGLEVEL_RANGE) { op = ">="; @@ -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 @@ -3054,8 +2921,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) { @@ -3068,23 +2942,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; @@ -3101,7 +3043,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; @@ -3117,6 +3059,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; +} + static int lttng_track_untrack_id(struct lttng_handle *handle, enum lttng_tracker_type tracker_type, const struct lttng_tracker_id *id, 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..6020964f1 100644 --- a/tests/regression/Makefile.am +++ b/tests/regression/Makefile.am @@ -27,8 +27,14 @@ 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_event_rule_condition_exclusion \ + tools/notification/test_notification_kernel_error \ + tools/notification/test_notification_kernel_buffer_usage \ + 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 \ @@ -39,7 +45,10 @@ TESTS = tools/filtering/test_invalid_filter \ 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/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/select_poll_epoll.c b/tests/regression/kernel/select_poll_epoll.c index 33b8cb5d5..42d554cb2 100644 --- a/tests/regression/kernel/select_poll_epoll.c +++ b/tests/regression/kernel/select_poll_epoll.c @@ -46,7 +46,7 @@ struct ppoll_thread_data { int value; }; -void test_select_big(void) +static void test_select_big(void) { fd_set rfds, wfds, exfds; struct timeval tv; @@ -95,7 +95,7 @@ end: return; } -void test_pselect(void) +static void test_pselect(void) { fd_set rfds; struct timespec tv; @@ -128,7 +128,7 @@ void test_pselect(void) } -void test_select(void) +static void test_select(void) { fd_set rfds; struct timeval tv; @@ -161,7 +161,7 @@ void test_select(void) } -void test_poll(void) +static void test_poll(void) { struct pollfd ufds[NB_FD]; char buf[BUF_SIZE]; @@ -185,7 +185,7 @@ void test_poll(void) } } -void test_ppoll(void) +static void test_ppoll(void) { struct pollfd ufds[NB_FD]; char buf[BUF_SIZE]; @@ -217,7 +217,7 @@ void test_ppoll(void) } } -void test_ppoll_big(void) +static void test_ppoll_big(void) { struct pollfd ufds[MAX_FDS]; char buf[BUF_SIZE]; @@ -256,7 +256,7 @@ void test_ppoll_big(void) return; } -void test_epoll(void) +static void test_epoll(void) { int ret, epollfd; char buf[BUF_SIZE]; @@ -298,7 +298,7 @@ end: return; } -void test_pepoll(void) +static void test_pepoll(void) { int ret, epollfd; char buf[BUF_SIZE]; @@ -340,7 +340,7 @@ end: return; } -void run_working_cases(void) +static void run_working_cases(void) { int ret; int pipe_fds[2]; @@ -386,7 +386,7 @@ end: * segfault (eventually with a "*** stack smashing detected ***" message). * The event should contain an array of 100 FDs filled with garbage. */ -void ppoll_fds_buffer_overflow(void) +static void ppoll_fds_buffer_overflow(void) { struct pollfd ufds[NB_FD]; char buf[BUF_SIZE]; @@ -417,7 +417,7 @@ void ppoll_fds_buffer_overflow(void) * cleanly fail with a "Invalid argument". * The event should contain an empty array of FDs and overflow = 1. */ -void ppoll_fds_ulong_max(void) +static void ppoll_fds_ulong_max(void) { struct pollfd ufds[NB_FD]; char buf[BUF_SIZE]; @@ -447,7 +447,7 @@ void ppoll_fds_ulong_max(void) * Pass an invalid file descriptor to pselect6(). The syscall should return * -EBADF. The recorded event should contain a "ret = -EBADF (-9)". */ -void pselect_invalid_fd(void) +static void pselect_invalid_fd(void) { fd_set rfds; int ret; @@ -493,7 +493,7 @@ error: * Invalid pointer as writefds, should output a ppoll event * with 0 FDs. */ -void pselect_invalid_pointer(void) +static void pselect_invalid_pointer(void) { fd_set rfds; int ret; @@ -524,7 +524,7 @@ void pselect_invalid_pointer(void) * Pass an invalid pointer to epoll_pwait, should fail with * "Bad address", the event returns 0 FDs. */ -void epoll_pwait_invalid_pointer(void) +static void epoll_pwait_invalid_pointer(void) { int ret, epollfd; char buf[BUF_SIZE]; @@ -568,7 +568,7 @@ end: * Set maxevents to INT_MAX, should output "Invalid argument" * The event should return an empty array. */ -void epoll_pwait_int_max(void) +static void epoll_pwait_int_max(void) { int ret, epollfd; char buf[BUF_SIZE]; @@ -607,7 +607,7 @@ end: return; } -void *ppoll_writer(void *arg) +static void *ppoll_writer(void *arg) { struct ppoll_thread_data *data = (struct ppoll_thread_data *) arg; @@ -620,7 +620,7 @@ void *ppoll_writer(void *arg) return NULL; } -void do_ppoll(int *fds, struct pollfd *ufds) +static void do_ppoll(int *fds, struct pollfd *ufds) { int i, ret; struct timespec ts; @@ -649,7 +649,7 @@ void do_ppoll(int *fds, struct pollfd *ufds) } } -void stress_ppoll(int *fds, int value) +static void stress_ppoll(int *fds, int value) { pthread_t writer; int iter, ret; @@ -690,7 +690,7 @@ end: * * ppoll should work as expected and the trace should be readable at the end. */ -void ppoll_concurrent_write(void) +static void ppoll_concurrent_write(void) { int i, ret, fds[MAX_FDS]; @@ -715,7 +715,7 @@ void ppoll_concurrent_write(void) return; } -void *epoll_pwait_writer(void *addr) +static void *epoll_pwait_writer(void *addr) { srand(time(NULL)); @@ -732,7 +732,7 @@ void *epoll_pwait_writer(void *addr) * buffer allocated for the returned data. This should randomly segfault. * The trace should be readable and no kernel OOPS should occur. */ -void epoll_pwait_concurrent_munmap(void) +static void epoll_pwait_concurrent_munmap(void) { int ret, epollfd, i, fds[MAX_FDS]; char buf[BUF_SIZE]; @@ -814,16 +814,7 @@ end: return; } -void usage(poptContext optCon, int exitcode, char *error, char *addl) -{ - poptPrintUsage(optCon, stderr, 0); - if (error) { - fprintf(stderr, "%s: %s\n", error, addl); - } - exit(exitcode); -} - -void print_list(void) +static void print_list(void) { fprintf(stderr, "Test list (-t X):\n"); fprintf(stderr, "\t1: Working cases for select, pselect6, poll, ppoll " 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/health/health_exit.c b/tests/regression/tools/health/health_exit.c index e9324fa2e..6d04c9da7 100644 --- a/tests/regression/tools/health/health_exit.c +++ b/tests/regression/tools/health/health_exit.c @@ -30,6 +30,7 @@ int check_env_var(const char *env) /* Session daemon */ +int __testpoint_sessiond_thread_manage_clients(void); int __testpoint_sessiond_thread_manage_clients(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_CLIENTS_EXIT"; @@ -42,6 +43,7 @@ int __testpoint_sessiond_thread_manage_clients(void) return 0; } +int __testpoint_sessiond_thread_registration_apps(void); int __testpoint_sessiond_thread_registration_apps(void) { const char *var = "LTTNG_SESSIOND_THREAD_REG_APPS_EXIT"; @@ -53,6 +55,7 @@ int __testpoint_sessiond_thread_registration_apps(void) return 0; } +int __testpoint_sessiond_thread_manage_apps(void); int __testpoint_sessiond_thread_manage_apps(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_APPS_EXIT"; @@ -65,6 +68,7 @@ int __testpoint_sessiond_thread_manage_apps(void) return 0; } +int __testpoint_sessiond_thread_manage_kernel(void); int __testpoint_sessiond_thread_manage_kernel(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_KERNEL_EXIT"; @@ -76,6 +80,7 @@ int __testpoint_sessiond_thread_manage_kernel(void) return 0; } +int __testpoint_sessiond_thread_manage_consumer(void); int __testpoint_sessiond_thread_manage_consumer(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_CONSUMER_EXIT"; @@ -87,6 +92,7 @@ int __testpoint_sessiond_thread_manage_consumer(void) return 0; } +int __testpoint_sessiond_thread_ht_cleanup(void); int __testpoint_sessiond_thread_ht_cleanup(void) { const char *var = "LTTNG_SESSIOND_THREAD_HT_CLEANUP_EXIT"; @@ -99,6 +105,7 @@ int __testpoint_sessiond_thread_ht_cleanup(void) return 0; } +int __testpoint_sessiond_thread_app_manage_notify(void); int __testpoint_sessiond_thread_app_manage_notify(void) { const char *var = "LTTNG_SESSIOND_THREAD_APP_MANAGE_NOTIFY_EXIT"; @@ -111,6 +118,7 @@ int __testpoint_sessiond_thread_app_manage_notify(void) return 0; } +int __testpoint_sessiond_thread_app_reg_dispatch(void); int __testpoint_sessiond_thread_app_reg_dispatch(void) { const char *var = "LTTNG_SESSIOND_THREAD_APP_REG_DISPATCH_EXIT"; @@ -124,6 +132,7 @@ int __testpoint_sessiond_thread_app_reg_dispatch(void) /* Consumer daemon */ +int __testpoint_consumerd_thread_channel(void); int __testpoint_consumerd_thread_channel(void) { const char *var = "LTTNG_CONSUMERD_THREAD_CHANNEL_EXIT"; @@ -136,6 +145,7 @@ int __testpoint_consumerd_thread_channel(void) return 0; } +int __testpoint_consumerd_thread_metadata(void); int __testpoint_consumerd_thread_metadata(void) { const char *var = "LTTNG_CONSUMERD_THREAD_METADATA_EXIT"; @@ -148,6 +158,7 @@ int __testpoint_consumerd_thread_metadata(void) return 0; } +int __testpoint_consumerd_thread_data(void); int __testpoint_consumerd_thread_data(void) { const char *var = "LTTNG_CONSUMERD_THREAD_DATA_EXIT"; @@ -160,6 +171,7 @@ int __testpoint_consumerd_thread_data(void) return 0; } +int __testpoint_consumerd_thread_sessiond(void); int __testpoint_consumerd_thread_sessiond(void) { const char *var = "LTTNG_CONSUMERD_THREAD_SESSIOND_EXIT"; @@ -172,6 +184,7 @@ int __testpoint_consumerd_thread_sessiond(void) return 0; } +int __testpoint_consumerd_thread_metadata_timer(void); int __testpoint_consumerd_thread_metadata_timer(void) { const char *var = "LTTNG_CONSUMERD_THREAD_METADATA_TIMER_EXIT"; @@ -185,6 +198,7 @@ int __testpoint_consumerd_thread_metadata_timer(void) /* Relay daemon */ +int __testpoint_relayd_thread_dispatcher(void); int __testpoint_relayd_thread_dispatcher(void) { const char *var = "LTTNG_RELAYD_THREAD_DISPATCHER_EXIT"; @@ -196,6 +210,7 @@ int __testpoint_relayd_thread_dispatcher(void) return 0; } +int __testpoint_relayd_thread_worker(void); int __testpoint_relayd_thread_worker(void) { const char *var = "LTTNG_RELAYD_THREAD_WORKER_EXIT"; @@ -208,6 +223,7 @@ int __testpoint_relayd_thread_worker(void) return 0; } +int __testpoint_relayd_thread_listener(void); int __testpoint_relayd_thread_listener(void) { const char *var = "LTTNG_RELAYD_THREAD_LISTENER_EXIT"; @@ -219,6 +235,7 @@ int __testpoint_relayd_thread_listener(void) return 0; } +int __testpoint_relayd_thread_live_dispatcher(void); int __testpoint_relayd_thread_live_dispatcher(void) { const char *var = "LTTNG_RELAYD_THREAD_LIVE_DISPATCHER_EXIT"; @@ -230,6 +247,7 @@ int __testpoint_relayd_thread_live_dispatcher(void) return 0; } +int __testpoint_relayd_thread_live_worker(void); int __testpoint_relayd_thread_live_worker(void) { const char *var = "LTTNG_RELAYD_THREAD_LIVE_WORKER_EXIT"; @@ -242,6 +260,7 @@ int __testpoint_relayd_thread_live_worker(void) return 0; } +int __testpoint_relayd_thread_live_listener(void); int __testpoint_relayd_thread_live_listener(void) { const char *var = "LTTNG_RELAYD_THREAD_LIVE_LISTENER_EXIT"; diff --git a/tests/regression/tools/health/health_fail.c b/tests/regression/tools/health/health_fail.c index b3ebcedea..ca2bc3485 100644 --- a/tests/regression/tools/health/health_fail.c +++ b/tests/regression/tools/health/health_fail.c @@ -30,6 +30,7 @@ int check_env_var(const char *env) /* Session daemon */ +int __testpoint_sessiond_thread_manage_clients(void); int __testpoint_sessiond_thread_manage_clients(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_CLIENTS_TP_FAIL"; @@ -41,6 +42,7 @@ int __testpoint_sessiond_thread_manage_clients(void) return 0; } +int __testpoint_sessiond_thread_registration_apps(void); int __testpoint_sessiond_thread_registration_apps(void) { const char *var = "LTTNG_SESSIOND_THREAD_REG_APPS_TP_FAIL"; @@ -52,6 +54,7 @@ int __testpoint_sessiond_thread_registration_apps(void) return 0; } +int __testpoint_sessiond_thread_manage_apps(void); int __testpoint_sessiond_thread_manage_apps(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_APPS_TP_FAIL"; @@ -63,6 +66,7 @@ int __testpoint_sessiond_thread_manage_apps(void) return 0; } +int __testpoint_sessiond_thread_manage_kernel(void); int __testpoint_sessiond_thread_manage_kernel(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_KERNEL_TP_FAIL"; @@ -74,6 +78,7 @@ int __testpoint_sessiond_thread_manage_kernel(void) return 0; } +int __testpoint_sessiond_thread_manage_consumer(void); int __testpoint_sessiond_thread_manage_consumer(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_CONSUMER_TP_FAIL"; @@ -85,6 +90,7 @@ int __testpoint_sessiond_thread_manage_consumer(void) return 0; } +int __testpoint_sessiond_thread_ht_cleanup(void); int __testpoint_sessiond_thread_ht_cleanup(void) { const char *var = "LTTNG_SESSIOND_THREAD_HT_CLEANUP_TP_FAIL"; @@ -96,6 +102,7 @@ int __testpoint_sessiond_thread_ht_cleanup(void) return 0; } +int __testpoint_sessiond_thread_app_manage_notify(void); int __testpoint_sessiond_thread_app_manage_notify(void) { const char *var = "LTTNG_SESSIOND_THREAD_APP_MANAGE_NOTIFY_TP_FAIL"; @@ -107,6 +114,7 @@ int __testpoint_sessiond_thread_app_manage_notify(void) return 0; } +int __testpoint_sessiond_thread_app_reg_dispatch(void); int __testpoint_sessiond_thread_app_reg_dispatch(void) { const char *var = "LTTNG_SESSIOND_THREAD_APP_REG_DISPATCH_TP_FAIL"; @@ -120,6 +128,7 @@ int __testpoint_sessiond_thread_app_reg_dispatch(void) /* Consumer daemon */ +int __testpoint_consumerd_thread_channel(void); int __testpoint_consumerd_thread_channel(void) { const char *var = "LTTNG_CONSUMERD_THREAD_CHANNEL_TP_FAIL"; @@ -131,6 +140,7 @@ int __testpoint_consumerd_thread_channel(void) return 0; } +int __testpoint_consumerd_thread_metadata(void); int __testpoint_consumerd_thread_metadata(void) { const char *var = "LTTNG_CONSUMERD_THREAD_METADATA_TP_FAIL"; @@ -142,6 +152,7 @@ int __testpoint_consumerd_thread_metadata(void) return 0; } +int __testpoint_consumerd_thread_data(void); int __testpoint_consumerd_thread_data(void) { const char *var = "LTTNG_CONSUMERD_THREAD_DATA_TP_FAIL"; @@ -153,6 +164,7 @@ int __testpoint_consumerd_thread_data(void) return 0; } +int __testpoint_consumerd_thread_sessiond(void); int __testpoint_consumerd_thread_sessiond(void) { const char *var = "LTTNG_CONSUMERD_THREAD_SESSIOND_TP_FAIL"; @@ -164,6 +176,7 @@ int __testpoint_consumerd_thread_sessiond(void) return 0; } +int __testpoint_consumerd_thread_metadata_timer(void); int __testpoint_consumerd_thread_metadata_timer(void) { const char *var = "LTTNG_CONSUMERD_THREAD_METADATA_TIMER_TP_FAIL"; @@ -177,6 +190,7 @@ int __testpoint_consumerd_thread_metadata_timer(void) /* Relay daemon */ +int __testpoint_relayd_thread_dispatcher(void); int __testpoint_relayd_thread_dispatcher(void) { const char *var = "LTTNG_RELAYD_THREAD_DISPATCHER_TP_FAIL"; @@ -188,6 +202,7 @@ int __testpoint_relayd_thread_dispatcher(void) return 0; } +int __testpoint_relayd_thread_worker(void); int __testpoint_relayd_thread_worker(void) { const char *var = "LTTNG_RELAYD_THREAD_WORKER_TP_FAIL"; @@ -199,6 +214,7 @@ int __testpoint_relayd_thread_worker(void) return 0; } +int __testpoint_relayd_thread_listener(void); int __testpoint_relayd_thread_listener(void) { const char *var = "LTTNG_RELAYD_THREAD_LISTENER_TP_FAIL"; @@ -210,6 +226,7 @@ int __testpoint_relayd_thread_listener(void) return 0; } +int __testpoint_relayd_thread_live_dispatcher(void); int __testpoint_relayd_thread_live_dispatcher(void) { const char *var = "LTTNG_RELAYD_THREAD_LIVE_DISPATCHER_TP_FAIL"; @@ -221,6 +238,7 @@ int __testpoint_relayd_thread_live_dispatcher(void) return 0; } +int __testpoint_relayd_thread_live_worker(void); int __testpoint_relayd_thread_live_worker(void) { const char *var = "LTTNG_RELAYD_THREAD_LIVE_WORKER_TP_FAIL"; @@ -232,6 +250,7 @@ int __testpoint_relayd_thread_live_worker(void) return 0; } +int __testpoint_relayd_thread_live_listener(void); int __testpoint_relayd_thread_live_listener(void) { const char *var = "LTTNG_RELAYD_THREAD_LIVE_LISTENER_TP_FAIL"; diff --git a/tests/regression/tools/health/health_stall.c b/tests/regression/tools/health/health_stall.c index 4439b7369..1c0d6dc77 100644 --- a/tests/regression/tools/health/health_stall.c +++ b/tests/regression/tools/health/health_stall.c @@ -43,6 +43,7 @@ void do_stall(void) /* Session daemon */ +int __testpoint_sessiond_thread_manage_clients(void); int __testpoint_sessiond_thread_manage_clients(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_CLIENTS_STALL"; @@ -54,6 +55,7 @@ int __testpoint_sessiond_thread_manage_clients(void) return 0; } +int __testpoint_sessiond_thread_registration_apps(void); int __testpoint_sessiond_thread_registration_apps(void) { const char *var = "LTTNG_SESSIOND_THREAD_REG_APPS_STALL"; @@ -65,6 +67,7 @@ int __testpoint_sessiond_thread_registration_apps(void) return 0; } +int __testpoint_sessiond_thread_manage_apps(void); int __testpoint_sessiond_thread_manage_apps(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_APPS_STALL"; @@ -76,6 +79,7 @@ int __testpoint_sessiond_thread_manage_apps(void) return 0; } +int __testpoint_sessiond_thread_manage_kernel(void); int __testpoint_sessiond_thread_manage_kernel(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_KERNEL_STALL"; @@ -87,6 +91,7 @@ int __testpoint_sessiond_thread_manage_kernel(void) return 0; } +int __testpoint_sessiond_thread_manage_consumer(void); int __testpoint_sessiond_thread_manage_consumer(void) { const char *var = "LTTNG_SESSIOND_THREAD_MANAGE_CONSUMER_STALL"; @@ -98,6 +103,7 @@ int __testpoint_sessiond_thread_manage_consumer(void) return 0; } +int __testpoint_sessiond_thread_ht_cleanup(void); int __testpoint_sessiond_thread_ht_cleanup(void) { const char *var = "LTTNG_SESSIOND_THREAD_HT_CLEANUP_STALL"; @@ -109,6 +115,7 @@ int __testpoint_sessiond_thread_ht_cleanup(void) return 0; } +int __testpoint_sessiond_thread_app_manage_notify(void); int __testpoint_sessiond_thread_app_manage_notify(void) { const char *var = "LTTNG_SESSIOND_THREAD_APP_MANAGE_NOTIFY_STALL"; @@ -120,6 +127,7 @@ int __testpoint_sessiond_thread_app_manage_notify(void) return 0; } +int __testpoint_sessiond_thread_app_reg_dispatch(void); int __testpoint_sessiond_thread_app_reg_dispatch(void) { const char *var = "LTTNG_SESSIOND_THREAD_APP_REG_DISPATCH_STALL"; @@ -133,6 +141,7 @@ int __testpoint_sessiond_thread_app_reg_dispatch(void) /* Consumer daemon */ +int __testpoint_consumerd_thread_channel(void); int __testpoint_consumerd_thread_channel(void) { const char *var = "LTTNG_CONSUMERD_THREAD_CHANNEL_STALL"; @@ -144,6 +153,7 @@ int __testpoint_consumerd_thread_channel(void) return 0; } +int __testpoint_consumerd_thread_metadata(void); int __testpoint_consumerd_thread_metadata(void) { const char *var = "LTTNG_CONSUMERD_THREAD_METADATA_STALL"; @@ -155,6 +165,7 @@ int __testpoint_consumerd_thread_metadata(void) return 0; } +int __testpoint_consumerd_thread_data(void); int __testpoint_consumerd_thread_data(void) { const char *var = "LTTNG_CONSUMERD_THREAD_DATA_STALL"; @@ -166,6 +177,7 @@ int __testpoint_consumerd_thread_data(void) return 0; } +int __testpoint_consumerd_thread_sessiond(void); int __testpoint_consumerd_thread_sessiond(void) { const char *var = "LTTNG_CONSUMERD_THREAD_SESSIOND_STALL"; @@ -177,6 +189,7 @@ int __testpoint_consumerd_thread_sessiond(void) return 0; } +int __testpoint_consumerd_thread_metadata_timer(void); int __testpoint_consumerd_thread_metadata_timer(void) { const char *var = "LTTNG_CONSUMERD_THREAD_METADATA_TIMER_STALL"; @@ -190,6 +203,7 @@ int __testpoint_consumerd_thread_metadata_timer(void) /* Relay daemon */ +int __testpoint_relayd_thread_dispatcher(void); int __testpoint_relayd_thread_dispatcher(void) { const char *var = "LTTNG_RELAYD_THREAD_DISPATCHER_STALL"; @@ -201,6 +215,7 @@ int __testpoint_relayd_thread_dispatcher(void) return 0; } +int __testpoint_relayd_thread_worker(void); int __testpoint_relayd_thread_worker(void) { const char *var = "LTTNG_RELAYD_THREAD_WORKER_STALL"; @@ -212,6 +227,7 @@ int __testpoint_relayd_thread_worker(void) return 0; } +int __testpoint_relayd_thread_listener(void); int __testpoint_relayd_thread_listener(void) { const char *var = "LTTNG_RELAYD_THREAD_LISTENER_STALL"; @@ -223,6 +239,7 @@ int __testpoint_relayd_thread_listener(void) return 0; } +int __testpoint_relayd_thread_live_dispatcher(void); int __testpoint_relayd_thread_live_dispatcher(void) { const char *var = "LTTNG_RELAYD_THREAD_LIVE_DISPATCHER_STALL"; @@ -234,6 +251,7 @@ int __testpoint_relayd_thread_live_dispatcher(void) return 0; } +int __testpoint_relayd_thread_live_worker(void); int __testpoint_relayd_thread_live_worker(void) { const char *var = "LTTNG_RELAYD_THREAD_LIVE_WORKER_STALL"; @@ -245,6 +263,7 @@ int __testpoint_relayd_thread_live_worker(void) return 0; } +int __testpoint_relayd_thread_live_listener(void); int __testpoint_relayd_thread_live_listener(void) { const char *var = "LTTNG_RELAYD_THREAD_LIVE_LISTENER_STALL"; diff --git a/tests/regression/tools/live/live_test.c b/tests/regression/tools/live/live_test.c index ee75c04f7..5d10d228b 100644 --- a/tests/regression/tools/live/live_test.c +++ b/tests/regression/tools/live/live_test.c @@ -97,7 +97,7 @@ ssize_t lttng_live_send(int fd, const void *buf, size_t len) } static -int connect_viewer(char *hostname) +int connect_viewer(const char *hostname) { struct hostent *host; struct sockaddr_in server_addr; @@ -138,7 +138,7 @@ end: return ret; } -int establish_connection(void) +static int establish_connection(void) { struct lttng_viewer_cmd cmd; struct lttng_viewer_connect connect; @@ -182,7 +182,7 @@ error: /* * Returns the number of sessions, should be 1 during the unit test. */ -int list_sessions(uint64_t *session_id) +static int list_sessions(uint64_t *session_id) { struct lttng_viewer_cmd cmd; struct lttng_viewer_list_sessions list; @@ -229,7 +229,7 @@ error: return -1; } -int create_viewer_session(void) +static int create_viewer_session(void) { struct lttng_viewer_cmd cmd; struct lttng_viewer_create_session_response resp; @@ -267,7 +267,7 @@ error: return -1; } -int attach_session(uint64_t id) +static int attach_session(uint64_t id) { struct lttng_viewer_cmd cmd; struct lttng_viewer_attach_session_request rq; @@ -356,7 +356,7 @@ error: return -1; } -int get_metadata(void) +static int get_metadata(void) { struct lttng_viewer_cmd cmd; struct lttng_viewer_get_metadata rq; @@ -451,7 +451,7 @@ error: return -1; } -int get_next_index(void) +static int get_next_index(void) { struct lttng_viewer_cmd cmd; struct lttng_viewer_get_next_index rq; @@ -616,7 +616,7 @@ error: return -1; } -int detach_viewer_session(uint64_t id) +static int detach_viewer_session(uint64_t id) { struct lttng_viewer_cmd cmd; struct lttng_viewer_detach_session_response resp; diff --git a/tests/regression/tools/notification/Makefile.am b/tests/regression/tools/notification/Makefile.am index bc2c9123c..beb6cee6a 100644 --- a/tests/regression/tools/notification/Makefile.am +++ b/tests/regression/tools/notification/Makefile.am @@ -10,7 +10,7 @@ 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 = test_notification_ust_error test_notification_ust_buffer_usage test_notification_ust_event_rule_condition_exclusion test_notification_kernel_error test_notification_kernel_buffer_usage test_notification_kernel_instrumentation test_notification_kernel_syscall test_notification_kernel_userspace_probe test_notification_multi_app base_client.c notification.c consumer_testpoints.c else @@ -36,8 +36,8 @@ 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_ust_error test_notification_ust_buffer_usage test_notification_ust_event_rule_condition_exclusion test_notification_kernel_error test_notification_kernel_buffer_usage test_notification_kernel_instrumentation test_notification_kernel_syscall test_notification_kernel_userspace_probe test_notification_multi_app test_rotation +EXTRA_DIST = test_notification_ust_error test_notification_ust_buffer_usage test_notification_ust_event_rule_condition_exclusion test_notification_kernel_error test_notification_kernel_buffer_usage test_notification_kernel_instrumentation test_notification_kernel_syscall test_notification_kernel_userspace_probe test_notification_multi_app test_rotation all-local: diff --git a/tests/regression/tools/notification/base_client.c b/tests/regression/tools/notification/base_client.c index fcef66c21..18304b729 100644 --- a/tests/regression/tools/notification/base_client.c +++ b/tests/regression/tools/notification/base_client.c @@ -42,7 +42,8 @@ int handle_condition( const struct lttng_condition *condition, const struct lttng_evaluation *condition_evaluation); -int parse_arguments(char **argv) { +static int parse_arguments(char **argv) +{ const char *domain_type_string = NULL; const char *buffer_usage_type_string = NULL; const char *buffer_usage_threshold_type = NULL; diff --git a/tests/regression/tools/notification/consumer_testpoints.c b/tests/regression/tools/notification/consumer_testpoints.c index c43721606..0d1edc5f9 100644 --- a/tests/regression/tools/notification/consumer_testpoints.c +++ b/tests/regression/tools/notification/consumer_testpoints.c @@ -47,6 +47,7 @@ void __attribute__((destructor)) pause_pipe_fini(void) * 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 __testpoint_consumerd_thread_data(void) { int ret = 0; @@ -106,6 +107,7 @@ end: return ret; } +int __testpoint_consumerd_thread_data_poll(void); int __testpoint_consumerd_thread_data_poll(void) { int ret = 0; diff --git a/tests/regression/tools/notification/notification.c b/tests/regression/tools/notification/notification.c index 68ec64d01..436519c69 100644 --- a/tests/regression/tools/notification/notification.c +++ b/tests/regression/tools/notification/notification.c @@ -29,21 +29,25 @@ #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; +pid_t app_pid = 0; const char *app_state_file = NULL; static @@ -87,7 +91,7 @@ void wait_on_file(const char *path, bool file_exist) } } -int write_pipe(const char *path, uint8_t data) +static int write_pipe(const char *path, uint8_t data) { int ret = 0; int fd = 0; @@ -118,7 +122,7 @@ end: return ret; } -int stop_consumer(const char **argv) +static int stop_consumer(const char **argv) { int ret = 0, i; @@ -128,7 +132,7 @@ int stop_consumer(const char **argv) return ret; } -int resume_consumer(const char **argv) +static int resume_consumer(const char **argv) { int ret = 0, i; @@ -138,7 +142,7 @@ int resume_consumer(const char **argv) return ret; } -int suspend_application() +static int suspend_application(void) { int ret; struct stat buf; @@ -152,6 +156,8 @@ int suspend_application() /* * 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); @@ -166,7 +172,7 @@ error: } -int resume_application() +static int resume_application() { int ret; struct stat buf; @@ -181,6 +187,8 @@ int resume_application() goto error; } + assert(app_pid > 1); + ret = kill(app_pid, SIGUSR1); if (ret) { fail("SIGUSR1 failed. errno %d", errno); @@ -196,7 +204,7 @@ error: } -void test_triggers_buffer_usage_condition(const char *session_name, +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) @@ -389,46 +397,90 @@ void wait_data_pending(const char *session_name) } 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) +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) { - int ret = 0; enum lttng_condition_status condition_status; - enum lttng_notification_channel_status nc_status; + int ret = 0; - struct lttng_action *action = NULL; - struct lttng_notification *notification = NULL; - struct lttng_notification_channel *notification_channel = NULL; - struct lttng_trigger *trigger = NULL; + 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; + } - 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; + 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; + } - double low_ratio = 0.0; - double high_ratio = 0.99; + 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; + } - /* Set-up */ - action = lttng_action_notify_create(); - if (!action) { - fail("Setup error on action creation"); +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 condition for later test */ + /* + * 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) { @@ -436,168 +488,247 @@ void test_notification_channel(const char *session_name, const char *channel_nam 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"); + 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; } - /* 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; - } + /* + * 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"); - 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; + 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; } - 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; + 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(); } - /* 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; + 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( - high_condition, high_ratio); + tmp_condition, ratio); if (condition_status != LTTNG_CONDITION_STATUS_OK) { - fail("Setup error on high_condition creation"); - goto end; + fail("Setup error on condition creation"); + ret = -1; + goto error; } - 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; + 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; } - 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; + + /* 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; } - 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; + + ret = lttng_register_trigger(tmp_trigger); + if (ret) { + fail("Setup error on trigger registration"); + ret = -1; + goto error; } - /* Register the triggers for low and high condition */ - trigger = lttng_trigger_create(low_condition, action); - if (!trigger) { - fail("Setup error on low trigger creation"); + *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; } - ret = lttng_register_trigger(trigger); - if (ret) { - fail("Setup error on low trigger registration"); + /* 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); - trigger = NULL; + lttng_notification_channel_destroy(notification_channel); + lttng_action_destroy(action); + lttng_condition_destroy(condition); +} - trigger = lttng_trigger_create(high_condition, action); - if (!trigger) { - fail("Setup error on high trigger creation"); +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; + double high_ratio = 0.99; + + 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 = lttng_register_trigger(trigger); + 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); + 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"); + 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 condition"); + nc_status = lttng_notification_channel_subscribe( + notification_channel, high_condition); + ok(nc_status == LTTNG_NOTIFICATION_CHANNEL_STATUS_OK, + "Subscribe to high 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"); + 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, + 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; @@ -612,16 +743,22 @@ void test_notification_channel(const char *session_name, const char *channel_nam * 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_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_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, + 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; @@ -631,9 +768,12 @@ void test_notification_channel(const char *session_name, const char *channel_nam resume_application(); lttng_start_tracing(session_name); - nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); + 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, + 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; @@ -643,9 +783,12 @@ void test_notification_channel(const char *session_name, const char *channel_nam resume_consumer(argv); wait_data_pending(session_name); - nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); + 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, + 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; @@ -655,77 +798,892 @@ void test_notification_channel(const char *session_name, const char *channel_nam /* Stop consumer to force a high notification */ lttng_start_tracing(session_name); - nc_status = lttng_notification_channel_get_next_notification(notification_channel, ¬ification); + 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, + 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(); + + /* 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"); + 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_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); - lttng_condition_destroy(dummy_invalid_condition); - lttng_condition_destroy(dummy_condition); } -int main(int argc, const char *argv[]) +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, + struct lttng_condition **condition, + struct lttng_trigger **trigger) { - 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; + enum lttng_event_rule_status event_rule_status; + enum lttng_trigger_status trigger_status; - plan_tests(NUM_TESTS); + 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); + } - /* Argument 6 and upward are named pipe location for consumerd control */ - named_pipe_args_start = 6; + if (exclusions) { + assert(domain_type == LTTNG_DOMAIN_UST); + assert(exclusion_count > 0); - if (argc < 7) { - fail("Missing parameter for tests to run %d", argc); - goto error; + 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"); } - nb_args = argc; + 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; - 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]; + tmp_action = lttng_action_notify_create(); + ok(tmp_action, "Action event rule object creation"); - if (!strcmp("LTTNG_DOMAIN_UST", domain_type_string)) { - domain_type = LTTNG_DOMAIN_UST; + 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; + *trigger = tmp_trigger; + + return; +} + +static char *get_next_notification_trigger_name( + struct lttng_notification_channel *notification_channel) +{ + struct lttng_notification *notification; + enum lttng_notification_channel_status status; + const struct lttng_evaluation *notification_evaluation; + char *trigger_name = NULL; + 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; + default: + /* Unhandled conditions / errors. */ + fail("error: Unknown notification channel status\n"); + goto end; } - if (!strcmp("LTTNG_DOMAIN_KERNEL", domain_type_string)) { - domain_type = LTTNG_DOMAIN_KERNEL; + + 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); + + trigger_name = strdup(name); + break; + default: + fail("error: Wrong notification evaluation type \n"); + break; } - if (domain_type == LTTNG_DOMAIN_NONE) { - fail("Unknown domain type"); - goto error; + + lttng_notification_destroy(notification); + +end: + return trigger_name; +} + +static void test_tracepoint_event_rule_notification( + enum lttng_domain_type domain_type) +{ + int i; + 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, &condition, &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(); + + /* Get 3 notifications */ + for (i = 0; i < 3; i++) { + char *name = get_next_notification_trigger_name( + notification_channel); + ok(strcmp(trigger_name, name) == 0, + "Received notification for the expected trigger name: %s", + trigger_name); + free(name); } - 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); + 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_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, &ctrl_condition, &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, &condition, + &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++) { + char *name = get_next_notification_trigger_name( + notification_channel); + if (strcmp(ctrl_trigger_name, name) == 0) { + ctrl_count++; + } else if (strcmp(trigger_name, name) == 0) { + count++; + } + free(name); + } + ok(ctrl_count / 2 == count, + "Get twice as many control notif as of regular notif"); + + 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); + return; +} + +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_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"); + + create_tracepoint_event_rule_trigger(pattern, ctrl_trigger_name, NULL, + 0, NULL, domain_type, &ctrl_condition, &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"); + + create_tracepoint_event_rule_trigger(pattern, trigger_name, NULL, 4, + exclusions, domain_type, &condition, &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 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++) { + char *name = get_next_notification_trigger_name( + notification_channel); + if (strcmp(ctrl_trigger_name, name) == 0) { + ctrl_count++; + } else if (strcmp(trigger_name, name) == 0) { + count++; + } + free(name); + } + ok(ctrl_count / 5 == count, + "Got 5 times as many control notif as of regular notif"); + + 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); + return; +} + +static void test_kprobe_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 = "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++) { + char *name = get_next_notification_trigger_name( + notification_channel); + ok(strcmp(trigger_name, name) == 0, + "Received notification for the expected trigger name: %s", + trigger_name); + free(name); + } + +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_uprobe_event_rule_notification( + enum lttng_domain_type domain_type, + const char *testapp_path, + const char *test_symbol_name) +{ + 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_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; + + action = lttng_action_notify_create(); + if (!action) { + fail("Setup error on action creation"); + goto end; + } + + 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; + } + + 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; + } + + 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; + } + + 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++) { + char *name = get_next_notification_trigger_name( + notification_channel); + ok(strcmp(trigger_name, name) == 0, + "Received notification for the expected trigger name: %s", + trigger_name); + free(name); + } +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; + } + + 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); + + 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++) { + char *name = get_next_notification_trigger_name( + notification_channel); + ok(strcmp(trigger_name, name) == 0, + "Received notification for the expected trigger name: %s", + trigger_name); + free(name); + } +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; + } + + 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 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++) { + char *name = get_next_notification_trigger_name( + notification_channel); + ok(strcmp(trigger_name, name) == 0, + "Received notification for the expected trigger name: %s", + trigger_name); + free(name); + } + +end: + suspend_application(); + lttng_unregister_trigger(trigger); + lttng_notification_channel_destroy(notification_channel); + lttng_trigger_destroy(trigger); + lttng_condition_destroy(condition); + return; +} + +int main(int argc, const char *argv[]) +{ + int test_scenario; + const char *domain_type_string = NULL; + enum lttng_domain_type domain_type = LTTNG_DOMAIN_NONE; + + if (argc < 5) { + fail("Missing test scenario, domain type, pid, or application state file argument(s)"); + goto error; + } + + 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; + } + 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; + } + + /* + * 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(38); + + /* 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(19); + + /* + * 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(10); + /* 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(19); + /* 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(10); + + 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; + } + 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..36a238c5f --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_buffer_usage @@ -0,0 +1,126 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# 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" +TESTAPP_NAME="gen-ust-events" +TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME" +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +SYSCALL_TESTAPP_NAME="gen-syscall-events" +SYSCALL_TESTAPP_BIN="$TESTAPP_PATH/$SYSCALL_TESTAPP_NAME/$SYSCALL_TESTAPP_NAME" + +USERSPACE_PROBE_ELF_TESTAPP_NAME="userspace-probe-elf-binary" +USERSPACE_PROBE_ELF_TESTAPP_BIN="$TESTAPP_PATH/$USERSPACE_PROBE_ELF_TESTAPP_NAME/.libs/$USERSPACE_PROBE_ELF_TESTAPP_NAME" + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +TRACE_PATH=$(mktemp -d) +PAGE_SIZE=$(getconf PAGE_SIZE) + +DIR=$(readlink -f $TESTDIR) +NUM_TESTS=104 + +source $TESTDIR/utils/utils.sh + +function kernel_event_generator_toggle_state +{ + kernel_event_generator_suspended=$((kernel_event_generator_suspended==0)) +} + +function generate_filter_events +{ + /bin/echo -n "10" > /proc/lttng-test-filter-event 2> /dev/null +} + +function kernel_event_generator +{ + command_to_run=$1 + state_file=$2 + 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 + $command_to_run + fi + done +} + +function test_buffer_usage_notification +{ + local consumerd_pipe=$1 + local event_name="lttng_test_filter_event" + + 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=$! + + $CURDIR/notification 2 LTTNG_DOMAIN_KERNEL $APP_PID $TESTAPP_STATE_PATH \ + $SESSION_NAME $CHANNEL_NAME ${consumerd_pipe[@]} + + destroy_lttng_session_notap $SESSION_NAME + + kill -9 $APP_PID + wait $APP_PID 2> /dev/null +} + +if [ "$(id -u)" == "0" ]; then + consumerd_pipe=() + + validate_lttng_modules_present + + # 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 + + modprobe lttng-test + + 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 $consumerd_pipe + + 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 + +# Just in case cleanup +rm -rf $TRACE_PATH +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..fca7ef540 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_error @@ -0,0 +1,115 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# 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" +TESTAPP_NAME="gen-ust-events" +TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME" +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +SYSCALL_TESTAPP_NAME="gen-syscall-events" +SYSCALL_TESTAPP_BIN="$TESTAPP_PATH/$SYSCALL_TESTAPP_NAME/$SYSCALL_TESTAPP_NAME" + +USERSPACE_PROBE_ELF_TESTAPP_NAME="userspace-probe-elf-binary" +USERSPACE_PROBE_ELF_TESTAPP_BIN="$TESTAPP_PATH/$USERSPACE_PROBE_ELF_TESTAPP_NAME/.libs/$USERSPACE_PROBE_ELF_TESTAPP_NAME" + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +TRACE_PATH=$(mktemp -d) +PAGE_SIZE=$(getconf PAGE_SIZE) + +DIR=$(readlink -f $TESTDIR) +NUM_TESTS=104 + +source $TESTDIR/utils/utils.sh + +function kernel_event_generator_toggle_state +{ + kernel_event_generator_suspended=$((kernel_event_generator_suspended==0)) +} + +function generate_filter_events +{ + /bin/echo -n "10" > /proc/lttng-test-filter-event 2> /dev/null +} + +function kernel_event_generator +{ + command_to_run=$1 + state_file=$2 + 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 + $command_to_run + fi + done +} + +function test_basic_error_path +{ + kernel_event_generator generate_filter_events $TESTAPP_STATE_PATH & + APP_PID=$! + + $CURDIR/notification 1 LTTNG_DOMAIN_KERNEL $APP_PID $TESTAPP_STATE_PATH + + kill -9 $APP_PID + wait $APP_PID 2> /dev/null +} + + +if [ "$(id -u)" == "0" ]; then + consumerd_pipe=() + + validate_lttng_modules_present + + # 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 + + modprobe lttng-test + + LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 \ + CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} \ + LD_PRELOAD=${TESTPOINT}" + + start_lttng_sessiond_notap + + test_basic_error_path + + 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 + +# Just in case cleanup +rm -rf $TRACE_PATH +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..236775aed --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_instrumentation @@ -0,0 +1,125 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# 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" +TESTAPP_NAME="gen-ust-events" +TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME" +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +SYSCALL_TESTAPP_NAME="gen-syscall-events" +SYSCALL_TESTAPP_BIN="$TESTAPP_PATH/$SYSCALL_TESTAPP_NAME/$SYSCALL_TESTAPP_NAME" + +USERSPACE_PROBE_ELF_TESTAPP_NAME="userspace-probe-elf-binary" +USERSPACE_PROBE_ELF_TESTAPP_BIN="$TESTAPP_PATH/$USERSPACE_PROBE_ELF_TESTAPP_NAME/.libs/$USERSPACE_PROBE_ELF_TESTAPP_NAME" + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +TRACE_PATH=$(mktemp -d) +PAGE_SIZE=$(getconf PAGE_SIZE) + +DIR=$(readlink -f $TESTDIR) +NUM_TESTS=104 + +source $TESTDIR/utils/utils.sh + +function kernel_event_generator_toggle_state +{ + kernel_event_generator_suspended=$((kernel_event_generator_suspended==0)) +} + +function generate_filter_events +{ + /bin/echo -n "10" > /proc/lttng-test-filter-event 2> /dev/null +} + +function generate_syscalls +{ + # Pass /dev/null so to generate the syscall right away. + $SYSCALL_TESTAPP_BIN /dev/null +} + +function userspace_probe_testapp +{ + $USERSPACE_PROBE_ELF_TESTAPP_BIN +} + +function kernel_event_generator +{ + command_to_run=$1 + state_file=$2 + 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 + $command_to_run + fi + done +} + +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 -9 $APP_PID + wait $APP_PID 2> /dev/null +} + +if [ "$(id -u)" == "0" ]; then + consumerd_pipe=() + + validate_lttng_modules_present + + # 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 + + modprobe lttng-test + + LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 \ + CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} \ + LD_PRELOAD=${TESTPOINT}" + + start_lttng_sessiond_notap + + test_kernel_instrumentation_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 + +# Just in case cleanup +rm -rf $TRACE_PATH +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..05f9e277d --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_syscall @@ -0,0 +1,125 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# 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" +TESTAPP_NAME="gen-ust-events" +TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME" +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +SYSCALL_TESTAPP_NAME="gen-syscall-events" +SYSCALL_TESTAPP_BIN="$TESTAPP_PATH/$SYSCALL_TESTAPP_NAME/$SYSCALL_TESTAPP_NAME" + +USERSPACE_PROBE_ELF_TESTAPP_NAME="userspace-probe-elf-binary" +USERSPACE_PROBE_ELF_TESTAPP_BIN="$TESTAPP_PATH/$USERSPACE_PROBE_ELF_TESTAPP_NAME/.libs/$USERSPACE_PROBE_ELF_TESTAPP_NAME" + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +TRACE_PATH=$(mktemp -d) +PAGE_SIZE=$(getconf PAGE_SIZE) + +DIR=$(readlink -f $TESTDIR) +NUM_TESTS=104 + +source $TESTDIR/utils/utils.sh + +function kernel_event_generator_toggle_state +{ + kernel_event_generator_suspended=$((kernel_event_generator_suspended==0)) +} + +function generate_filter_events +{ + /bin/echo -n "10" > /proc/lttng-test-filter-event 2> /dev/null +} + +function generate_syscalls +{ + # Pass /dev/null so to generate the syscall right away. + $SYSCALL_TESTAPP_BIN /dev/null +} + +function userspace_probe_testapp +{ + $USERSPACE_PROBE_ELF_TESTAPP_BIN +} + +function kernel_event_generator +{ + command_to_run=$1 + state_file=$2 + 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 + $command_to_run + fi + done +} + +function test_kernel_syscall_notification +{ + kernel_event_generator generate_syscalls $TESTAPP_STATE_PATH & + APP_PID=$! + + $CURDIR/notification 5 LTTNG_DOMAIN_KERNEL $APP_PID $TESTAPP_STATE_PATH + + kill -9 $APP_PID + wait $APP_PID 2> /dev/null +} + +if [ "$(id -u)" == "0" ]; then + consumerd_pipe=() + + validate_lttng_modules_present + + # 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 + + modprobe lttng-test + + LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 \ + CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} \ + LD_PRELOAD=${TESTPOINT}" + + start_lttng_sessiond_notap + + test_kernel_syscall_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 + +# Just in case cleanup +rm -rf $TRACE_PATH +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..253631028 --- /dev/null +++ b/tests/regression/tools/notification/test_notification_kernel_userspace_probe @@ -0,0 +1,116 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# 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" +TESTAPP_NAME="gen-ust-events" +TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME" +TESTAPP_STATE_PATH=$(mktemp -u "$TMPDIR/application_state.XXXXXXXXXX") + +SYSCALL_TESTAPP_NAME="gen-syscall-events" +SYSCALL_TESTAPP_BIN="$TESTAPP_PATH/$SYSCALL_TESTAPP_NAME/$SYSCALL_TESTAPP_NAME" + +USERSPACE_PROBE_ELF_TESTAPP_NAME="userspace-probe-elf-binary" +USERSPACE_PROBE_ELF_TESTAPP_BIN="$TESTAPP_PATH/$USERSPACE_PROBE_ELF_TESTAPP_NAME/.libs/$USERSPACE_PROBE_ELF_TESTAPP_NAME" + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +TRACE_PATH=$(mktemp -d) +PAGE_SIZE=$(getconf PAGE_SIZE) + +DIR=$(readlink -f $TESTDIR) +NUM_TESTS=104 + +source $TESTDIR/utils/utils.sh + +function kernel_event_generator_toggle_state +{ + kernel_event_generator_suspended=$((kernel_event_generator_suspended==0)) +} + +function userspace_probe_testapp +{ + $USERSPACE_PROBE_ELF_TESTAPP_BIN +} + +function kernel_event_generator +{ + command_to_run=$1 + state_file=$2 + 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 + $command_to_run + fi + done +} + +function test_kernel_userspace_probe_notification +{ + kernel_event_generator userspace_probe_testapp $TESTAPP_STATE_PATH & + APP_PID=$! + + $CURDIR/notification 6 LTTNG_DOMAIN_KERNEL \ + $APP_PID $TESTAPP_STATE_PATH \ + $USERSPACE_PROBE_ELF_TESTAPP_BIN "test_function" + + kill -9 $APP_PID + wait $APP_PID 2> /dev/null +} + +if [ "$(id -u)" == "0" ]; then + consumerd_pipe=() + + validate_lttng_modules_present + + # 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 + + modprobe lttng-test + + LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 \ + CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} \ + LD_PRELOAD=${TESTPOINT}" + + start_lttng_sessiond_notap + + test_kernel_userspace_probe_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 + +# Just in case cleanup +rm -rf $TRACE_PATH +rm -rf $TMPDIR 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..8488dceeb --- /dev/null +++ b/tests/regression/tools/notification/test_notification_ust_buffer_usage @@ -0,0 +1,106 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# 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" + +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") + +NR_ITER=5 +NR_USEC_WAIT=5 + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +TRACE_PATH=$(mktemp -d) +PAGE_SIZE=$(getconf PAGE_SIZE) + +DIR=$(readlink -f $TESTDIR) + + +source $TESTDIR/utils/utils.sh + +function ust_event_generator_toggle_state +{ + ust_event_generator_suspended=$((ust_event_generator_suspended==0)) + +} +function ust_event_generator +{ + test_app=$1 + state_file=$2 + ust_event_generator_suspended=0 + trap ust_event_generator_toggle_state SIGUSR1 + + while (true); do + if [[ $ust_event_generator_suspended -eq "1" ]]; then + touch $state_file + sleep 0.5 + else + if [[ -f $state_file ]]; then + rm -rf $state_file 2> /dev/null + fi + taskset -c 0 $test_app -i $NR_ITER -w $NR_USEC_WAIT > /dev/null 2>&1 + fi + done +} + +function test_buffer_usage_notification +{ + consumerd_pipe=() + file_sync_after_first_event=$(mktemp -u) + 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 -9 $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_error b/tests/regression/tools/notification/test_notification_ust_error new file mode 100755 index 000000000..9b90b7efe --- /dev/null +++ b/tests/regression/tools/notification/test_notification_ust_error @@ -0,0 +1,85 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# 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" + +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") + +NR_ITER=5 +NR_USEC_WAIT=5 + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +TRACE_PATH=$(mktemp -d) +PAGE_SIZE=$(getconf PAGE_SIZE) + +DIR=$(readlink -f $TESTDIR) + + +source $TESTDIR/utils/utils.sh + +function ust_event_generator_toggle_state +{ + ust_event_generator_suspended=$((ust_event_generator_suspended==0)) + +} +function ust_event_generator +{ + test_app=$1 + state_file=$2 + ust_event_generator_suspended=0 + trap ust_event_generator_toggle_state SIGUSR1 + + while (true); do + if [[ $ust_event_generator_suspended -eq "1" ]]; then + touch $state_file + sleep 0.5 + else + if [[ -f $state_file ]]; then + rm -rf $state_file 2> /dev/null + fi + taskset -c 0 $test_app -i $NR_ITER -w $NR_USEC_WAIT > /dev/null 2>&1 + fi + done +} + +function test_basic_error_path +{ + ust_event_generator $GEN_UST_EVENTS_TESTAPP_BIN $TESTAPP_STATE_PATH & + APP_PID=$! + + $CURDIR/notification 1 LTTNG_DOMAIN_UST $APP_PID $TESTAPP_STATE_PATH + + kill -9 $APP_PID + wait $APP_PID 2> /dev/null +} + +LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} LD_PRELOAD=${TESTPOINT}" +start_lttng_sessiond_notap + +test_basic_error_path + +stop_lttng_sessiond_notap + +rm -rf $TMPDIR 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..8f2ed739b --- /dev/null +++ b/tests/regression/tools/notification/test_notification_ust_event_rule_condition_exclusion @@ -0,0 +1,85 @@ +#!/bin/bash +# +# Copyright (C) 2017 Jonathan Rajotte-Julien +# +# 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" + +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") + +NR_ITER=5 +NR_USEC_WAIT=5 + +SESSION_NAME="my_session" +CHANNEL_NAME="my_channel" + +TRACE_PATH=$(mktemp -d) +PAGE_SIZE=$(getconf PAGE_SIZE) + +DIR=$(readlink -f $TESTDIR) + + +source $TESTDIR/utils/utils.sh + +function ust_event_generator_toggle_state +{ + ust_event_generator_suspended=$((ust_event_generator_suspended==0)) + +} +function ust_event_generator +{ + test_app=$1 + state_file=$2 + ust_event_generator_suspended=0 + trap ust_event_generator_toggle_state SIGUSR1 + + while (true); do + if [[ $ust_event_generator_suspended -eq "1" ]]; then + touch $state_file + sleep 0.5 + else + if [[ -f $state_file ]]; then + rm -rf $state_file 2> /dev/null + fi + taskset -c 0 $test_app -i $NR_ITER -w $NR_USEC_WAIT > /dev/null 2>&1 + fi + done +} + +function test_event_rule_condition_exclusion_notification +{ + ust_event_generator $GEN_UST_NEVENTS_TESTAPP_BIN $TESTAPP_STATE_PATH & + APP_PID=$! + + $CURDIR/.libs/notification 3 LTTNG_DOMAIN_UST $APP_PID $TESTAPP_STATE_PATH + + kill -9 $APP_PID + wait $APP_PID 2> /dev/null +} + +LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 CONSUMER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} LD_PRELOAD=${TESTPOINT}" +start_lttng_sessiond_notap + +test_event_rule_condition_exclusion_notification + +stop_lttng_sessiond_notap + +rm -rf $TMPDIR diff --git a/tests/regression/tools/rotation/schedule_api.c b/tests/regression/tools/rotation/schedule_api.c index 03c446fcb..1e19f5be3 100644 --- a/tests/regression/tools/rotation/schedule_api.c +++ b/tests/regression/tools/rotation/schedule_api.c @@ -22,7 +22,7 @@ const char *session_name; -bool schedules_equal(const struct lttng_rotation_schedule *a, +static bool schedules_equal(const struct lttng_rotation_schedule *a, const struct lttng_rotation_schedule *b) { bool equal = false; @@ -84,7 +84,7 @@ end: return equal; } -void test_add_null_session(void) +static void test_add_null_session(void) { enum lttng_rotation_status status; struct lttng_rotation_schedule *size_schedule = NULL; @@ -97,7 +97,7 @@ void test_add_null_session(void) lttng_rotation_schedule_destroy(size_schedule); } -void test_add_null_schedule(void) +static void test_add_null_schedule(void) { enum lttng_rotation_status status; @@ -106,7 +106,7 @@ void test_add_null_schedule(void) "NULL schedule rejected by lttng_session_add_rotation_schedule()"); } -void test_add_uninitialized_schedule(void) +static void test_add_uninitialized_schedule(void) { enum lttng_rotation_status status; struct lttng_rotation_schedule *size_schedule = NULL, @@ -132,7 +132,7 @@ void test_add_uninitialized_schedule(void) lttng_rotation_schedule_destroy(periodic_schedule); } -void test_remove_null_session(void) +static void test_remove_null_session(void) { enum lttng_rotation_status status; struct lttng_rotation_schedule *size_schedule = NULL; @@ -145,7 +145,7 @@ void test_remove_null_session(void) lttng_rotation_schedule_destroy(size_schedule); } -void test_remove_null_schedule(void) +static void test_remove_null_schedule(void) { enum lttng_rotation_status status; @@ -154,7 +154,7 @@ void test_remove_null_schedule(void) "NULL schedule rejected by lttng_session_remove_rotation_schedule()"); } -void test_remove_uninitialized_schedule(void) +static void test_remove_uninitialized_schedule(void) { enum lttng_rotation_status status; struct lttng_rotation_schedule *size_schedule = NULL, @@ -176,7 +176,7 @@ void test_remove_uninitialized_schedule(void) lttng_rotation_schedule_destroy(periodic_schedule); } -void test_uninitialized_schedule_get(void) +static void test_uninitialized_schedule_get(void) { uint64_t value; enum lttng_rotation_status status; @@ -200,7 +200,7 @@ void test_uninitialized_schedule_get(void) } -void test_add_list_remove_schedule( +static void test_add_list_remove_schedule( const struct lttng_rotation_schedule *original_schedule) { int ret; @@ -249,7 +249,7 @@ void test_add_list_remove_schedule( } -void test_add_list_remove_size_schedule(void) +static void test_add_list_remove_size_schedule(void) { struct lttng_rotation_schedule *size_schedule; @@ -261,7 +261,7 @@ void test_add_list_remove_size_schedule(void) lttng_rotation_schedule_destroy(size_schedule); } -void test_add_list_remove_periodic_schedule(void) +static void test_add_list_remove_periodic_schedule(void) { struct lttng_rotation_schedule *periodic_schedule; diff --git a/tests/regression/tools/trigger/Makefile.am b/tests/regression/tools/trigger/Makefile.am new file mode 100644 index 000000000..ba55d31b1 --- /dev/null +++ b/tests/regression/tools/trigger/Makefile.am @@ -0,0 +1,55 @@ +AM_CFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src -I$(top_srcdir)/tests -I$(top_srcdir)/tests/utils/ -I$(srcdir) +AM_LDFLAGS = + + +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/test_add_trigger_cli b/tests/regression/tools/trigger/test_add_trigger_cli new file mode 100755 index 000000000..ff9bdbb54 --- /dev/null +++ b/tests/regression/tools/trigger/test_add_trigger_cli @@ -0,0 +1,330 @@ +#!/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 171 + +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 + +# `--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" "Missing --condition." + +test_failure "unknown option" \ + "Unknown option \`--hello\`" \ + --hello + +test_failure "missing --action" \ + "Need at least one --action." \ + --condition on-event hello -u + +test_failure "two --condition" \ + "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" \ + "Failed to parse \`123bob\` as an integer." \ + --condition on-event -u -a --action notify \ + --${cmd} 123bob + + test_failure "invalid argument to --${cmd}: empty string" \ + "Failed to parse \`\` as an integer." \ + --condition on-event -u -a --action notify \ + --${cmd} "" +done + +# `--condition` failures +test_failure "missing args after --condition" \ + "Missing condition name." \ + --condition +test_failure "unknown --condition" \ + "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 + +# `--action` failures +test_failure "missing args after --action" \ + "Missing action name." \ + --condition on-event -u -a \ + --action + +# `--action notify` failures +test_failure "extra arg after --action notify" \ + "Unexpected argument \`bob\`." \ + --condition on-event -u -a \ + --action notify bob + +# `--action start-session` failures +test_failure "missing arg after --action start-session" \ + "Missing session name." \ + --condition on-event some-event-start-session -u \ + --action start-session +test_failure "extra arg after --action start-session" \ + "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" \ + "Missing session name." \ + --condition on-event some-event-stop-session -u \ + --action stop-session +test_failure "extra arg after --action stop-session" \ + "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" \ + "Missing session name." \ + --condition on-event some-event-rotate-session -u \ + --action rotate-session +test_failure "extra arg after --action rotate-session" \ + "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" \ + "Missing session name." \ + --condition on-event some-event-snapshot-session -u \ + --action snapshot-session +test_failure "extra arg after --action snapshot-session" \ + "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" \ + "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" \ + "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..202a7b70a --- /dev/null +++ b/tests/regression/tools/trigger/test_list_triggers_cli @@ -0,0 +1,301 @@ +#!/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 41 + +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 + + 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 + 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 81035b831..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-Julien +# 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 ad8b0b021..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-Julien +# 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/ust/clock-override/lttng-ust-clock-override-test.c b/tests/regression/ust/clock-override/lttng-ust-clock-override-test.c index b2c20bee3..dace1d99c 100644 --- a/tests/regression/ust/clock-override/lttng-ust-clock-override-test.c +++ b/tests/regression/ust/clock-override/lttng-ust-clock-override-test.c @@ -49,6 +49,7 @@ const char *plugin_description(void) return "Freeze time with 1KHz for regression test"; } +void lttng_ust_clock_plugin_init(void); void lttng_ust_clock_plugin_init(void) { int ret; diff --git a/tests/regression/ust/fork/fork.c b/tests/regression/ust/fork/fork.c index 55c9d49bc..7030a293d 100644 --- a/tests/regression/ust/fork/fork.c +++ b/tests/regression/ust/fork/fork.c @@ -34,7 +34,7 @@ int main(int argc, char **argv, char *env[]) return 1; } if (result == 0) { - char *args[] = { "fork2", NULL }; + char *args[] = { (char *) "fork2", NULL }; tracepoint(ust_tests_fork, after_fork_child, getpid()); diff --git a/tests/regression/ust/getcpu-override/lttng-ust-getcpu-override-test.c b/tests/regression/ust/getcpu-override/lttng-ust-getcpu-override-test.c index f1b707425..c38706311 100644 --- a/tests/regression/ust/getcpu-override/lttng-ust-getcpu-override-test.c +++ b/tests/regression/ust/getcpu-override/lttng-ust-getcpu-override-test.c @@ -18,7 +18,7 @@ static long nprocessors; -int plugin_getcpu(void) +static int plugin_getcpu(void) { /* Generate a sequence based on the number of configurated processor * by using sequence[i] % nb_configured_processors. Where sequence @@ -57,6 +57,7 @@ int plugin_getcpu(void) return ret; } +void lttng_ust_getcpu_plugin_init(void); void lttng_ust_getcpu_plugin_init(void) { int ret; diff --git a/tests/regression/ust/high-throughput/main.c b/tests/regression/ust/high-throughput/main.c index b59c9241f..fab148790 100644 --- a/tests/regression/ust/high-throughput/main.c +++ b/tests/regression/ust/high-throughput/main.c @@ -21,11 +21,11 @@ #define TRACEPOINT_DEFINE #include "tp.h" -void inthandler(int sig) +static void inthandler(int sig) { } -int init_int_handler(void) +static int init_int_handler(void) { int result; struct sigaction act; diff --git a/tests/regression/ust/low-throughput/tp.h b/tests/regression/ust/low-throughput/tp.h index 3fa8c9a2d..3ec3c40c1 100644 --- a/tests/regression/ust/low-throughput/tp.h +++ b/tests/regression/ust/low-throughput/tp.h @@ -14,7 +14,7 @@ #include TRACEPOINT_EVENT(tp, slow, - TP_ARGS(unsigned int, c, char *, thread_name), + TP_ARGS(unsigned int, c, const char *, thread_name), TP_FIELDS( ctf_integer(unsigned int, counter, c) ctf_string(th_name, thread_name) diff --git a/tests/regression/ust/multi-lib/callsites.c b/tests/regression/ust/multi-lib/callsites.c index eaa613708..a67f102b4 100644 --- a/tests/regression/ust/multi-lib/callsites.c +++ b/tests/regression/ust/multi-lib/callsites.c @@ -18,7 +18,9 @@ #define VALUE (-1) #endif -void call_tracepoint(void) { +void call_tracepoint(void); +void call_tracepoint(void) +{ tracepoint(multi, tp, VALUE); } diff --git a/tests/regression/ust/multi-lib/multi-lib-test.c b/tests/regression/ust/multi-lib/multi-lib-test.c index 6a91089df..6ebb13d7a 100644 --- a/tests/regression/ust/multi-lib/multi-lib-test.c +++ b/tests/regression/ust/multi-lib/multi-lib-test.c @@ -15,6 +15,7 @@ #include "callsites.h" #endif +void exec_callsite(); void exec_callsite() { #if HAS_CALLSITES @@ -22,7 +23,7 @@ void exec_callsite() #endif } -void print_list(void) +static void print_list(void) { fprintf(stderr, "Test list (-t X):\n"); fprintf(stderr, "\t0: dlopen() all libraries pass in arguments and execute " @@ -31,7 +32,8 @@ void print_list(void) fprintf(stderr, "\t2: simulate the upgrade of a library containing the callsites using dlopen() and dlclose(). \n"); } -int dl_open_all(int nb_libraries, char **libraries) +#if HAS_CALLSITES +static int dl_open_all(int nb_libraries, char **libraries) { int i, ret = 0; void **handles; @@ -56,12 +58,14 @@ error: free(handles); return ret; } +#endif +#if HAS_CALLSITES /* * Takes 2 paths to libraries, dlopen() the first, trace, dlopen() the second, * and dlclose the first to simulate the upgrade of a library. */ -int upgrade_lib(int nb_libraries, char **libraries) +static int upgrade_lib(int nb_libraries, char **libraries) { int i, ret = 0; void *handles[2]; @@ -92,12 +96,14 @@ int upgrade_lib(int nb_libraries, char **libraries) error: return ret; } +#endif +#if !HAS_CALLSITES /* * Simulate the upgrade of a library containing a callsite. * Receives two libraries containing callsites for the same tracepoint. */ -int upgrade_callsite(int nb_libraries, char **libraries) +static int upgrade_callsite(int nb_libraries, char **libraries) { int ret = 0; void *handles[2]; @@ -159,6 +165,7 @@ int upgrade_callsite(int nb_libraries, char **libraries) error: return ret; } +#endif int main(int argc, const char **argv) { diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 09de39e8c..1b69b1f6f 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -17,11 +17,13 @@ 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_uuid \ + test_buffer_view LIBTAP=$(top_builddir)/tests/utils/tap/libtap.la @@ -39,7 +41,8 @@ noinst_PROGRAMS = test_uri test_session test_kernel_data \ test_utils_expand_path test_utils_compat_poll \ test_string_utils test_notification test_directory_handle \ test_relayd_backward_compat_group_by_session \ - test_fd_tracker test_uuid + test_fd_tracker test_uuid \ + test_buffer_view test_fd_tracker test_event_rule test_condition if HAVE_LIBLTTNG_UST_CTL noinst_PROGRAMS += test_ust_data @@ -59,6 +62,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) \ @@ -182,6 +186,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 @@ -195,3 +207,7 @@ test_fd_tracker_LDADD = $(LIBTAP) $(LIBFDTRACKER) $(DL_LIBS) -lurcu $(LIBCOMMON) # uuid unit test test_uuid_SOURCES = test_uuid.c test_uuid_LDADD = $(LIBTAP) $(LIBCOMMON) + +# buffer view unit test +test_buffer_view_SOURCES = test_buffer_view.c +test_buffer_view_LDADD = $(LIBTAP) $(LIBCOMMON) diff --git a/tests/unit/test_buffer_view.c b/tests/unit/test_buffer_view.c new file mode 100644 index 000000000..b6c772d64 --- /dev/null +++ b/tests/unit/test_buffer_view.c @@ -0,0 +1,50 @@ +/* + * Copyright (C) - EfficiOS, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by as + * published by the Free Software Foundation; only version 2 of the License. + * + * 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 + +static const int TEST_COUNT = 5; + +/* For error.h */ +int lttng_opt_quiet = 1; +int lttng_opt_verbose; +int lttng_opt_mi; + +static void test_validate_string(void) +{ + const char buf[] = {'A', 'l', 'l', 'o', '\0'}; + struct lttng_buffer_view view = lttng_buffer_view_init(buf, 0, 5); + struct lttng_buffer_view view_minus_one = + lttng_buffer_view_init(buf, 0, 4); + + ok1(!lttng_buffer_view_validate_string(&view, buf, 4)); + ok1(lttng_buffer_view_validate_string(&view, buf, 5)); + ok1(!lttng_buffer_view_validate_string(&view, buf, 6)); + + ok1(!lttng_buffer_view_validate_string(&view_minus_one, buf, 4)); + ok1(!lttng_buffer_view_validate_string(&view_minus_one, buf, 5)); +} + +int main(void) +{ + plan_tests(TEST_COUNT); + + test_validate_string(); + + return exit_status(); +} diff --git a/tests/unit/test_condition.c b/tests/unit/test_condition.c new file mode 100644 index 000000000..9ae793fda --- /dev/null +++ b/tests/unit/test_condition.c @@ -0,0 +1,108 @@ +/* + * test_condition.c + * + * Unit tests for the condition 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 +#include + +/* For error.h */ +int lttng_opt_quiet = 1; +int lttng_opt_verbose; +int lttng_opt_mi; + +#define NUM_TESTS 3 + +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_directory_handle.c b/tests/unit/test_directory_handle.c index f3213cb74..7bbb648d1 100644 --- a/tests/unit/test_directory_handle.c +++ b/tests/unit/test_directory_handle.c @@ -126,7 +126,8 @@ end: } /* Remove "file1" from the test folder hierarchy. */ -int remove_file_from_hierarchy(struct lttng_directory_handle *test_dir_handle, +static int remove_file_from_hierarchy( + struct lttng_directory_handle *test_dir_handle, const char *test_root_name) { int ret; diff --git a/tests/unit/test_event_rule.c b/tests/unit/test_event_rule.c new file mode 100644 index 000000000..590364518 --- /dev/null +++ b/tests/unit/test_event_rule.c @@ -0,0 +1,152 @@ +/* + * 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 + +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); +} + +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/unit/test_fd_tracker.c b/tests/unit/test_fd_tracker.c index cb192ff80..928f3755a 100644 --- a/tests/unit/test_fd_tracker.c +++ b/tests/unit/test_fd_tracker.c @@ -76,7 +76,7 @@ void get_temporary_directories(char **_test_directory, char **_unlink_directory) } } -int fd_count(void) +static int fd_count(void) { DIR *dir; struct dirent *entry; diff --git a/tests/unit/test_relayd_backward_compat_group_by_session.c b/tests/unit/test_relayd_backward_compat_group_by_session.c index 769f8809e..d2c0573ae 100644 --- a/tests/unit/test_relayd_backward_compat_group_by_session.c +++ b/tests/unit/test_relayd_backward_compat_group_by_session.c @@ -18,12 +18,12 @@ #define NUM_TESTS_PER_TEST 1 struct test { - char *stream_path; - char *session_name; - char *hostname; - char *creation_time; - char *extra_path; - char *leftover; + const char *stream_path; + const char *session_name; + const char *hostname; + const char *creation_time; + const char *extra_path; + const char *leftover; bool is_valid; }; diff --git a/tests/unit/test_session.c b/tests/unit/test_session.c index 94e93cd80..2103a8f46 100644 --- a/tests/unit/test_session.c +++ b/tests/unit/test_session.c @@ -66,7 +66,7 @@ static char *get_random_string(void) /* * Return 0 if session name is found, else -1 */ -static int find_session_name(char *name) +static int find_session_name(const char *name) { struct ltt_session *iter; @@ -110,7 +110,7 @@ static void empty_session_list(void) /* * Test creation of 1 session */ -static int create_one_session(char *name) +static int create_one_session(const char *name) { int ret; enum lttng_error_code ret_code; diff --git a/tests/unit/test_utils_expand_path.c b/tests/unit/test_utils_expand_path.c index e6f5e0c8b..d7e7f2b05 100644 --- a/tests/unit/test_utils_expand_path.c +++ b/tests/unit/test_utils_expand_path.c @@ -25,19 +25,19 @@ int lttng_opt_verbose = 3; int lttng_opt_mi; struct valid_test_input { - char *input; - char *relative_part; - char *absolute_part; + const char *input; + const char *relative_part; + const char *absolute_part; }; struct tree_symlink { - char *orig; - char *dest; + const char *orig; + const char *dest; }; struct symlink_test_input { - char *input; - char *expected_result; + const char *input; + const char *expected_result; }; /* Valid test cases */ diff --git a/tests/unit/test_utils_parse_size_suffix.c b/tests/unit/test_utils_parse_size_suffix.c index 27dbf2c9a..4340a69ec 100644 --- a/tests/unit/test_utils_parse_size_suffix.c +++ b/tests/unit/test_utils_parse_size_suffix.c @@ -19,7 +19,7 @@ int lttng_opt_verbose = 3; int lttng_opt_mi; struct valid_test_input { - char *input; + const char *input; uint64_t expected_result; }; @@ -69,7 +69,7 @@ static struct valid_test_input valid_tests_inputs[] = { static const int num_valid_tests = sizeof(valid_tests_inputs) / sizeof(valid_tests_inputs[0]); /* Invalid test cases */ -static char *invalid_tests_inputs[] = { +static const char *invalid_tests_inputs[] = { "", " ", "-1", diff --git a/tests/unit/test_utils_parse_time_suffix.c b/tests/unit/test_utils_parse_time_suffix.c index 09708edd0..6a1c3280b 100644 --- a/tests/unit/test_utils_parse_time_suffix.c +++ b/tests/unit/test_utils_parse_time_suffix.c @@ -20,7 +20,7 @@ int lttng_opt_verbose = 3; int lttng_opt_mi; struct valid_test_input { - char *input; + const char *input; uint64_t expected_result; }; @@ -56,7 +56,7 @@ static struct valid_test_input valid_tests_inputs[] = { static const int num_valid_tests = sizeof(valid_tests_inputs) / sizeof(valid_tests_inputs[0]); /* Invalid test cases */ -static char *invalid_tests_inputs[] = { +static const char *invalid_tests_inputs[] = { "", " ", "-1", diff --git a/tests/utils/tap/tap.c b/tests/utils/tap/tap.c index 9c967f82d..7395f6fc5 100644 --- a/tests/utils/tap/tap.c +++ b/tests/utils/tap/tap.c @@ -40,7 +40,7 @@ static unsigned int test_count = 0; /* Number of tests that have been run */ static unsigned int e_tests = 0; /* Expected number of tests to run */ static unsigned int failures = 0; /* Number of tests that failed */ static char *todo_msg = NULL; -static char *todo_msg_fixed = "libtap malloc issue"; +static const char *todo_msg_fixed = "libtap malloc issue"; static int todo = 0; static int test_died = 0; @@ -68,8 +68,8 @@ static void _cleanup(void); * test_comment -- a comment to print afterwards, may be NULL */ unsigned int -_gen_result(int ok, const char *func, char *file, unsigned int line, - char *test_name, ...) +_gen_result(int ok, const char *func, const char *file, unsigned int line, + const char *test_name, ...) { va_list ap; char *local_test_name = NULL; @@ -270,7 +270,7 @@ plan_tests(unsigned int tests) } unsigned int -diag(char *fmt, ...) +diag(const char *fmt, ...) { va_list ap; @@ -294,7 +294,7 @@ _expected_tests(unsigned int tests) } int -skip(unsigned int n, char *fmt, ...) +skip(unsigned int n, const char *fmt, ...) { va_list ap; char *skip_msg = NULL; diff --git a/tests/utils/tap/tap.h b/tests/utils/tap/tap.h index 045499a98..ab9aad1aa 100644 --- a/tests/utils/tap/tap.h +++ b/tests/utils/tap/tap.h @@ -75,15 +75,15 @@ #define skip_end() } while(0); -unsigned int _gen_result(int, const char *, char *, unsigned int, char *, ...); +unsigned int _gen_result(int, const char *, const char *, unsigned int, const char *, ...); int plan_no_plan(void); int plan_skip_all(char *); int plan_tests(unsigned int); -unsigned int diag(char *, ...); +unsigned int diag(const char *, ...); -int skip(unsigned int, char *, ...); +int skip(unsigned int, const char *, ...); void todo_start(char *, ...); void todo_end(void); diff --git a/tests/utils/testapp/gen-syscall-events-callstack/Makefile.am b/tests/utils/testapp/gen-syscall-events-callstack/Makefile.am index 81800a50a..3b10c274d 100644 --- a/tests/utils/testapp/gen-syscall-events-callstack/Makefile.am +++ b/tests/utils/testapp/gen-syscall-events-callstack/Makefile.am @@ -10,7 +10,7 @@ AM_CFLAGS += -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer # available, we assume that the PIE is not enabled by default so we do not # need to disable it. if LINKER_HAVE_NO_PIE_OPTION -AM_CFLAGS += -no-pie +AM_LDFLAGS += -no-pie endif noinst_PROGRAMS = gen-syscall-events-callstack 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..29472572f 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,8 +56,7 @@ */ int main(int argc, char **argv) { - int fd, ret; - char buf[MAX_LEN]; + int ret; char *start_file; if (argc != 2) { @@ -47,23 +81,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("/proc/cpuinfo"); + if (ret == -1) { ret = -1; goto error; } - ret = syscall(SYS_close, fd); + ret = open_read_close("/proc/cmdline"); if (ret == -1) { - perror("close"); ret = -1; goto error; } diff --git a/tests/utils/testapp/gen-ust-tracef/gen-ust-tracef.c b/tests/utils/testapp/gen-ust-tracef/gen-ust-tracef.c index 09a2c327d..c5e721136 100644 --- a/tests/utils/testapp/gen-ust-tracef/gen-ust-tracef.c +++ b/tests/utils/testapp/gen-ust-tracef/gen-ust-tracef.c @@ -23,7 +23,7 @@ const char *str = "test string"; -void create_file(const char *path) +static void create_file(const char *path) { int ret; diff --git a/tests/utils/utils.sh b/tests/utils/utils.sh index d0f76bc6f..7da125654 100644 --- a/tests/utils/utils.sh +++ b/tests/utils/utils.sh @@ -392,7 +392,7 @@ function start_lttng_relayd_opt() DIR=$(readlink -f "$TESTDIR") - if [ -z $(pgrep $RELAYD_MATCH) ]; then + if [ -z $(pgrep -f $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 & @@ -441,7 +441,7 @@ function stop_lttng_relayd_opt() local retval=0 local pids= - pids=$(pgrep "$RELAYD_MATCH") + pids=$(pgrep -f "$RELAYD_MATCH") if [ -z "$pids" ]; then if [ "$withtap" -eq "1" ]; then pass "No relay daemon to kill" @@ -460,7 +460,7 @@ function stop_lttng_relayd_opt() else out=1 while [ -n "$out" ]; do - out=$(pgrep "$RELAYD_MATCH") + out=$(pgrep -f "$RELAYD_MATCH") if [ -n "$dtimeleft_s" ]; then if [ $dtimeleft_s -lt 0 ]; then out= @@ -538,7 +538,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 "$(pgrep -f "${SESSIOND_MATCH}")" ]; then # Have a load path ? if [ -n "$load_path" ]; then # shellcheck disable=SC2086 @@ -590,10 +590,10 @@ function stop_lttng_sessiond_opt() local retval=0 local runas_pids= - runas_pids=$(pgrep "$RUNAS_MATCH") + runas_pids=$(pgrep -f "$RUNAS_MATCH") local pids= - pids=$(pgrep "$SESSIOND_MATCH") + pids=$(pgrep -f "$SESSIOND_MATCH") if [ -n "$runas_pids" ]; then pids="$pids $runas_pids" @@ -617,7 +617,7 @@ function stop_lttng_sessiond_opt() else out=1 while [ -n "$out" ]; do - out=$(pgrep "${SESSIOND_MATCH}") + out=$(pgrep -f "${SESSIOND_MATCH}") if [ -n "$dtimeleft_s" ]; then if [ $dtimeleft_s -lt 0 ]; then out= @@ -629,7 +629,7 @@ function stop_lttng_sessiond_opt() done out=1 while [ -n "$out" ]; do - out=$(pgrep "$CONSUMERD_MATCH") + out=$(pgrep -f "$CONSUMERD_MATCH") if [ -n "$dtimeleft_s" ]; then if [ $dtimeleft_s -lt 0 ]; then out= @@ -683,7 +683,7 @@ function sigstop_lttng_sessiond_opt() return fi - PID_SESSIOND="$(pgrep "${SESSIOND_MATCH}") $(pgrep "$RUNAS_MATCH")" + PID_SESSIOND="$(pgrep -f "${SESSIOND_MATCH}") $(pgrep -f "$RUNAS_MATCH")" if [ "$withtap" -eq "1" ]; then diag "Sending SIGSTOP to lt-$SESSIOND_BIN and $SESSIOND_BIN pids: $(echo "$PID_SESSIOND" | tr '\n' ' ')" @@ -697,7 +697,7 @@ function sigstop_lttng_sessiond_opt() else out=1 while [ $out -ne 0 ]; do - pid="$(pgrep "$SESSIOND_MATCH")" + pid="$(pgrep -f "$SESSIOND_MATCH")" # Wait until state becomes stopped for session # daemon(s). @@ -745,7 +745,7 @@ function stop_lttng_consumerd_opt() local retval=0 - PID_CONSUMERD="$(pgrep "$CONSUMERD_MATCH")" + PID_CONSUMERD="$(pgrep -f "$CONSUMERD_MATCH")" if [ -z "$PID_CONSUMERD" ]; then if [ "$withtap" -eq "1" ]; then @@ -765,7 +765,7 @@ function stop_lttng_consumerd_opt() else out=1 while [ $out -ne 0 ]; do - pid="$(pgrep "$CONSUMERD_MATCH")" + pid="$(pgrep -f "$CONSUMERD_MATCH")" # If consumerds are still present check their status. # A zombie status qualifies the consumerd as *killed* @@ -812,7 +812,7 @@ function sigstop_lttng_consumerd_opt() local withtap=$1 local signal=SIGSTOP - PID_CONSUMERD="$(pgrep "$CONSUMERD_MATCH")" + PID_CONSUMERD="$(pgrep -f "$CONSUMERD_MATCH")" diag "Sending SIGSTOP to $CONSUMERD_BIN pids: $(echo "$PID_CONSUMERD" | tr '\n' ' ')" @@ -828,7 +828,7 @@ function sigstop_lttng_consumerd_opt() else out=1 while [ $out -ne 0 ]; do - pid="$(pgrep "$CONSUMERD_MATCH")" + pid="$(pgrep -f "$CONSUMERD_MATCH")" # Wait until state becomes stopped for all # consumers. -- 2.34.1