From: Simon Marchi Date: Wed, 6 Oct 2021 16:14:41 +0000 (-0400) Subject: bin: compile lttng-sessiond as C++ X-Git-Url: https://git.lttng.org/?p=lttng-tools.git;a=commitdiff_plain;h=7966af5763c4aaca39df9bbfa9277ff15715c720 bin: compile lttng-sessiond as C++ Same as commit 48a400056134 ("bin: compile lttng as C++"), but change lttng-sessiond to be a C++ program. In addition to the categories of changes already mentioned in that commit's message, here are some interesting changes: - Add an include in trigger.h, an exported header, to fix: CXX notification-thread.lo In file included from /home/simark/src/lttng-tools/src/bin/lttng-sessiond/notification-thread.cpp:9: /home/simark/src/lttng-tools/include/lttng/trigger/trigger.h:142:13: error: use of enum ‘lttng_error_code’ without previous declaration 142 | extern enum lttng_error_code lttng_register_trigger_with_name( | ^~~~~~~~~~~~~~~~ - We get this with clang: CXX lttng-conf.o In file included from /home/simark/src/lttng-tools/src/bin/lttng/conf.cpp:18: In file included from /home/simark/src/lttng-tools/src/common/common.h:14: In file included from /home/simark/src/lttng-tools/src/common/runas.h:17: In file included from /home/simark/src/lttng-tools/src/common/sessiond-comm/sessiond-comm.h:38: In file included from /home/simark/src/lttng-tools/src/common/unix.h:17: /home/simark/src/lttng-tools/src/common/payload-view.h:82:27: error: 'lttng_payload_view_from_payload' has C-linkage specified, but returns user-defined type 'struct lttng_payload_view' which is incompatible with C [-Werror,-Wreturn-type-c-linkage] struct lttng_payload_view lttng_payload_view_from_payload( ^ Turns out that because of the "const" field in lttng_payload_view, clang doesn't consider that type incompatible with C. I don't really want to remove the "const" for C code using that API, so conditionally remove it if we are compiling with clang in C++. - clang gives: CXX event.lo In file included from /home/simark/src/lttng-tools/src/bin/lttng-sessiond/event.cpp:19: /home/simark/src/lttng-tools/src/common/bytecode/bytecode.h:50:1: error: struct has size 0 in C, size 1 in C++ [-Werror,-Wextern-c-compat] struct literal_string { ^ It looks like that type isn't even used? Remove it. - it's not possible to initialize some union members, for example with lttcomm_consumer_msg, in consumer.cpp. Initialize it in a separate statement. - It's not possible to use the transparent union trick when calling urcu function, for example in thread_application_registration, in register.cpp. We need to instantiate a cds_wfcq_head_ptr_t object, assign the appropriate field, and pass that object to the function. - the ALIGNED_CONST_PTR trick does not work in C++: CXX consumer.lo In file included from /home/simark/src/lttng-tools/src/common/error.h:19, from /home/simark/src/lttng-tools/src/common/common.h:12, from /home/simark/src/lttng-tools/src/bin/lttng-sessiond/consumer.cpp:19: /home/simark/src/lttng-tools/src/bin/lttng-sessiond/consumer.cpp: In function ‘int consumer_send_relayd_socket(consumer_socket*, lttcomm_relayd_sock*, consumer_output*, lttng_stream_type, uint64_t, const char*, const char*, const char*, int, const uint64_t*, time_t, bool)’: /home/simark/src/lttng-tools/src/common/macros.h:116:58: error: expected primary-expression before ‘]’ token 116 | #define ALIGNED_CONST_PTR(value) (((const typeof(value) []) { value })) | ^ /home/simark/src/lttng-tools/src/bin/lttng-sessiond/consumer.cpp:1192:48: note: in expansion of macro ‘ALIGNED_CONST_PTR’ 1192 | ret = consumer_send_fds(consumer_sock, ALIGNED_CONST_PTR(rsock->sock.fd), 1); | ^~~~~~~~~~~~~~~~~ Replace uses with copying the data in a local variable (which is properly aligned), and pass the address to that variable to the function. - In consumer.h, an array field in a structure is defined using the max macro. It can't be replaced with std::max, since std::max isn't constexpr in C++11. Define a max_constexpr function locally and use it. - g++ 7 doesn't support non-trivial designated initializers, leading to errors like: CXX globals.lo /home/smarchi/src/lttng-tools/src/bin/lttng-sessiond/globals.cpp:44:1: sorry, unimplemented: non-trivial designated initializers not supported }; ^ Change consumer_data to have a constructor instead. Change initializations of some structures, such as lttcomm_lttng_msg, to initialize the fields separate from the variable declaration. This requires making these variable non-const which is not ideal. But once everything is C++, these types could get a fancy constructor, and then they can be made const again. - When compiling without UST support the stub versions of functions ust_app_rotate_session & co, in ust-app.h, are used. Some of them have the return type "enum lttng_error_code", but return 0, an invalid value, causing: CXX main.o In file included from /home/smarchi/src/lttng-tools/src/bin/lttng-sessiond/lttng-sessiond.h:22:0, from /home/smarchi/src/lttng-tools/src/bin/lttng-sessiond/main.cpp:45: /home/smarchi/src/lttng-tools/src/bin/lttng-sessiond/ust-app.h: In function ‘lttng_error_code ust_app_snapshot_record(ltt_ust_session*, const consumer_output*, int, uint64_t)’: /home/smarchi/src/lttng-tools/src/bin/lttng-sessiond/ust-app.h:575:9: error: invalid conversion from ‘int’ to ‘lttng_error_code’ [-fpermissive] return 0; ^ Change these functions to return LTTNG_ERR_UNK. These functions are not supposed to be called if UST support is not included. But even if they were: all their callers check that the return value is not LTTNG_OK. The value 0 would be considered an error, so will be LTTNG_ERR_UNK. Change-Id: I2cdd34459a54b1943087b43843ef20b35b7bf7d8 Signed-off-by: Simon Marchi Signed-off-by: Jérémie Galarneau --- diff --git a/include/lttng/action/action-internal.h b/include/lttng/action/action-internal.h index 71270a835..b55a6d59b 100644 --- a/include/lttng/action/action-internal.h +++ b/include/lttng/action/action-internal.h @@ -19,6 +19,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_rate_policy; struct mi_writer; struct mi_lttng_error_query_callbacks; @@ -128,4 +132,8 @@ enum lttng_error_code lttng_action_mi_serialize(const struct lttng_trigger *trig *error_query_callbacks, struct lttng_dynamic_array *action_path_indexes); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_ACTION_INTERNAL_H */ diff --git a/include/lttng/action/list-internal.h b/include/lttng/action/list-internal.h index 231755fd6..3d7a5ba60 100644 --- a/include/lttng/action/list-internal.h +++ b/include/lttng/action/list-internal.h @@ -12,6 +12,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_action; struct lttng_payload_view; struct mi_writer; @@ -39,4 +43,8 @@ enum lttng_error_code lttng_action_list_mi_serialize(const struct lttng_trigger *error_query_callbacks, struct lttng_dynamic_array *action_path_indexes); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_ACTION_LIST_INTERNAL_H */ diff --git a/include/lttng/condition/buffer-usage-internal.h b/include/lttng/condition/buffer-usage-internal.h index 1e6d5a509..6ea19e065 100644 --- a/include/lttng/condition/buffer-usage-internal.h +++ b/include/lttng/condition/buffer-usage-internal.h @@ -15,6 +15,10 @@ #include "common/buffer-view.h" #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_condition_buffer_usage { struct lttng_condition parent; struct { @@ -77,4 +81,8 @@ ssize_t lttng_evaluation_buffer_usage_high_create_from_payload( struct lttng_payload_view *view, struct lttng_evaluation **evaluation); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_CONDITION_BUFFER_USAGE_INTERNAL_H */ diff --git a/include/lttng/condition/event-rule-matches-internal.h b/include/lttng/condition/event-rule-matches-internal.h index ef39f4f04..4aac9f991 100644 --- a/include/lttng/condition/event-rule-matches-internal.h +++ b/include/lttng/condition/event-rule-matches-internal.h @@ -16,6 +16,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_capture_descriptor { struct lttng_event_expr *event_expression; struct lttng_bytecode *bytecode; @@ -86,4 +90,8 @@ const struct lttng_bytecode * lttng_condition_event_rule_matches_get_capture_bytecode_at_index( const struct lttng_condition *condition, unsigned int index); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_CONDITION_EVENT_RULE_MATCHES_INTERNAL_H */ diff --git a/include/lttng/condition/session-consumed-size-internal.h b/include/lttng/condition/session-consumed-size-internal.h index 9340a5f23..07c5953e1 100644 --- a/include/lttng/condition/session-consumed-size-internal.h +++ b/include/lttng/condition/session-consumed-size-internal.h @@ -14,6 +14,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_payload; struct lttng_payload_view; @@ -53,4 +57,8 @@ ssize_t lttng_evaluation_session_consumed_size_create_from_payload( struct lttng_payload_view *view, struct lttng_evaluation **evaluation); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_CONDITION_SESSION_CONSUMED_SIZE_INTERNAL_H */ diff --git a/include/lttng/condition/session-rotation-internal.h b/include/lttng/condition/session-rotation-internal.h index c723c6d6a..e5993e960 100644 --- a/include/lttng/condition/session-rotation-internal.h +++ b/include/lttng/condition/session-rotation-internal.h @@ -15,6 +15,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_condition_session_rotation { struct lttng_condition parent; char *session_name; @@ -61,4 +65,8 @@ ssize_t lttng_evaluation_session_rotation_completed_create_from_payload( struct lttng_payload_view *view, struct lttng_evaluation **evaluation); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_CONDITION_SESSION_ROTATION_INTERNAL_H */ diff --git a/include/lttng/error-query-internal.h b/include/lttng/error-query-internal.h index 07e2280d7..d3072f54b 100644 --- a/include/lttng/error-query-internal.h +++ b/include/lttng/error-query-internal.h @@ -15,6 +15,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct mi_writer; enum lttng_error_query_target_type { @@ -78,4 +82,8 @@ enum lttng_error_code lttng_error_query_results_mi_serialize( const struct lttng_error_query_results *results, struct mi_writer *writer); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_ERROR_QUERY_INTERNAL_H */ diff --git a/include/lttng/event-internal.h b/include/lttng/event-internal.h index 8d03fbdec..7370d0510 100644 --- a/include/lttng/event-internal.h +++ b/include/lttng/event-internal.h @@ -15,6 +15,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_userspace_probe_location; struct lttng_event_extended { @@ -35,4 +39,8 @@ struct lttng_event_extended { struct lttng_event *lttng_event_copy(const struct lttng_event *event); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_EVENT_INTERNAL_H */ diff --git a/include/lttng/health-internal.h b/include/lttng/health-internal.h index ceda0a8dd..875cc960b 100644 --- a/include/lttng/health-internal.h +++ b/include/lttng/health-internal.h @@ -17,6 +17,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* * These are the value added to the current state depending of the position in * the thread where is either waiting on a poll() or running in the code. @@ -116,4 +120,8 @@ int health_check_state(struct health_app *ha, int type); void health_register(struct health_app *ha, int type); void health_unregister(struct health_app *ha); +#ifdef __cplusplus +} +#endif + #endif /* HEALTH_INTERNAL_H */ diff --git a/include/lttng/location-internal.h b/include/lttng/location-internal.h index 33498c90d..9eafd471a 100644 --- a/include/lttng/location-internal.h +++ b/include/lttng/location-internal.h @@ -15,6 +15,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* * The public API assumes that trace archive locations are always * provided as "constant". This means that the user of liblttng-ctl never @@ -97,4 +101,8 @@ void lttng_trace_archive_location_get( void lttng_trace_archive_location_put( struct lttng_trace_archive_location *location); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_LOCATION_INTERNAL_H */ diff --git a/include/lttng/log-level-rule-internal.h b/include/lttng/log-level-rule-internal.h index c17e590b7..b02afafe0 100644 --- a/include/lttng/log-level-rule-internal.h +++ b/include/lttng/log-level-rule-internal.h @@ -18,6 +18,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct mi_writer; /* @@ -60,4 +64,8 @@ enum lttng_error_code lttng_log_level_rule_mi_serialize( const struct lttng_log_level_rule *rule, struct mi_writer *writer); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_LOG_LEVEL_RULE_INTERNAL_H */ diff --git a/include/lttng/notification/notification-internal.h b/include/lttng/notification/notification-internal.h index 7e601b0c7..1d337e07d 100644 --- a/include/lttng/notification/notification-internal.h +++ b/include/lttng/notification/notification-internal.h @@ -14,6 +14,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_payload; struct lttng_payload_view; @@ -40,4 +44,8 @@ ssize_t lttng_notification_create_from_payload( struct lttng_payload_view *view, struct lttng_notification **notification); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_NOTIFICATION_INTERNAL_H */ diff --git a/include/lttng/session-descriptor-internal.h b/include/lttng/session-descriptor-internal.h index f438d4812..97628bb88 100644 --- a/include/lttng/session-descriptor-internal.h +++ b/include/lttng/session-descriptor-internal.h @@ -15,6 +15,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* Note that these enums are used as part of the lttnctl protocol. */ enum lttng_session_descriptor_type { LTTNG_SESSION_DESCRIPTOR_TYPE_UNKNOWN = -1, @@ -81,4 +85,8 @@ int lttng_session_descriptor_assign( struct lttng_session_descriptor *dst_descriptor, const struct lttng_session_descriptor *src_descriptor); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_SESSION_DESCRIPTOR_INTERNAL_H */ diff --git a/include/lttng/trigger/trigger.h b/include/lttng/trigger/trigger.h index c4899f936..842203ace 100644 --- a/include/lttng/trigger/trigger.h +++ b/include/lttng/trigger/trigger.h @@ -11,6 +11,7 @@ #include #include #include +#include struct lttng_action; struct lttng_condition; diff --git a/include/lttng/userspace-probe-internal.h b/include/lttng/userspace-probe-internal.h index 5e960c106..fe96a4ea3 100644 --- a/include/lttng/userspace-probe-internal.h +++ b/include/lttng/userspace-probe-internal.h @@ -14,6 +14,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_payload; struct lttng_payload_view; struct lttng_dynamic_buffer; @@ -159,4 +163,8 @@ enum lttng_error_code lttng_userspace_probe_location_mi_serialize( const struct lttng_userspace_probe_location *location, struct mi_writer *writer); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_USERSPACE_PROBE_INTERNAL_H */ diff --git a/src/bin/lttng-sessiond/Makefile.am b/src/bin/lttng-sessiond/Makefile.am index 11fb85e0b..2de44a492 100644 --- a/src/bin/lttng-sessiond/Makefile.am +++ b/src/bin/lttng-sessiond/Makefile.am @@ -9,62 +9,62 @@ endif noinst_LTLIBRARIES = liblttng-sessiond-common.la -liblttng_sessiond_common_la_SOURCES = utils.c utils.h \ - trace-kernel.c trace-kernel.h \ - kernel.c kernel.h \ +liblttng_sessiond_common_la_SOURCES = utils.cpp utils.h \ + trace-kernel.cpp trace-kernel.h \ + kernel.cpp kernel.h \ ust-app.h ust-sigbus.h trace-ust.h notify-apps.h \ lttng-ust-ctl.h lttng-ust-abi.h lttng-ust-error.h \ ust-ctl-internal.h ust-abi-internal.h ust-error-internal.h \ ust-registry.h \ - condition-internal.c condition-internal.h \ - context.c context.h \ - channel.c channel.h \ - event.c event.h \ - consumer.c consumer.h \ - session.c session.h \ - modprobe.c modprobe.h kern-modules.h \ - fd-limit.c fd-limit.h \ - kernel-consumer.c kernel-consumer.h \ + condition-internal.cpp condition-internal.h \ + context.cpp context.h \ + channel.cpp channel.h \ + event.cpp event.h \ + consumer.cpp consumer.h \ + session.cpp session.h \ + modprobe.cpp modprobe.h kern-modules.h \ + fd-limit.cpp fd-limit.h \ + kernel-consumer.cpp kernel-consumer.h \ consumer.h \ health-sessiond.h \ - cmd.c cmd.h \ - buffer-registry.c buffer-registry.h \ - testpoint.h ht-cleanup.c ht-cleanup.h \ - snapshot.c snapshot.h \ - agent.c agent.h \ - save.h save.c \ - lttng-syscall.h lttng-syscall.c \ - notification-thread.h notification-thread.c \ + cmd.cpp cmd.h \ + buffer-registry.cpp buffer-registry.h \ + testpoint.h ht-cleanup.cpp ht-cleanup.h \ + snapshot.cpp snapshot.h \ + agent.cpp agent.h \ + save.h save.cpp \ + lttng-syscall.h lttng-syscall.cpp \ + notification-thread.h notification-thread.cpp \ notification-thread-internal.h \ - notification-thread-commands.h notification-thread-commands.c \ - notification-thread-events.h notification-thread-events.c \ - sessiond-config.h sessiond-config.c \ - rotate.h rotate.c \ - rotation-thread.h rotation-thread.c \ - timer.c timer.h \ - globals.c \ - thread-utils.c \ - process-utils.c \ - thread.c thread.h \ - health.c \ - client.c client.h \ - dispatch.c dispatch.h \ - register.c register.h \ - manage-apps.c manage-apps.h \ - manage-kernel.c manage-kernel.h \ - manage-consumer.c manage-consumer.h \ - clear.c clear.h \ - tracker.c tracker.h \ - event-notifier-error-accounting.c event-notifier-error-accounting.h \ - action-executor.c action-executor.h\ - trigger-error-query.c + notification-thread-commands.h notification-thread-commands.cpp \ + notification-thread-events.h notification-thread-events.cpp \ + sessiond-config.h sessiond-config.cpp \ + rotate.h rotate.cpp \ + rotation-thread.h rotation-thread.cpp \ + timer.cpp timer.h \ + globals.cpp \ + thread-utils.cpp \ + process-utils.cpp \ + thread.cpp thread.h \ + health.cpp \ + client.cpp client.h \ + dispatch.cpp dispatch.h \ + register.cpp register.h \ + manage-apps.cpp manage-apps.h \ + manage-kernel.cpp manage-kernel.h \ + manage-consumer.cpp manage-consumer.h \ + clear.cpp clear.h \ + tracker.cpp tracker.h \ + event-notifier-error-accounting.cpp event-notifier-error-accounting.h \ + action-executor.cpp action-executor.h\ + trigger-error-query.cpp if HAVE_LIBLTTNG_UST_CTL -liblttng_sessiond_common_la_SOURCES += trace-ust.c ust-registry.c ust-app.c \ - ust-consumer.c ust-consumer.h notify-apps.c \ - ust-metadata.c ust-clock.h agent-thread.c agent-thread.h \ - ust-field-utils.h ust-field-utils.c \ - ust-sigbus.c +liblttng_sessiond_common_la_SOURCES += trace-ust.cpp ust-registry.cpp ust-app.cpp \ + ust-consumer.cpp ust-consumer.h notify-apps.cpp \ + ust-metadata.cpp ust-clock.h agent-thread.cpp agent-thread.h \ + ust-field-utils.h ust-field-utils.cpp \ + ust-sigbus.cpp endif # link on liblttngctl for check if sessiond is already alive. @@ -88,7 +88,7 @@ endif bin_PROGRAMS = lttng-sessiond -lttng_sessiond_SOURCES = lttng-sessiond.h main.c +lttng_sessiond_SOURCES = lttng-sessiond.h main.cpp lttng_sessiond_LDFLAGS = -rdynamic diff --git a/src/bin/lttng-sessiond/action-executor.c b/src/bin/lttng-sessiond/action-executor.c deleted file mode 100644 index 94b819c46..000000000 --- a/src/bin/lttng-sessiond/action-executor.c +++ /dev/null @@ -1,1113 +0,0 @@ -/* - * Copyright (C) 2020 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "action-executor.h" -#include "cmd.h" -#include "health-sessiond.h" -#include "lttng-sessiond.h" -#include "notification-thread-internal.h" -#include "session.h" -#include "thread.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define THREAD_NAME "Action Executor" -#define MAX_QUEUED_WORK_COUNT 8192 - -/* - * A work item is composed of a dynamic array of sub-items which - * represent a flattened, and augmented, version of a trigger's actions. - * - * We cannot rely solely on the trigger's actions since each action can have an - * execution context we need to comply with. - * - * The notion of execution context is required since for some actions the - * associated object are referenced by name and not by id. This can lead to - * a number of ambiguities when executing an action work item. - * - * For example, let's take a simple trigger such as: - * - condition: ust event a - * - action: start session S - * - * At time T, session S exists. - * At T + 1, the event A is hit. - * At T + 2, the tracer event notification is received and the work item is - * queued. Here session S have an id of 1. - * At T + 3, the session S is destroyed and a new session S is created, with a - * resulting id of 200. - * At T +4, the work item is popped from the queue and begin execution and will - * start session S with an id of 200 instead of the session S id 1 that was - * present at the queuing phase. - * - * The context to be respected is the one when the work item is queued. If the - * execution context is not the same at the moment of execution, we skip the - * execution of that sub-item. - * - * It is the same policy in regards to the validity of the associated - * trigger object at the moment of execution, if the trigger is found to be - * unregistered, the execution is skipped. - */ - -struct action_work_item { - uint64_t id; - - /* - * The actions to be executed with their respective execution context. - * See struct `action_work_subitem`. - */ - struct lttng_dynamic_array subitems; - - /* Execution context data */ - struct lttng_trigger *trigger; - struct lttng_evaluation *evaluation; - struct notification_client_list *client_list; - LTTNG_OPTIONAL(struct lttng_credentials) object_creds; - struct cds_list_head list_node; -}; - -struct action_work_subitem { - struct lttng_action *action; - struct { - /* Used by actions targeting a session. */ - LTTNG_OPTIONAL(uint64_t) session_id; - } context; -}; - -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; -}; - -/* - * Only return non-zero on a fatal error that should shut down the action - * executor. - */ -typedef int (*action_executor_handler)(struct action_executor *executor, - const struct action_work_item *, - struct action_work_subitem *item); - -static int action_executor_notify_handler(struct action_executor *executor, - const struct action_work_item *, - struct action_work_subitem *); -static int action_executor_start_session_handler( - struct action_executor *executor, - const struct action_work_item *, - struct action_work_subitem *); -static int action_executor_stop_session_handler( - struct action_executor *executor, - const struct action_work_item *, - struct action_work_subitem *); -static int action_executor_rotate_session_handler( - struct action_executor *executor, - const struct action_work_item *, - struct action_work_subitem *); -static int action_executor_snapshot_session_handler( - struct action_executor *executor, - const struct action_work_item *, - struct action_work_subitem *); -static int action_executor_list_handler(struct action_executor *executor, - const struct action_work_item *, - struct action_work_subitem *); -static int action_executor_generic_handler(struct action_executor *executor, - const struct action_work_item *, - struct action_work_subitem *); - -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_LIST] = action_executor_list_handler, -}; - -/* Forward declaration */ -static int add_action_to_subitem_array(struct lttng_action *action, - struct lttng_dynamic_array *subitems); - -static int populate_subitem_array_from_trigger(struct lttng_trigger *trigger, - struct lttng_dynamic_array *subitems); - -static void action_work_subitem_destructor(void *element) -{ - struct action_work_subitem *subitem = element; - - lttng_action_put(subitem->action); -} - -static const char *get_action_name(const struct lttng_action *action) -{ - const enum lttng_action_type action_type = lttng_action_get_type(action); - - LTTNG_ASSERT(action_type != LTTNG_ACTION_TYPE_UNKNOWN); - - return lttng_action_type_string(action_type); -} - -/* Check if this trigger allowed to interect with a given session. */ -static bool is_trigger_allowed_for_session(const struct lttng_trigger *trigger, - struct ltt_session *session) -{ - bool is_allowed = false; - const struct lttng_credentials session_creds = { - .uid = LTTNG_OPTIONAL_INIT_VALUE(session->uid), - .gid = LTTNG_OPTIONAL_INIT_VALUE(session->gid), - }; - /* Can never be NULL. */ - const struct lttng_credentials *trigger_creds = - lttng_trigger_get_credentials(trigger); - - is_allowed = (lttng_credentials_is_equal_uid(trigger_creds, &session_creds)) || - (lttng_credentials_get_uid(trigger_creds) == 0); - if (!is_allowed) { - WARN("Trigger is not allowed to interact with session `%s`: session uid = %ld, session gid = %ld, trigger uid = %ld", - session->name, - (long int) session->uid, - (long int) session->gid, - (long int) lttng_credentials_get_uid(trigger_creds)); - } - - return is_allowed; -} - -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); - switch (trigger_status) { - case LTTNG_TRIGGER_STATUS_OK: - break; - case LTTNG_TRIGGER_STATUS_UNSET: - trigger_name = "(anonymous)"; - break; - default: - trigger_name = "(failed to get name)"; - break; - } - - 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; - - 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); - break; - default: - ERR("Fatal error encoutered while sending notification to client, client_id = %" PRIu64, - client->id); - ret = -1; - goto end; - } - - if (!update_communication) { - goto end; - } - - /* Safe to read client's id without locking as it is immutable. */ - 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, - struct action_work_subitem *item) -{ - return notification_client_list_send_evaluation(work_item->client_list, - work_item->trigger, - work_item->evaluation, - work_item->object_creds.is_set ? - &(work_item->object_creds.value) : - NULL, - client_handle_transmission_status, executor); -} - -static int action_executor_start_session_handler( - struct action_executor *executor, - const struct action_work_item *work_item, - struct action_work_subitem *item) -{ - int ret = 0; - const char *session_name; - enum lttng_action_status action_status; - struct ltt_session *session; - enum lttng_error_code cmd_ret; - struct lttng_action *action = item->action; - - 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; - } - - /* - * Validate if at the moment of the action was queued the session - * existed. If not skip the action altogether. - */ - if (!item->context.session_id.is_set) { - DBG("Session `%s` was not present at the moment the work item was enqueued for %s` action of trigger `%s`", - session_name, get_action_name(action), - get_trigger_name(work_item->trigger)); - lttng_action_increase_execution_failure_count(action); - ret = 0; - goto end; - } - - session_lock_list(); - session = session_find_by_name(session_name); - if (!session) { - 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)); - goto error_unlock_list; - } - - /* - * Check if the session id is the same as when the work item was - * enqueued. - */ - if (session->id != LTTNG_OPTIONAL_GET(item->context.session_id)) { - DBG("Session id for session `%s` (id: %" PRIu64 - " is not the same that was sampled (id: %" PRIu64 - " at the moment the work item was enqueued for %s` action of trigger `%s`", - session_name, session->id, - LTTNG_OPTIONAL_GET(item->context.session_id), - get_action_name(action), - get_trigger_name(work_item->trigger)); - ret = 0; - goto error_unlock_list; - } - - session_lock(session); - if (!is_trigger_allowed_for_session(work_item->trigger, session)) { - goto error_dispose_session; - } - - cmd_ret = cmd_start_trace(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)); - lttng_action_increase_execution_failure_count(action); - break; - } - -error_dispose_session: - session_unlock(session); - session_put(session); -error_unlock_list: - session_unlock_list(); -end: - return ret; -} - -static int action_executor_stop_session_handler( - struct action_executor *executor, - const struct action_work_item *work_item, - struct action_work_subitem *item) -{ - int ret = 0; - const char *session_name; - enum lttng_action_status action_status; - struct ltt_session *session; - enum lttng_error_code cmd_ret; - struct lttng_action *action = item->action; - - 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; - } - - /* - * Validate if, at the moment the action was queued, the target session - * existed. If not, skip the action altogether. - */ - if (!item->context.session_id.is_set) { - DBG("Session `%s` was not present at the moment the work item was enqueued for %s` action of trigger `%s`", - session_name, get_action_name(action), - get_trigger_name(work_item->trigger)); - lttng_action_increase_execution_failure_count(action); - ret = 0; - goto end; - } - - session_lock_list(); - session = session_find_by_name(session_name); - if (!session) { - 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)); - lttng_action_increase_execution_failure_count(action); - goto error_unlock_list; - } - - /* - * Check if the session id is the same as when the work item was - * enqueued - */ - if (session->id != LTTNG_OPTIONAL_GET(item->context.session_id)) { - DBG("Session id for session `%s` (id: %" PRIu64 - " is not the same that was sampled (id: %" PRIu64 - " at the moment the work item was enqueued for %s` action of trigger `%s`", - session_name, session->id, - LTTNG_OPTIONAL_GET(item->context.session_id), - get_action_name(action), - get_trigger_name(work_item->trigger)); - ret = 0; - goto error_unlock_list; - } - - session_lock(session); - if (!is_trigger_allowed_for_session(work_item->trigger, session)) { - goto error_dispose_session; - } - - cmd_ret = cmd_stop_trace(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)); - lttng_action_increase_execution_failure_count(action); - break; - } - -error_dispose_session: - session_unlock(session); - session_put(session); -error_unlock_list: - session_unlock_list(); -end: - return ret; -} - -static int action_executor_rotate_session_handler( - struct action_executor *executor, - const struct action_work_item *work_item, - struct action_work_subitem *item) -{ - int ret = 0; - const char *session_name; - enum lttng_action_status action_status; - struct ltt_session *session; - enum lttng_error_code cmd_ret; - struct lttng_action *action = item->action; - - 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; - } - - /* - * Validate if, at the moment the action was queued, the target session - * existed. If not, skip the action altogether. - */ - if (!item->context.session_id.is_set) { - DBG("Session `%s` was not present at the moment the work item was enqueued for %s` action of trigger `%s`", - session_name, get_action_name(action), - get_trigger_name(work_item->trigger)); - lttng_action_increase_execution_failure_count(action); - ret = 0; - goto end; - } - - session_lock_list(); - session = session_find_by_name(session_name); - if (!session) { - 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)); - lttng_action_increase_execution_failure_count(action); - goto error_unlock_list; - } - - /* - * Check if the session id is the same as when the work item was - * enqueued. - */ - if (session->id != LTTNG_OPTIONAL_GET(item->context.session_id)) { - DBG("Session id for session `%s` (id: %" PRIu64 - " is not the same that was sampled (id: %" PRIu64 - " at the moment the work item was enqueued for %s` action of trigger `%s`", - session_name, session->id, - LTTNG_OPTIONAL_GET(item->context.session_id), - get_action_name(action), - get_trigger_name(work_item->trigger)); - ret = 0; - goto error_unlock_list; - } - - session_lock(session); - if (!is_trigger_allowed_for_session(work_item->trigger, session)) { - goto error_dispose_session; - } - - cmd_ret = cmd_rotate_session(session, NULL, false, - LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); - 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)); - lttng_action_increase_execution_failure_count(action); - 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)); - lttng_action_increase_execution_failure_count(action); - break; - } - -error_dispose_session: - session_unlock(session); - session_put(session); -error_unlock_list: - session_unlock_list(); -end: - return ret; -} - -static int action_executor_snapshot_session_handler( - struct action_executor *executor, - const struct action_work_item *work_item, - struct action_work_subitem *item) -{ - 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; - enum lttng_error_code cmd_ret; - struct lttng_action *action = item->action; - - /* - * Validate if, at the moment the action was queued, the target session - * existed. If not, skip the action altogether. - */ - if (!item->context.session_id.is_set) { - DBG("Session was not present at the moment the work item was enqueued for %s` action of trigger `%s`", - get_action_name(action), - get_trigger_name(work_item->trigger)); - lttng_action_increase_execution_failure_count(action); - ret = 0; - goto end; - } - - 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( - 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) { - 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)); - lttng_action_increase_execution_failure_count(action); - goto error_unlock_list; - } - - /* - * Check if the session id is the same as when the work item was - * enqueued. - */ - if (session->id != LTTNG_OPTIONAL_GET(item->context.session_id)) { - DBG("Session id for session `%s` (id: %" PRIu64 - " is not the same that was sampled (id: %" PRIu64 - " at the moment the work item was enqueued for %s` action of trigger `%s`", - session_name, session->id, - LTTNG_OPTIONAL_GET(item->context.session_id), - get_action_name(action), - get_trigger_name(work_item->trigger)); - ret = 0; - goto error_unlock_list; - } - - session_lock(session); - if (!is_trigger_allowed_for_session(work_item->trigger, session)) { - goto error_dispose_session; - } - - cmd_ret = cmd_snapshot_record(session, snapshot_output, 0); - 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)); - lttng_action_increase_execution_failure_count(action); - break; - } - -error_dispose_session: - session_unlock(session); - session_put(session); -error_unlock_list: - session_unlock_list(); -end: - return ret; -} - -static int action_executor_list_handler(struct action_executor *executor, - const struct action_work_item *work_item, - struct action_work_subitem *item) -{ - ERR("Execution of a list action by the action executor should never occur"); - abort(); -} - -static int action_executor_generic_handler(struct action_executor *executor, - const struct action_work_item *work_item, - struct action_work_subitem *item) -{ - int ret; - struct lttng_action *action = item->action; - const enum lttng_action_type action_type = lttng_action_get_type(action); - - LTTNG_ASSERT(action_type != LTTNG_ACTION_TYPE_UNKNOWN); - - lttng_action_increase_execution_request_count(action); - if (!lttng_action_should_execute(action)) { - DBG("Policy prevented execution of action `%s` of trigger `%s` action work item %" PRIu64, - get_action_name(action), - get_trigger_name(work_item->trigger), - work_item->id); - ret = 0; - goto end; - } - - lttng_action_increase_execution_count(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); - ret = action_executors[action_type](executor, work_item, item); -end: - return ret; -} - -static int action_work_item_execute(struct action_executor *executor, - struct action_work_item *work_item) -{ - int ret; - size_t count, i; - - DBG("Starting execution of action work item %" PRIu64 " of trigger `%s`", - work_item->id, get_trigger_name(work_item->trigger)); - - count = lttng_dynamic_array_get_count(&work_item->subitems); - for (i = 0; i < count; i++) { - struct action_work_subitem *item; - - item = lttng_dynamic_array_get_element(&work_item->subitems, i); - ret = action_executor_generic_handler( - executor, work_item, item); - if (ret) { - goto end; - } - } -end: - 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); - lttng_evaluation_destroy(work_item->evaluation); - notification_client_list_put(work_item->client_list); - lttng_dynamic_array_reset(&work_item->subitems); - free(work_item); -} - -static void *action_executor_thread(void *_data) -{ - struct action_executor *executor = _data; - - LTTNG_ASSERT(executor); - - health_register(the_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 = 0; - struct action_work_item *work_item; - - health_code_update(); - if (executor->work.pending_count == 0) { - health_poll_entry(); - DBG("No work items enqueued, entering wait"); - pthread_cond_wait(&executor->work.cond, - &executor->work.lock); - DBG("Woke-up from wait"); - health_poll_exit(); - continue; - } - - /* Pop item from front of the list with work lock held. */ - work_item = cds_list_first_entry(&executor->work.list, - struct action_work_item, list_node); - cds_list_del(&work_item->list_node); - executor->work.pending_count--; - - /* - * Work can be performed without holding the work lock, - * allowing new items to be queued. - */ - pthread_mutex_unlock(&executor->work.lock); - - /* Execute item only if a trigger is registered. */ - lttng_trigger_lock(work_item->trigger); - if (!lttng_trigger_is_registered(work_item->trigger)) { - const char *trigger_name = NULL; - uid_t trigger_owner_uid; - enum lttng_trigger_status trigger_status; - - trigger_name = get_trigger_name(work_item->trigger); - - trigger_status = lttng_trigger_get_owner_uid( - work_item->trigger, &trigger_owner_uid); - LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); - - DBG("Work item skipped since the associated trigger is no longer registered: work item id = %" PRIu64 ", trigger name = '%s', trigger owner uid = %d", - work_item->id, trigger_name, - (int) trigger_owner_uid); - ret = 0; - goto skip_execute; - } - - ret = action_work_item_execute(executor, work_item); - - skip_execute: - lttng_trigger_unlock(work_item->trigger); - action_work_item_destroy(work_item); - if (ret) { - /* Fatal error. */ - break; - } - - health_code_update(); - pthread_mutex_lock(&executor->work.lock); - } - - if (executor->should_quit) { - pthread_mutex_unlock(&executor->work.lock); - } - DBG("Left work execution loop"); - - health_code_update(); - - rcu_thread_offline(); - rcu_unregister_thread(); - health_unregister(the_health_sessiond); - - return NULL; -} - -static bool shutdown_action_executor_thread(void *_data) -{ - struct action_executor *executor = _data; - - pthread_mutex_lock(&executor->work.lock); - executor->should_quit = true; - pthread_cond_signal(&executor->work.cond); - pthread_mutex_unlock(&executor->work.lock); - return true; -} - -static void clean_up_action_executor_thread(void *_data) -{ - struct action_executor *executor = _data; - - LTTNG_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_trigger( - struct action_executor *executor, - struct lttng_trigger *trigger, - struct lttng_evaluation *evaluation, - const struct lttng_credentials *object_creds, - struct notification_client_list *client_list) -{ - int ret; - 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; - - LTTNG_ASSERT(trigger); - - 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 (overflow): trigger name = `%s`, work item id = %" PRIu64, - 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: trigger name = '%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); - - LTTNG_ASSERT(reference_acquired); - } - - *work_item = (typeof(*work_item)){ - .id = work_item_id, - .trigger = trigger, - /* Ownership transferred to the work item. */ - .evaluation = evaluation, - .object_creds = { - .is_set = !!object_creds, - .value = object_creds ? *object_creds : - (typeof(work_item->object_creds.value)) {}, - }, - .client_list = client_list, - .list_node = CDS_LIST_HEAD_INIT(work_item->list_node), - }; - - evaluation = NULL; - - /* Build the array of action work subitems for the passed trigger. */ - lttng_dynamic_array_init(&work_item->subitems, - sizeof(struct action_work_subitem), - action_work_subitem_destructor); - - ret = populate_subitem_array_from_trigger( - trigger, &work_item->subitems); - if (ret) { - ERR("Failed to populate work item sub items on behalf of trigger: trigger name = `%s`", - get_trigger_name(trigger)); - executor_status = ACTION_EXECUTOR_STATUS_ERROR; - goto error_unlock; - } - - cds_list_add_tail(&work_item->list_node, &executor->work.list); - executor->work.pending_count++; - DBG("Enqueued action for trigger: trigger name = `%s`, work item id = %" PRIu64, - get_trigger_name(trigger), work_item_id); - signal = true; - -error_unlock: - if (signal) { - pthread_cond_signal(&executor->work.cond); - } - - pthread_mutex_unlock(&executor->work.lock); - lttng_evaluation_destroy(evaluation); - return executor_status; -} - -static int add_action_to_subitem_array(struct lttng_action *action, - struct lttng_dynamic_array *subitems) -{ - int ret = 0; - enum lttng_action_type type = lttng_action_get_type(action); - const char *session_name = NULL; - enum lttng_action_status status; - struct action_work_subitem subitem = { - .action = NULL, - .context = { - .session_id = LTTNG_OPTIONAL_INIT_UNSET, - }, - }; - - LTTNG_ASSERT(action); - LTTNG_ASSERT(subitems); - - if (type == LTTNG_ACTION_TYPE_LIST) { - unsigned int count, i; - - status = lttng_action_list_get_count(action, &count); - LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); - - for (i = 0; i < count; i++) { - struct lttng_action *inner_action = NULL; - - inner_action = lttng_action_list_borrow_mutable_at_index( - action, i); - LTTNG_ASSERT(inner_action); - ret = add_action_to_subitem_array( - inner_action, subitems); - if (ret) { - goto end; - } - } - - /* - * Go directly to the end since there is no need to add the - * list action by itself to the subitems array. - */ - goto end; - } - - /* Gather execution context. */ - switch (type) { - case LTTNG_ACTION_TYPE_NOTIFY: - break; - case LTTNG_ACTION_TYPE_START_SESSION: - status = lttng_action_start_session_get_session_name( - action, &session_name); - LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); - break; - case LTTNG_ACTION_TYPE_STOP_SESSION: - status = lttng_action_stop_session_get_session_name( - action, &session_name); - LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); - break; - case LTTNG_ACTION_TYPE_ROTATE_SESSION: - status = lttng_action_rotate_session_get_session_name( - action, &session_name); - LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); - break; - case LTTNG_ACTION_TYPE_SNAPSHOT_SESSION: - status = lttng_action_snapshot_session_get_session_name( - action, &session_name); - LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); - break; - case LTTNG_ACTION_TYPE_LIST: - case LTTNG_ACTION_TYPE_UNKNOWN: - /* Fallthrough */ - default: - abort(); - break; - } - - /* - * Fetch the session execution context info as needed. - * Note that we could decide to not add an action for which we know the - * execution will not happen (i.e no session exists for that name). For - * now we leave the decision to skip to the action executor for sake of - * simplicity and consistency. - */ - if (session_name != NULL) { - uint64_t session_id; - - /* - * Instantaneous sampling of the session id if present. - * - * This method is preferred over `sessiond_find_by_name` then - * fetching the session'd id since `sessiond_find_by_name` - * requires the session list lock to be taken. - * - * Taking the session list lock can lead to a deadlock - * between the action executor and the notification thread - * (caller of add_action_to_subitem_array). It is okay if the - * session state changes between the enqueuing time and the - * execution time. The execution context is validated at - * execution time. - */ - if (sample_session_id_by_name(session_name, &session_id)) { - LTTNG_OPTIONAL_SET(&subitem.context.session_id, - session_id); - } - } - - /* Get a reference to the action. */ - lttng_action_get(action); - subitem.action = action; - - ret = lttng_dynamic_array_add_element(subitems, &subitem); - if (ret) { - ERR("Failed to add work subitem to the subitem array"); - lttng_action_put(action); - ret = -1; - goto end; - } - -end: - return ret; -} - -static int populate_subitem_array_from_trigger(struct lttng_trigger *trigger, - struct lttng_dynamic_array *subitems) -{ - struct lttng_action *action; - - action = lttng_trigger_get_action(trigger); - LTTNG_ASSERT(action); - - return add_action_to_subitem_array(action, subitems); -} diff --git a/src/bin/lttng-sessiond/action-executor.cpp b/src/bin/lttng-sessiond/action-executor.cpp new file mode 100644 index 000000000..cc7834f27 --- /dev/null +++ b/src/bin/lttng-sessiond/action-executor.cpp @@ -0,0 +1,1112 @@ +/* + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "action-executor.h" +#include "cmd.h" +#include "health-sessiond.h" +#include "lttng-sessiond.h" +#include "notification-thread-internal.h" +#include "session.h" +#include "thread.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define THREAD_NAME "Action Executor" +#define MAX_QUEUED_WORK_COUNT 8192 + +/* + * A work item is composed of a dynamic array of sub-items which + * represent a flattened, and augmented, version of a trigger's actions. + * + * We cannot rely solely on the trigger's actions since each action can have an + * execution context we need to comply with. + * + * The notion of execution context is required since for some actions the + * associated object are referenced by name and not by id. This can lead to + * a number of ambiguities when executing an action work item. + * + * For example, let's take a simple trigger such as: + * - condition: ust event a + * - action: start session S + * + * At time T, session S exists. + * At T + 1, the event A is hit. + * At T + 2, the tracer event notification is received and the work item is + * queued. Here session S have an id of 1. + * At T + 3, the session S is destroyed and a new session S is created, with a + * resulting id of 200. + * At T +4, the work item is popped from the queue and begin execution and will + * start session S with an id of 200 instead of the session S id 1 that was + * present at the queuing phase. + * + * The context to be respected is the one when the work item is queued. If the + * execution context is not the same at the moment of execution, we skip the + * execution of that sub-item. + * + * It is the same policy in regards to the validity of the associated + * trigger object at the moment of execution, if the trigger is found to be + * unregistered, the execution is skipped. + */ + +struct action_work_item { + uint64_t id; + + /* + * The actions to be executed with their respective execution context. + * See struct `action_work_subitem`. + */ + struct lttng_dynamic_array subitems; + + /* Execution context data */ + struct lttng_trigger *trigger; + struct lttng_evaluation *evaluation; + struct notification_client_list *client_list; + LTTNG_OPTIONAL(struct lttng_credentials) object_creds; + struct cds_list_head list_node; +}; + +struct action_work_subitem { + struct lttng_action *action; + struct { + /* Used by actions targeting a session. */ + LTTNG_OPTIONAL(uint64_t) session_id; + } context; +}; + +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; +}; + +/* + * Only return non-zero on a fatal error that should shut down the action + * executor. + */ +typedef int (*action_executor_handler)(struct action_executor *executor, + const struct action_work_item *, + struct action_work_subitem *item); + +static int action_executor_notify_handler(struct action_executor *executor, + const struct action_work_item *, + struct action_work_subitem *); +static int action_executor_start_session_handler( + struct action_executor *executor, + const struct action_work_item *, + struct action_work_subitem *); +static int action_executor_stop_session_handler( + struct action_executor *executor, + const struct action_work_item *, + struct action_work_subitem *); +static int action_executor_rotate_session_handler( + struct action_executor *executor, + const struct action_work_item *, + struct action_work_subitem *); +static int action_executor_snapshot_session_handler( + struct action_executor *executor, + const struct action_work_item *, + struct action_work_subitem *); +static int action_executor_list_handler(struct action_executor *executor, + const struct action_work_item *, + struct action_work_subitem *); +static int action_executor_generic_handler(struct action_executor *executor, + const struct action_work_item *, + struct action_work_subitem *); + +static const action_executor_handler action_executors[] = { + action_executor_notify_handler, + action_executor_start_session_handler, + action_executor_stop_session_handler, + action_executor_rotate_session_handler, + action_executor_snapshot_session_handler, + action_executor_list_handler, +}; + +/* Forward declaration */ +static int add_action_to_subitem_array(struct lttng_action *action, + struct lttng_dynamic_array *subitems); + +static int populate_subitem_array_from_trigger(struct lttng_trigger *trigger, + struct lttng_dynamic_array *subitems); + +static void action_work_subitem_destructor(void *element) +{ + struct action_work_subitem *subitem = (action_work_subitem *) element; + + lttng_action_put(subitem->action); +} + +static const char *get_action_name(const struct lttng_action *action) +{ + const enum lttng_action_type action_type = lttng_action_get_type(action); + + LTTNG_ASSERT(action_type != LTTNG_ACTION_TYPE_UNKNOWN); + + return lttng_action_type_string(action_type); +} + +/* Check if this trigger allowed to interect with a given session. */ +static bool is_trigger_allowed_for_session(const struct lttng_trigger *trigger, + struct ltt_session *session) +{ + bool is_allowed = false; + const struct lttng_credentials session_creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(session->uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(session->gid), + }; + /* Can never be NULL. */ + const struct lttng_credentials *trigger_creds = + lttng_trigger_get_credentials(trigger); + + is_allowed = (lttng_credentials_is_equal_uid(trigger_creds, &session_creds)) || + (lttng_credentials_get_uid(trigger_creds) == 0); + if (!is_allowed) { + WARN("Trigger is not allowed to interact with session `%s`: session uid = %ld, session gid = %ld, trigger uid = %ld", + session->name, + (long int) session->uid, + (long int) session->gid, + (long int) lttng_credentials_get_uid(trigger_creds)); + } + + return is_allowed; +} + +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); + switch (trigger_status) { + case LTTNG_TRIGGER_STATUS_OK: + break; + case LTTNG_TRIGGER_STATUS_UNSET: + trigger_name = "(anonymous)"; + break; + default: + trigger_name = "(failed to get name)"; + break; + } + + 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 = (action_executor *) user_data; + bool update_communication = true; + + 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); + break; + default: + ERR("Fatal error encoutered while sending notification to client, client_id = %" PRIu64, + client->id); + ret = -1; + goto end; + } + + if (!update_communication) { + goto end; + } + + /* Safe to read client's id without locking as it is immutable. */ + 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, + struct action_work_subitem *item) +{ + return notification_client_list_send_evaluation(work_item->client_list, + work_item->trigger, + work_item->evaluation, + work_item->object_creds.is_set ? + &(work_item->object_creds.value) : + NULL, + client_handle_transmission_status, executor); +} + +static int action_executor_start_session_handler( + struct action_executor *executor, + const struct action_work_item *work_item, + struct action_work_subitem *item) +{ + int ret = 0; + const char *session_name; + enum lttng_action_status action_status; + struct ltt_session *session; + enum lttng_error_code cmd_ret; + struct lttng_action *action = item->action; + + 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; + } + + /* + * Validate if at the moment of the action was queued the session + * existed. If not skip the action altogether. + */ + if (!item->context.session_id.is_set) { + DBG("Session `%s` was not present at the moment the work item was enqueued for %s` action of trigger `%s`", + session_name, get_action_name(action), + get_trigger_name(work_item->trigger)); + lttng_action_increase_execution_failure_count(action); + ret = 0; + goto end; + } + + session_lock_list(); + session = session_find_by_name(session_name); + if (!session) { + 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)); + goto error_unlock_list; + } + + /* + * Check if the session id is the same as when the work item was + * enqueued. + */ + if (session->id != LTTNG_OPTIONAL_GET(item->context.session_id)) { + DBG("Session id for session `%s` (id: %" PRIu64 + " is not the same that was sampled (id: %" PRIu64 + " at the moment the work item was enqueued for %s` action of trigger `%s`", + session_name, session->id, + LTTNG_OPTIONAL_GET(item->context.session_id), + get_action_name(action), + get_trigger_name(work_item->trigger)); + ret = 0; + goto error_unlock_list; + } + + session_lock(session); + if (!is_trigger_allowed_for_session(work_item->trigger, session)) { + goto error_dispose_session; + } + + cmd_ret = (lttng_error_code) cmd_start_trace(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)); + lttng_action_increase_execution_failure_count(action); + break; + } + +error_dispose_session: + session_unlock(session); + session_put(session); +error_unlock_list: + session_unlock_list(); +end: + return ret; +} + +static int action_executor_stop_session_handler( + struct action_executor *executor, + const struct action_work_item *work_item, + struct action_work_subitem *item) +{ + int ret = 0; + const char *session_name; + enum lttng_action_status action_status; + struct ltt_session *session; + enum lttng_error_code cmd_ret; + struct lttng_action *action = item->action; + + 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; + } + + /* + * Validate if, at the moment the action was queued, the target session + * existed. If not, skip the action altogether. + */ + if (!item->context.session_id.is_set) { + DBG("Session `%s` was not present at the moment the work item was enqueued for %s` action of trigger `%s`", + session_name, get_action_name(action), + get_trigger_name(work_item->trigger)); + lttng_action_increase_execution_failure_count(action); + ret = 0; + goto end; + } + + session_lock_list(); + session = session_find_by_name(session_name); + if (!session) { + 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)); + lttng_action_increase_execution_failure_count(action); + goto error_unlock_list; + } + + /* + * Check if the session id is the same as when the work item was + * enqueued + */ + if (session->id != LTTNG_OPTIONAL_GET(item->context.session_id)) { + DBG("Session id for session `%s` (id: %" PRIu64 + " is not the same that was sampled (id: %" PRIu64 + " at the moment the work item was enqueued for %s` action of trigger `%s`", + session_name, session->id, + LTTNG_OPTIONAL_GET(item->context.session_id), + get_action_name(action), + get_trigger_name(work_item->trigger)); + ret = 0; + goto error_unlock_list; + } + + session_lock(session); + if (!is_trigger_allowed_for_session(work_item->trigger, session)) { + goto error_dispose_session; + } + + cmd_ret = (lttng_error_code) cmd_stop_trace(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)); + lttng_action_increase_execution_failure_count(action); + break; + } + +error_dispose_session: + session_unlock(session); + session_put(session); +error_unlock_list: + session_unlock_list(); +end: + return ret; +} + +static int action_executor_rotate_session_handler( + struct action_executor *executor, + const struct action_work_item *work_item, + struct action_work_subitem *item) +{ + int ret = 0; + const char *session_name; + enum lttng_action_status action_status; + struct ltt_session *session; + enum lttng_error_code cmd_ret; + struct lttng_action *action = item->action; + + 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; + } + + /* + * Validate if, at the moment the action was queued, the target session + * existed. If not, skip the action altogether. + */ + if (!item->context.session_id.is_set) { + DBG("Session `%s` was not present at the moment the work item was enqueued for %s` action of trigger `%s`", + session_name, get_action_name(action), + get_trigger_name(work_item->trigger)); + lttng_action_increase_execution_failure_count(action); + ret = 0; + goto end; + } + + session_lock_list(); + session = session_find_by_name(session_name); + if (!session) { + 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)); + lttng_action_increase_execution_failure_count(action); + goto error_unlock_list; + } + + /* + * Check if the session id is the same as when the work item was + * enqueued. + */ + if (session->id != LTTNG_OPTIONAL_GET(item->context.session_id)) { + DBG("Session id for session `%s` (id: %" PRIu64 + " is not the same that was sampled (id: %" PRIu64 + " at the moment the work item was enqueued for %s` action of trigger `%s`", + session_name, session->id, + LTTNG_OPTIONAL_GET(item->context.session_id), + get_action_name(action), + get_trigger_name(work_item->trigger)); + ret = 0; + goto error_unlock_list; + } + + session_lock(session); + if (!is_trigger_allowed_for_session(work_item->trigger, session)) { + goto error_dispose_session; + } + + cmd_ret = (lttng_error_code) cmd_rotate_session(session, NULL, false, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); + 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)); + lttng_action_increase_execution_failure_count(action); + 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)); + lttng_action_increase_execution_failure_count(action); + break; + } + +error_dispose_session: + session_unlock(session); + session_put(session); +error_unlock_list: + session_unlock_list(); +end: + return ret; +} + +static int action_executor_snapshot_session_handler( + struct action_executor *executor, + const struct action_work_item *work_item, + struct action_work_subitem *item) +{ + int ret = 0; + const char *session_name; + enum lttng_action_status action_status; + struct ltt_session *session; + lttng_snapshot_output default_snapshot_output; + const struct lttng_snapshot_output *snapshot_output = + &default_snapshot_output; + enum lttng_error_code cmd_ret; + struct lttng_action *action = item->action; + + default_snapshot_output.max_size = UINT64_MAX; + + /* + * Validate if, at the moment the action was queued, the target session + * existed. If not, skip the action altogether. + */ + if (!item->context.session_id.is_set) { + DBG("Session was not present at the moment the work item was enqueued for %s` action of trigger `%s`", + get_action_name(action), + get_trigger_name(work_item->trigger)); + lttng_action_increase_execution_failure_count(action); + ret = 0; + goto end; + } + + 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( + 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) { + 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)); + lttng_action_increase_execution_failure_count(action); + goto error_unlock_list; + } + + /* + * Check if the session id is the same as when the work item was + * enqueued. + */ + if (session->id != LTTNG_OPTIONAL_GET(item->context.session_id)) { + DBG("Session id for session `%s` (id: %" PRIu64 + " is not the same that was sampled (id: %" PRIu64 + " at the moment the work item was enqueued for %s` action of trigger `%s`", + session_name, session->id, + LTTNG_OPTIONAL_GET(item->context.session_id), + get_action_name(action), + get_trigger_name(work_item->trigger)); + ret = 0; + goto error_unlock_list; + } + + session_lock(session); + if (!is_trigger_allowed_for_session(work_item->trigger, session)) { + goto error_dispose_session; + } + + cmd_ret = (lttng_error_code) cmd_snapshot_record(session, snapshot_output, 0); + 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)); + lttng_action_increase_execution_failure_count(action); + break; + } + +error_dispose_session: + session_unlock(session); + session_put(session); +error_unlock_list: + session_unlock_list(); +end: + return ret; +} + +static int action_executor_list_handler(struct action_executor *executor, + const struct action_work_item *work_item, + struct action_work_subitem *item) +{ + ERR("Execution of a list action by the action executor should never occur"); + abort(); +} + +static int action_executor_generic_handler(struct action_executor *executor, + const struct action_work_item *work_item, + struct action_work_subitem *item) +{ + int ret; + struct lttng_action *action = item->action; + const enum lttng_action_type action_type = lttng_action_get_type(action); + + LTTNG_ASSERT(action_type != LTTNG_ACTION_TYPE_UNKNOWN); + + lttng_action_increase_execution_request_count(action); + if (!lttng_action_should_execute(action)) { + DBG("Policy prevented execution of action `%s` of trigger `%s` action work item %" PRIu64, + get_action_name(action), + get_trigger_name(work_item->trigger), + work_item->id); + ret = 0; + goto end; + } + + lttng_action_increase_execution_count(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); + ret = action_executors[action_type](executor, work_item, item); +end: + return ret; +} + +static int action_work_item_execute(struct action_executor *executor, + struct action_work_item *work_item) +{ + int ret; + size_t count, i; + + DBG("Starting execution of action work item %" PRIu64 " of trigger `%s`", + work_item->id, get_trigger_name(work_item->trigger)); + + count = lttng_dynamic_array_get_count(&work_item->subitems); + for (i = 0; i < count; i++) { + struct action_work_subitem *item; + + item = (action_work_subitem *) lttng_dynamic_array_get_element(&work_item->subitems, i); + ret = action_executor_generic_handler( + executor, work_item, item); + if (ret) { + goto end; + } + } +end: + 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); + lttng_evaluation_destroy(work_item->evaluation); + notification_client_list_put(work_item->client_list); + lttng_dynamic_array_reset(&work_item->subitems); + free(work_item); +} + +static void *action_executor_thread(void *_data) +{ + struct action_executor *executor = (action_executor *) _data; + + LTTNG_ASSERT(executor); + + health_register(the_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 = 0; + struct action_work_item *work_item; + + health_code_update(); + if (executor->work.pending_count == 0) { + health_poll_entry(); + DBG("No work items enqueued, entering wait"); + pthread_cond_wait(&executor->work.cond, + &executor->work.lock); + DBG("Woke-up from wait"); + health_poll_exit(); + continue; + } + + /* Pop item from front of the list with work lock held. */ + work_item = cds_list_first_entry(&executor->work.list, + struct action_work_item, list_node); + cds_list_del(&work_item->list_node); + executor->work.pending_count--; + + /* + * Work can be performed without holding the work lock, + * allowing new items to be queued. + */ + pthread_mutex_unlock(&executor->work.lock); + + /* Execute item only if a trigger is registered. */ + lttng_trigger_lock(work_item->trigger); + if (!lttng_trigger_is_registered(work_item->trigger)) { + const char *trigger_name = NULL; + uid_t trigger_owner_uid; + enum lttng_trigger_status trigger_status; + + trigger_name = get_trigger_name(work_item->trigger); + + trigger_status = lttng_trigger_get_owner_uid( + work_item->trigger, &trigger_owner_uid); + LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + DBG("Work item skipped since the associated trigger is no longer registered: work item id = %" PRIu64 ", trigger name = '%s', trigger owner uid = %d", + work_item->id, trigger_name, + (int) trigger_owner_uid); + ret = 0; + goto skip_execute; + } + + ret = action_work_item_execute(executor, work_item); + + skip_execute: + lttng_trigger_unlock(work_item->trigger); + action_work_item_destroy(work_item); + if (ret) { + /* Fatal error. */ + break; + } + + health_code_update(); + pthread_mutex_lock(&executor->work.lock); + } + + if (executor->should_quit) { + pthread_mutex_unlock(&executor->work.lock); + } + DBG("Left work execution loop"); + + health_code_update(); + + rcu_thread_offline(); + rcu_unregister_thread(); + health_unregister(the_health_sessiond); + + return NULL; +} + +static bool shutdown_action_executor_thread(void *_data) +{ + struct action_executor *executor = (action_executor *) _data; + + pthread_mutex_lock(&executor->work.lock); + executor->should_quit = true; + pthread_cond_signal(&executor->work.cond); + pthread_mutex_unlock(&executor->work.lock); + return true; +} + +static void clean_up_action_executor_thread(void *_data) +{ + struct action_executor *executor = (action_executor *) _data; + + LTTNG_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 = (action_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_trigger( + struct action_executor *executor, + struct lttng_trigger *trigger, + struct lttng_evaluation *evaluation, + const struct lttng_credentials *object_creds, + struct notification_client_list *client_list) +{ + int ret; + 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; + + LTTNG_ASSERT(trigger); + + 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 (overflow): trigger name = `%s`, work item id = %" PRIu64, + get_trigger_name(trigger), work_item_id); + executor_status = ACTION_EXECUTOR_STATUS_OVERFLOW; + goto error_unlock; + } + + work_item = (action_work_item *) zmalloc(sizeof(*work_item)); + if (!work_item) { + PERROR("Failed to allocate action executor work item: trigger name = '%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); + + LTTNG_ASSERT(reference_acquired); + } + + work_item->id = work_item_id; + work_item->trigger = trigger; + + /* Ownership transferred to the work item. */ + work_item->evaluation = evaluation; + evaluation = NULL; + + work_item->client_list = client_list; + work_item->object_creds.is_set = !!object_creds; + if (object_creds) { + work_item->object_creds.value = *object_creds; + } + + CDS_INIT_LIST_HEAD(&work_item->list_node); + + /* Build the array of action work subitems for the passed trigger. */ + lttng_dynamic_array_init(&work_item->subitems, + sizeof(struct action_work_subitem), + action_work_subitem_destructor); + + ret = populate_subitem_array_from_trigger( + trigger, &work_item->subitems); + if (ret) { + ERR("Failed to populate work item sub items on behalf of trigger: trigger name = `%s`", + get_trigger_name(trigger)); + executor_status = ACTION_EXECUTOR_STATUS_ERROR; + goto error_unlock; + } + + cds_list_add_tail(&work_item->list_node, &executor->work.list); + executor->work.pending_count++; + DBG("Enqueued action for trigger: trigger name = `%s`, work item id = %" PRIu64, + get_trigger_name(trigger), work_item_id); + signal = true; + +error_unlock: + if (signal) { + pthread_cond_signal(&executor->work.cond); + } + + pthread_mutex_unlock(&executor->work.lock); + lttng_evaluation_destroy(evaluation); + return executor_status; +} + +static int add_action_to_subitem_array(struct lttng_action *action, + struct lttng_dynamic_array *subitems) +{ + int ret = 0; + enum lttng_action_type type = lttng_action_get_type(action); + const char *session_name = NULL; + enum lttng_action_status status; + struct action_work_subitem subitem = { + .action = NULL, + .context = { + .session_id = LTTNG_OPTIONAL_INIT_UNSET, + }, + }; + + LTTNG_ASSERT(action); + LTTNG_ASSERT(subitems); + + if (type == LTTNG_ACTION_TYPE_LIST) { + unsigned int count, i; + + status = lttng_action_list_get_count(action, &count); + LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); + + for (i = 0; i < count; i++) { + struct lttng_action *inner_action = NULL; + + inner_action = lttng_action_list_borrow_mutable_at_index( + action, i); + LTTNG_ASSERT(inner_action); + ret = add_action_to_subitem_array( + inner_action, subitems); + if (ret) { + goto end; + } + } + + /* + * Go directly to the end since there is no need to add the + * list action by itself to the subitems array. + */ + goto end; + } + + /* Gather execution context. */ + switch (type) { + case LTTNG_ACTION_TYPE_NOTIFY: + break; + case LTTNG_ACTION_TYPE_START_SESSION: + status = lttng_action_start_session_get_session_name( + action, &session_name); + LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); + break; + case LTTNG_ACTION_TYPE_STOP_SESSION: + status = lttng_action_stop_session_get_session_name( + action, &session_name); + LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); + break; + case LTTNG_ACTION_TYPE_ROTATE_SESSION: + status = lttng_action_rotate_session_get_session_name( + action, &session_name); + LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); + break; + case LTTNG_ACTION_TYPE_SNAPSHOT_SESSION: + status = lttng_action_snapshot_session_get_session_name( + action, &session_name); + LTTNG_ASSERT(status == LTTNG_ACTION_STATUS_OK); + break; + case LTTNG_ACTION_TYPE_LIST: + case LTTNG_ACTION_TYPE_UNKNOWN: + /* Fallthrough */ + default: + abort(); + break; + } + + /* + * Fetch the session execution context info as needed. + * Note that we could decide to not add an action for which we know the + * execution will not happen (i.e no session exists for that name). For + * now we leave the decision to skip to the action executor for sake of + * simplicity and consistency. + */ + if (session_name != NULL) { + uint64_t session_id; + + /* + * Instantaneous sampling of the session id if present. + * + * This method is preferred over `sessiond_find_by_name` then + * fetching the session'd id since `sessiond_find_by_name` + * requires the session list lock to be taken. + * + * Taking the session list lock can lead to a deadlock + * between the action executor and the notification thread + * (caller of add_action_to_subitem_array). It is okay if the + * session state changes between the enqueuing time and the + * execution time. The execution context is validated at + * execution time. + */ + if (sample_session_id_by_name(session_name, &session_id)) { + LTTNG_OPTIONAL_SET(&subitem.context.session_id, + session_id); + } + } + + /* Get a reference to the action. */ + lttng_action_get(action); + subitem.action = action; + + ret = lttng_dynamic_array_add_element(subitems, &subitem); + if (ret) { + ERR("Failed to add work subitem to the subitem array"); + lttng_action_put(action); + ret = -1; + goto end; + } + +end: + return ret; +} + +static int populate_subitem_array_from_trigger(struct lttng_trigger *trigger, + struct lttng_dynamic_array *subitems) +{ + struct lttng_action *action; + + action = lttng_trigger_get_action(trigger); + LTTNG_ASSERT(action); + + return add_action_to_subitem_array(action, subitems); +} diff --git a/src/bin/lttng-sessiond/agent-thread.c b/src/bin/lttng-sessiond/agent-thread.c deleted file mode 100644 index f2ee4c041..000000000 --- a/src/bin/lttng-sessiond/agent-thread.c +++ /dev/null @@ -1,598 +0,0 @@ -/* - * Copyright (C) 2013 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE - -#include -#include -#include -#include - -#include - -#include "fd-limit.h" -#include "agent-thread.h" -#include "agent.h" -#include "lttng-sessiond.h" -#include "session.h" -#include "utils.h" -#include "thread.h" - -struct thread_notifiers { - struct lttng_pipe *quit_pipe; - sem_t ready; -}; - -struct agent_app_id { - pid_t pid; - enum lttng_domain_type domain; -}; - -struct agent_protocol_version { - unsigned int major, minor; -}; - -static int agent_tracing_enabled = -1; - -/* - * Note that there is not port here. It's set after this URI is parsed so we - * can let the user define a custom one. However, localhost is ALWAYS the - * default listening address. - */ -static const char *default_reg_uri = - "tcp://" DEFAULT_NETWORK_VIEWER_BIND_ADDRESS; - -/* - * Update agent application using the given socket. This is done just after - * registration was successful. - * - * This will acquire the various sessions' lock; none must be held by the - * caller. - * The caller must hold the session list lock. - */ -static void update_agent_app(const struct agent_app *app) -{ - struct ltt_session *session, *stmp; - struct ltt_session_list *list; - struct agent *trigger_agent; - struct lttng_ht_iter iter; - - list = session_get_list(); - LTTNG_ASSERT(list); - - cds_list_for_each_entry_safe(session, stmp, &list->head, list) { - if (!session_get(session)) { - continue; - } - - session_lock(session); - if (session->ust_session) { - const struct agent *agt; - - rcu_read_lock(); - agt = trace_ust_find_agent(session->ust_session, app->domain); - if (agt) { - agent_update(agt, app); - } - rcu_read_unlock(); - } - session_unlock(session); - session_put(session); - } - - rcu_read_lock(); - /* - * We are protected against the addition of new events by the session - * list lock being held. - */ - cds_lfht_for_each_entry(the_trigger_agents_ht_by_domain->ht, - &iter.iter, trigger_agent, node.node) { - agent_update(trigger_agent, app); - } - rcu_read_unlock(); -} - -/* - * Create and init socket from uri. - */ -static struct lttcomm_sock *init_tcp_socket(void) -{ - int ret; - struct lttng_uri *uri = NULL; - struct lttcomm_sock *sock = NULL; - unsigned int port; - bool bind_succeeded = false; - - /* - * This should never fail since the URI is hardcoded and the port is set - * before this thread is launched. - */ - ret = uri_parse(default_reg_uri, &uri); - LTTNG_ASSERT(ret); - LTTNG_ASSERT(the_config.agent_tcp_port.begin > 0); - uri->port = the_config.agent_tcp_port.begin; - - sock = lttcomm_alloc_sock_from_uri(uri); - uri_free(uri); - if (sock == NULL) { - ERR("agent allocating TCP socket"); - goto error; - } - - ret = lttcomm_create_sock(sock); - if (ret < 0) { - goto error; - } - - for (port = the_config.agent_tcp_port.begin; - port <= the_config.agent_tcp_port.end; port++) { - ret = lttcomm_sock_set_port(sock, (uint16_t) port); - if (ret) { - ERR("Failed to set port %u on socket", - port); - goto error; - } - DBG3("Trying to bind on port %u", port); - ret = sock->ops->bind(sock); - if (!ret) { - bind_succeeded = true; - break; - } - - if (errno == EADDRINUSE) { - DBG("Failed to bind to port %u since it is already in use", - port); - } else { - PERROR("Failed to bind to port %u", port); - goto error; - } - } - - if (!bind_succeeded) { - if (the_config.agent_tcp_port.begin == - the_config.agent_tcp_port.end) { - WARN("Another process is already using the agent port %i. " - "Agent support will be deactivated.", - the_config.agent_tcp_port.begin); - goto error; - } else { - WARN("All ports in the range [%i, %i] are already in use. " - "Agent support will be deactivated.", - the_config.agent_tcp_port.begin, - the_config.agent_tcp_port.end); - goto error; - } - } - - ret = sock->ops->listen(sock, -1); - if (ret < 0) { - goto error; - } - - DBG("Listening on TCP port %u and socket %d", - port, sock->fd); - - return sock; - -error: - if (sock) { - lttcomm_destroy_sock(sock); - } - return NULL; -} - -/* - * Close and destroy the given TCP socket. - */ -static void destroy_tcp_socket(struct lttcomm_sock *sock) -{ - int ret; - uint16_t port; - - LTTNG_ASSERT(sock); - - ret = lttcomm_sock_get_port(sock, &port); - if (ret) { - ERR("Failed to get port of agent TCP socket"); - port = 0; - } - - DBG3("Destroy TCP socket on port %" PRIu16, - port); - - /* This will return gracefully if fd is invalid. */ - sock->ops->close(sock); - lttcomm_destroy_sock(sock); -} - -static const char *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 "unknown"; - } -} - -static bool is_agent_protocol_version_supported( - const struct agent_protocol_version *version) -{ - const bool is_supported = version->major == AGENT_MAJOR_VERSION && - version->minor == AGENT_MINOR_VERSION; - - if (!is_supported) { - WARN("Refusing agent connection: unsupported protocol version %ui.%ui, expected %i.%i", - version->major, version->minor, - AGENT_MAJOR_VERSION, AGENT_MINOR_VERSION); - } - - return is_supported; -} - -/* - * Handle a new agent connection on the registration socket. - * - * Returns 0 on success, or else a negative errno value. - * On success, the resulting socket is returned through `agent_app_socket` - * and the application's reported id is updated through `agent_app_id`. - */ -static int accept_agent_connection( - struct lttcomm_sock *reg_sock, - struct agent_app_id *agent_app_id, - struct lttcomm_sock **agent_app_socket) -{ - int ret; - struct agent_protocol_version agent_version; - ssize_t size; - struct agent_register_msg msg; - struct lttcomm_sock *new_sock; - - LTTNG_ASSERT(reg_sock); - - new_sock = reg_sock->ops->accept(reg_sock); - if (!new_sock) { - ret = -ENOTCONN; - goto end; - } - - size = new_sock->ops->recvmsg(new_sock, &msg, sizeof(msg), 0); - if (size < sizeof(msg)) { - if (size < 0) { - PERROR("Failed to register new agent application"); - } else if (size != 0) { - ERR("Failed to register new agent application: invalid registration message length: expected length = %zu, message length = %zd", - sizeof(msg), size); - } else { - DBG("Failed to register new agent application: connection closed"); - } - ret = -EINVAL; - goto error_close_socket; - } - - agent_version = (struct agent_protocol_version) { - be32toh(msg.major_version), - be32toh(msg.minor_version), - }; - - /* Test communication protocol version of the registering agent. */ - if (!is_agent_protocol_version_supported(&agent_version)) { - ret = -EINVAL; - goto error_close_socket; - } - - *agent_app_id = (struct agent_app_id) { - .domain = (enum lttng_domain_type) be32toh(msg.domain), - .pid = (pid_t) be32toh(msg.pid), - }; - - DBG2("New registration for agent application: pid = %ld, domain = %s, socket fd = %d", - (long) agent_app_id->pid, - domain_type_str(agent_app_id->domain), new_sock->fd); - - *agent_app_socket = new_sock; - new_sock = NULL; - ret = 0; - goto end; - -error_close_socket: - new_sock->ops->close(new_sock); - lttcomm_destroy_sock(new_sock); -end: - return ret; -} - -bool agent_tracing_is_enabled(void) -{ - int enabled; - - enabled = uatomic_read(&agent_tracing_enabled); - LTTNG_ASSERT(enabled != -1); - return enabled == 1; -} - -/* - * Write agent TCP port using the rundir. - */ -static int write_agent_port(uint16_t port) -{ - return utils_create_pid_file( - (pid_t) port, the_config.agent_port_file_path.value); -} - -static -void mark_thread_as_ready(struct thread_notifiers *notifiers) -{ - DBG("Marking agent management thread as ready"); - sem_post(¬ifiers->ready); -} - -static -void wait_until_thread_is_ready(struct thread_notifiers *notifiers) -{ - DBG("Waiting for agent management thread to be ready"); - sem_wait(¬ifiers->ready); - DBG("Agent management thread is ready"); -} - -/* - * This thread manage application notify communication. - */ -static void *thread_agent_management(void *data) -{ - int i, ret, pollfd; - uint32_t revents, nb_fd; - struct lttng_poll_event events; - struct lttcomm_sock *reg_sock; - struct thread_notifiers *notifiers = data; - const int quit_pipe_read_fd = lttng_pipe_get_readfd( - notifiers->quit_pipe); - - DBG("Manage agent application registration."); - - rcu_register_thread(); - rcu_thread_online(); - - /* Agent initialization call MUST be called before starting the thread. */ - LTTNG_ASSERT(the_agent_apps_ht_by_sock); - - /* Create pollset with size 2, quit pipe and registration socket. */ - ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); - if (ret < 0) { - goto error_poll_create; - } - - ret = lttng_poll_add(&events, quit_pipe_read_fd, - LPOLLIN | LPOLLERR); - if (ret < 0) { - goto error_tcp_socket; - } - - reg_sock = init_tcp_socket(); - if (reg_sock) { - uint16_t port; - - ret = lttcomm_sock_get_port(reg_sock, &port); - LTTNG_ASSERT(ret == 0); - - ret = write_agent_port(port); - if (ret) { - ERR("Failed to create agent port file: agent tracing will be unavailable"); - /* Don't prevent the launch of the sessiond on error. */ - mark_thread_as_ready(notifiers); - goto error; - } - } else { - /* Don't prevent the launch of the sessiond on error. */ - mark_thread_as_ready(notifiers); - goto error_tcp_socket; - } - - /* - * Signal that the agent thread is ready. The command thread - * may start to query whether or not agent tracing is enabled. - */ - uatomic_set(&agent_tracing_enabled, 1); - mark_thread_as_ready(notifiers); - - /* Add TCP socket to the poll set. */ - ret = lttng_poll_add(&events, reg_sock->fd, - LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP); - if (ret < 0) { - goto error; - } - - while (1) { - DBG3("Manage agent polling"); - - /* Inifinite blocking call, waiting for transmission */ -restart: - ret = lttng_poll_wait(&events, -1); - DBG3("Manage agent return from poll on %d fds", - LTTNG_POLL_GETNB(&events)); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - goto restart; - } - goto error; - } - nb_fd = ret; - DBG3("%d fd ready", nb_fd); - - for (i = 0; i < nb_fd; i++) { - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - /* Thread quit pipe has been closed. Killing thread. */ - if (pollfd == quit_pipe_read_fd) { - goto exit; - } - - /* Activity on the registration socket. */ - if (revents & LPOLLIN) { - struct agent_app_id new_app_id; - struct agent_app *new_app = NULL; - struct lttcomm_sock *new_app_socket; - int new_app_socket_fd; - - LTTNG_ASSERT(pollfd == reg_sock->fd); - - ret = accept_agent_connection( - reg_sock, &new_app_id, &new_app_socket); - if (ret < 0) { - /* Errors are already logged. */ - continue; - } - - /* - * new_app_socket's ownership has been - * transferred to the new agent app. - */ - new_app = agent_create_app(new_app_id.pid, - new_app_id.domain, - new_app_socket); - if (!new_app) { - new_app_socket->ops->close( - new_app_socket); - continue; - } - new_app_socket_fd = new_app_socket->fd; - new_app_socket = NULL; - - /* - * Since this is a command socket (write then - * read), only add poll error event to only - * detect shutdown. - */ - ret = lttng_poll_add(&events, new_app_socket_fd, - LPOLLERR | LPOLLHUP | LPOLLRDHUP); - if (ret < 0) { - agent_destroy_app(new_app); - continue; - } - - /* - * Prevent sessions from being modified while - * the agent application's configuration is - * updated. - */ - session_lock_list(); - - /* - * Update the newly registered applications's - * configuration. - */ - update_agent_app(new_app); - - ret = agent_send_registration_done(new_app); - if (ret < 0) { - agent_destroy_app(new_app); - /* Removing from the poll set. */ - ret = lttng_poll_del(&events, - new_app_socket_fd); - if (ret < 0) { - session_unlock_list(); - goto error; - } - continue; - } - - /* Publish the new agent app. */ - agent_add_app(new_app); - - session_unlock_list(); - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - /* Removing from the poll set */ - ret = lttng_poll_del(&events, pollfd); - if (ret < 0) { - goto error; - } - agent_destroy_app_by_sock(pollfd); - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - } - } - -exit: - /* Whatever happens, try to delete it and exit. */ - (void) lttng_poll_del(&events, reg_sock->fd); -error: - destroy_tcp_socket(reg_sock); -error_tcp_socket: - lttng_poll_clean(&events); -error_poll_create: - uatomic_set(&agent_tracing_enabled, 0); - DBG("Cleaning up and stopping."); - rcu_thread_offline(); - rcu_unregister_thread(); - return NULL; -} - -static bool shutdown_agent_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); - - return notify_thread_pipe(write_fd) == 1; -} - -static void cleanup_agent_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - - lttng_pipe_destroy(notifiers->quit_pipe); - sem_destroy(¬ifiers->ready); - free(notifiers); -} - -bool launch_agent_management_thread(void) -{ - struct thread_notifiers *notifiers; - struct lttng_thread *thread; - - notifiers = zmalloc(sizeof(*notifiers)); - if (!notifiers) { - goto error_alloc; - } - - sem_init(¬ifiers->ready, 0, 0); - notifiers->quit_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!notifiers->quit_pipe) { - goto error; - } - thread = lttng_thread_create("Agent management", - thread_agent_management, - shutdown_agent_management_thread, - cleanup_agent_management_thread, - notifiers); - if (!thread) { - goto error; - } - wait_until_thread_is_ready(notifiers); - lttng_thread_put(thread); - return true; -error: - cleanup_agent_management_thread(notifiers); -error_alloc: - return false; -} diff --git a/src/bin/lttng-sessiond/agent-thread.cpp b/src/bin/lttng-sessiond/agent-thread.cpp new file mode 100644 index 000000000..5e158b49b --- /dev/null +++ b/src/bin/lttng-sessiond/agent-thread.cpp @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2013 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE + +#include +#include +#include +#include + +#include + +#include "fd-limit.h" +#include "agent-thread.h" +#include "agent.h" +#include "lttng-sessiond.h" +#include "session.h" +#include "utils.h" +#include "thread.h" + +struct thread_notifiers { + struct lttng_pipe *quit_pipe; + sem_t ready; +}; + +struct agent_app_id { + pid_t pid; + enum lttng_domain_type domain; +}; + +struct agent_protocol_version { + unsigned int major, minor; +}; + +static int agent_tracing_enabled = -1; + +/* + * Note that there is not port here. It's set after this URI is parsed so we + * can let the user define a custom one. However, localhost is ALWAYS the + * default listening address. + */ +static const char *default_reg_uri = + "tcp://" DEFAULT_NETWORK_VIEWER_BIND_ADDRESS; + +/* + * Update agent application using the given socket. This is done just after + * registration was successful. + * + * This will acquire the various sessions' lock; none must be held by the + * caller. + * The caller must hold the session list lock. + */ +static void update_agent_app(const struct agent_app *app) +{ + struct ltt_session *session, *stmp; + struct ltt_session_list *list; + struct agent *trigger_agent; + struct lttng_ht_iter iter; + + list = session_get_list(); + LTTNG_ASSERT(list); + + cds_list_for_each_entry_safe(session, stmp, &list->head, list) { + if (!session_get(session)) { + continue; + } + + session_lock(session); + if (session->ust_session) { + const struct agent *agt; + + rcu_read_lock(); + agt = trace_ust_find_agent(session->ust_session, app->domain); + if (agt) { + agent_update(agt, app); + } + rcu_read_unlock(); + } + session_unlock(session); + session_put(session); + } + + rcu_read_lock(); + /* + * We are protected against the addition of new events by the session + * list lock being held. + */ + cds_lfht_for_each_entry(the_trigger_agents_ht_by_domain->ht, + &iter.iter, trigger_agent, node.node) { + agent_update(trigger_agent, app); + } + rcu_read_unlock(); +} + +/* + * Create and init socket from uri. + */ +static struct lttcomm_sock *init_tcp_socket(void) +{ + int ret; + struct lttng_uri *uri = NULL; + struct lttcomm_sock *sock = NULL; + unsigned int port; + bool bind_succeeded = false; + + /* + * This should never fail since the URI is hardcoded and the port is set + * before this thread is launched. + */ + ret = uri_parse(default_reg_uri, &uri); + LTTNG_ASSERT(ret); + LTTNG_ASSERT(the_config.agent_tcp_port.begin > 0); + uri->port = the_config.agent_tcp_port.begin; + + sock = lttcomm_alloc_sock_from_uri(uri); + uri_free(uri); + if (sock == NULL) { + ERR("agent allocating TCP socket"); + goto error; + } + + ret = lttcomm_create_sock(sock); + if (ret < 0) { + goto error; + } + + for (port = the_config.agent_tcp_port.begin; + port <= the_config.agent_tcp_port.end; port++) { + ret = lttcomm_sock_set_port(sock, (uint16_t) port); + if (ret) { + ERR("Failed to set port %u on socket", + port); + goto error; + } + DBG3("Trying to bind on port %u", port); + ret = sock->ops->bind(sock); + if (!ret) { + bind_succeeded = true; + break; + } + + if (errno == EADDRINUSE) { + DBG("Failed to bind to port %u since it is already in use", + port); + } else { + PERROR("Failed to bind to port %u", port); + goto error; + } + } + + if (!bind_succeeded) { + if (the_config.agent_tcp_port.begin == + the_config.agent_tcp_port.end) { + WARN("Another process is already using the agent port %i. " + "Agent support will be deactivated.", + the_config.agent_tcp_port.begin); + goto error; + } else { + WARN("All ports in the range [%i, %i] are already in use. " + "Agent support will be deactivated.", + the_config.agent_tcp_port.begin, + the_config.agent_tcp_port.end); + goto error; + } + } + + ret = sock->ops->listen(sock, -1); + if (ret < 0) { + goto error; + } + + DBG("Listening on TCP port %u and socket %d", + port, sock->fd); + + return sock; + +error: + if (sock) { + lttcomm_destroy_sock(sock); + } + return NULL; +} + +/* + * Close and destroy the given TCP socket. + */ +static void destroy_tcp_socket(struct lttcomm_sock *sock) +{ + int ret; + uint16_t port; + + LTTNG_ASSERT(sock); + + ret = lttcomm_sock_get_port(sock, &port); + if (ret) { + ERR("Failed to get port of agent TCP socket"); + port = 0; + } + + DBG3("Destroy TCP socket on port %" PRIu16, + port); + + /* This will return gracefully if fd is invalid. */ + sock->ops->close(sock); + lttcomm_destroy_sock(sock); +} + +static const char *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 "unknown"; + } +} + +static bool is_agent_protocol_version_supported( + const struct agent_protocol_version *version) +{ + const bool is_supported = version->major == AGENT_MAJOR_VERSION && + version->minor == AGENT_MINOR_VERSION; + + if (!is_supported) { + WARN("Refusing agent connection: unsupported protocol version %ui.%ui, expected %i.%i", + version->major, version->minor, + AGENT_MAJOR_VERSION, AGENT_MINOR_VERSION); + } + + return is_supported; +} + +/* + * Handle a new agent connection on the registration socket. + * + * Returns 0 on success, or else a negative errno value. + * On success, the resulting socket is returned through `agent_app_socket` + * and the application's reported id is updated through `agent_app_id`. + */ +static int accept_agent_connection( + struct lttcomm_sock *reg_sock, + struct agent_app_id *agent_app_id, + struct lttcomm_sock **agent_app_socket) +{ + int ret; + struct agent_protocol_version agent_version; + ssize_t size; + struct agent_register_msg msg; + struct lttcomm_sock *new_sock; + + LTTNG_ASSERT(reg_sock); + + new_sock = reg_sock->ops->accept(reg_sock); + if (!new_sock) { + ret = -ENOTCONN; + goto end; + } + + size = new_sock->ops->recvmsg(new_sock, &msg, sizeof(msg), 0); + if (size < sizeof(msg)) { + if (size < 0) { + PERROR("Failed to register new agent application"); + } else if (size != 0) { + ERR("Failed to register new agent application: invalid registration message length: expected length = %zu, message length = %zd", + sizeof(msg), size); + } else { + DBG("Failed to register new agent application: connection closed"); + } + ret = -EINVAL; + goto error_close_socket; + } + + agent_version = (struct agent_protocol_version) { + be32toh(msg.major_version), + be32toh(msg.minor_version), + }; + + /* Test communication protocol version of the registering agent. */ + if (!is_agent_protocol_version_supported(&agent_version)) { + ret = -EINVAL; + goto error_close_socket; + } + + *agent_app_id = (struct agent_app_id) { + .pid = (pid_t) be32toh(msg.pid), + .domain = (lttng_domain_type) be32toh(msg.domain), + }; + + DBG2("New registration for agent application: pid = %ld, domain = %s, socket fd = %d", + (long) agent_app_id->pid, + domain_type_str(agent_app_id->domain), new_sock->fd); + + *agent_app_socket = new_sock; + new_sock = NULL; + ret = 0; + goto end; + +error_close_socket: + new_sock->ops->close(new_sock); + lttcomm_destroy_sock(new_sock); +end: + return ret; +} + +bool agent_tracing_is_enabled(void) +{ + int enabled; + + enabled = uatomic_read(&agent_tracing_enabled); + LTTNG_ASSERT(enabled != -1); + return enabled == 1; +} + +/* + * Write agent TCP port using the rundir. + */ +static int write_agent_port(uint16_t port) +{ + return utils_create_pid_file( + (pid_t) port, the_config.agent_port_file_path.value); +} + +static +void mark_thread_as_ready(struct thread_notifiers *notifiers) +{ + DBG("Marking agent management thread as ready"); + sem_post(¬ifiers->ready); +} + +static +void wait_until_thread_is_ready(struct thread_notifiers *notifiers) +{ + DBG("Waiting for agent management thread to be ready"); + sem_wait(¬ifiers->ready); + DBG("Agent management thread is ready"); +} + +/* + * This thread manage application notify communication. + */ +static void *thread_agent_management(void *data) +{ + int i, ret, pollfd; + uint32_t revents, nb_fd; + struct lttng_poll_event events; + struct lttcomm_sock *reg_sock; + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int quit_pipe_read_fd = lttng_pipe_get_readfd( + notifiers->quit_pipe); + + DBG("Manage agent application registration."); + + rcu_register_thread(); + rcu_thread_online(); + + /* Agent initialization call MUST be called before starting the thread. */ + LTTNG_ASSERT(the_agent_apps_ht_by_sock); + + /* Create pollset with size 2, quit pipe and registration socket. */ + ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); + if (ret < 0) { + goto error_poll_create; + } + + ret = lttng_poll_add(&events, quit_pipe_read_fd, + LPOLLIN | LPOLLERR); + if (ret < 0) { + goto error_tcp_socket; + } + + reg_sock = init_tcp_socket(); + if (reg_sock) { + uint16_t port; + + ret = lttcomm_sock_get_port(reg_sock, &port); + LTTNG_ASSERT(ret == 0); + + ret = write_agent_port(port); + if (ret) { + ERR("Failed to create agent port file: agent tracing will be unavailable"); + /* Don't prevent the launch of the sessiond on error. */ + mark_thread_as_ready(notifiers); + goto error; + } + } else { + /* Don't prevent the launch of the sessiond on error. */ + mark_thread_as_ready(notifiers); + goto error_tcp_socket; + } + + /* + * Signal that the agent thread is ready. The command thread + * may start to query whether or not agent tracing is enabled. + */ + uatomic_set(&agent_tracing_enabled, 1); + mark_thread_as_ready(notifiers); + + /* Add TCP socket to the poll set. */ + ret = lttng_poll_add(&events, reg_sock->fd, + LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP); + if (ret < 0) { + goto error; + } + + while (1) { + DBG3("Manage agent polling"); + + /* Inifinite blocking call, waiting for transmission */ +restart: + ret = lttng_poll_wait(&events, -1); + DBG3("Manage agent return from poll on %d fds", + LTTNG_POLL_GETNB(&events)); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + goto restart; + } + goto error; + } + nb_fd = ret; + DBG3("%d fd ready", nb_fd); + + for (i = 0; i < nb_fd; i++) { + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + /* Thread quit pipe has been closed. Killing thread. */ + if (pollfd == quit_pipe_read_fd) { + goto exit; + } + + /* Activity on the registration socket. */ + if (revents & LPOLLIN) { + struct agent_app_id new_app_id; + struct agent_app *new_app = NULL; + struct lttcomm_sock *new_app_socket; + int new_app_socket_fd; + + LTTNG_ASSERT(pollfd == reg_sock->fd); + + ret = accept_agent_connection( + reg_sock, &new_app_id, &new_app_socket); + if (ret < 0) { + /* Errors are already logged. */ + continue; + } + + /* + * new_app_socket's ownership has been + * transferred to the new agent app. + */ + new_app = agent_create_app(new_app_id.pid, + new_app_id.domain, + new_app_socket); + if (!new_app) { + new_app_socket->ops->close( + new_app_socket); + continue; + } + new_app_socket_fd = new_app_socket->fd; + new_app_socket = NULL; + + /* + * Since this is a command socket (write then + * read), only add poll error event to only + * detect shutdown. + */ + ret = lttng_poll_add(&events, new_app_socket_fd, + LPOLLERR | LPOLLHUP | LPOLLRDHUP); + if (ret < 0) { + agent_destroy_app(new_app); + continue; + } + + /* + * Prevent sessions from being modified while + * the agent application's configuration is + * updated. + */ + session_lock_list(); + + /* + * Update the newly registered applications's + * configuration. + */ + update_agent_app(new_app); + + ret = agent_send_registration_done(new_app); + if (ret < 0) { + agent_destroy_app(new_app); + /* Removing from the poll set. */ + ret = lttng_poll_del(&events, + new_app_socket_fd); + if (ret < 0) { + session_unlock_list(); + goto error; + } + continue; + } + + /* Publish the new agent app. */ + agent_add_app(new_app); + + session_unlock_list(); + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + /* Removing from the poll set */ + ret = lttng_poll_del(&events, pollfd); + if (ret < 0) { + goto error; + } + agent_destroy_app_by_sock(pollfd); + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + } + } + +exit: + /* Whatever happens, try to delete it and exit. */ + (void) lttng_poll_del(&events, reg_sock->fd); +error: + destroy_tcp_socket(reg_sock); +error_tcp_socket: + lttng_poll_clean(&events); +error_poll_create: + uatomic_set(&agent_tracing_enabled, 0); + DBG("Cleaning up and stopping."); + rcu_thread_offline(); + rcu_unregister_thread(); + return NULL; +} + +static bool shutdown_agent_management_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); + + return notify_thread_pipe(write_fd) == 1; +} + +static void cleanup_agent_management_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + + lttng_pipe_destroy(notifiers->quit_pipe); + sem_destroy(¬ifiers->ready); + free(notifiers); +} + +bool launch_agent_management_thread(void) +{ + struct thread_notifiers *notifiers; + struct lttng_thread *thread; + + notifiers = (thread_notifiers *) zmalloc(sizeof(*notifiers)); + if (!notifiers) { + goto error_alloc; + } + + sem_init(¬ifiers->ready, 0, 0); + notifiers->quit_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!notifiers->quit_pipe) { + goto error; + } + thread = lttng_thread_create("Agent management", + thread_agent_management, + shutdown_agent_management_thread, + cleanup_agent_management_thread, + notifiers); + if (!thread) { + goto error; + } + wait_until_thread_is_ready(notifiers); + lttng_thread_put(thread); + return true; +error: + cleanup_agent_management_thread(notifiers); +error_alloc: + return false; +} diff --git a/src/bin/lttng-sessiond/agent.c b/src/bin/lttng-sessiond/agent.c deleted file mode 100644 index 77846b4f2..000000000 --- a/src/bin/lttng-sessiond/agent.c +++ /dev/null @@ -1,1624 +0,0 @@ -/* - * Copyright (C) 2013 David Goulet - * Copyright (C) 2016 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include "agent.h" -#include "ust-app.h" -#include "utils.h" -#include "common/error.h" - -#define AGENT_RET_CODE_INDEX(code) (code - AGENT_RET_CODE_SUCCESS) - -typedef enum lttng_event_rule_status (*event_rule_logging_get_name_pattern)( - const struct lttng_event_rule *rule, const char **pattern); -typedef enum lttng_event_rule_status (*event_rule_logging_get_log_level_rule)( - const struct lttng_event_rule *rule, - const struct lttng_log_level_rule **log_level_rule); - -/* - * Agent application context representation. - */ -struct agent_app_ctx { - char *provider_name; - char *ctx_name; - - /* agent_app_ctx are part of the agent app_ctx_list. */ - struct cds_list_head list_node; - - /* For call_rcu teardown. */ - struct rcu_head rcu_node; -}; - -/* - * Human readable agent return code. - */ -static const char *error_string_array[] = { - [ AGENT_RET_CODE_INDEX(AGENT_RET_CODE_SUCCESS) ] = "Success", - [ AGENT_RET_CODE_INDEX(AGENT_RET_CODE_INVALID) ] = "Invalid command", - [ AGENT_RET_CODE_INDEX(AGENT_RET_CODE_UNKNOWN_NAME) ] = "Unknown logger name", - - /* Last element */ - [ AGENT_RET_CODE_INDEX(AGENT_RET_CODE_NR) ] = "Unknown code", -}; - -static -void log_reply_code(uint32_t in_reply_ret_code) -{ - int level = PRINT_DBG3; - /* - * reply_ret_code and in_reply_ret_code are kept separate to have a - * sanitized value (used to retrieve the human readable string) and the - * original value which is logged as-is. - */ - uint32_t reply_ret_code = in_reply_ret_code; - - if (reply_ret_code < AGENT_RET_CODE_SUCCESS || - reply_ret_code >= AGENT_RET_CODE_NR) { - reply_ret_code = AGENT_RET_CODE_NR; - level = PRINT_ERR; - } - - LOG(level, "Agent replied with retcode: %s (%"PRIu32")", - error_string_array[AGENT_RET_CODE_INDEX( - reply_ret_code)], - in_reply_ret_code); -} - -/* - * Match function for the events hash table lookup by name. - */ -static int ht_match_event_by_name(struct cds_lfht_node *node, - const void *_key) -{ - struct agent_event *event; - const struct agent_ht_key *key; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - event = caa_container_of(node, struct agent_event, node.node); - key = _key; - - /* Match 1 elements of the key: name. */ - - /* Event name */ - if (strncmp(event->name, key->name, sizeof(event->name)) != 0) { - goto no_match; - } - /* Match. */ - return 1; - -no_match: - return 0; -} - -/* - * Match function for the events hash table lookup by name, log level and - * filter expression. - */ -static int ht_match_event(struct cds_lfht_node *node, - const void *_key) -{ - struct agent_event *event; - const struct agent_ht_key *key; - int ll_match; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - event = caa_container_of(node, struct agent_event, node.node); - key = _key; - - /* Match 2 elements of the key: name and loglevel. */ - - /* Event name */ - if (strncmp(event->name, key->name, sizeof(event->name)) != 0) { - goto no_match; - } - - /* Event loglevel value and type. */ - ll_match = loglevels_match(event->loglevel_type, - event->loglevel_value, key->loglevel_type, - key->loglevel_value, LTTNG_EVENT_LOGLEVEL_ALL); - - if (!ll_match) { - goto no_match; - } - - /* Filter expression */ - if (!!event->filter_expression != !!key->filter_expression) { - /* One has a filter expression, the other does not */ - goto no_match; - } - - if (event->filter_expression) { - if (strncmp(event->filter_expression, key->filter_expression, - strlen(event->filter_expression)) != 0) { - goto no_match; - } - } - - return 1; - -no_match: - return 0; -} - -/* - * Add unique agent event based on the event name and loglevel. - */ -static void add_unique_agent_event(struct lttng_ht *ht, - struct agent_event *event) -{ - struct cds_lfht_node *node_ptr; - struct agent_ht_key key; - - LTTNG_ASSERT(ht); - LTTNG_ASSERT(ht->ht); - LTTNG_ASSERT(event); - - key.name = event->name; - key.loglevel_value = event->loglevel_value; - key.loglevel_type = event->loglevel_type; - key.filter_expression = event->filter_expression; - - node_ptr = cds_lfht_add_unique(ht->ht, - ht->hash_fct(event->node.key, lttng_ht_seed), - ht_match_event, &key, &event->node.node); - LTTNG_ASSERT(node_ptr == &event->node.node); -} - -/* - * URCU delayed agent event reclaim. - */ -static void destroy_event_agent_rcu(struct rcu_head *head) -{ - struct lttng_ht_node_str *node = - caa_container_of(head, struct lttng_ht_node_str, head); - struct agent_event *event = - caa_container_of(node, struct agent_event, node); - - agent_destroy_event(event); -} - -/* - * URCU delayed agent app reclaim. - */ -static void destroy_app_agent_rcu(struct rcu_head *head) -{ - struct lttng_ht_node_ulong *node = - caa_container_of(head, struct lttng_ht_node_ulong, head); - struct agent_app *app = - caa_container_of(node, struct agent_app, node); - - free(app); -} - -/* - * Communication with the agent. Send the message header to the given socket in - * big endian. - * - * Return 0 on success or else a negative errno message of sendmsg() op. - */ -static int send_header(struct lttcomm_sock *sock, uint64_t data_size, - uint32_t cmd, uint32_t cmd_version) -{ - int ret; - ssize_t size; - struct lttcomm_agent_hdr msg; - - LTTNG_ASSERT(sock); - - memset(&msg, 0, sizeof(msg)); - msg.data_size = htobe64(data_size); - msg.cmd = htobe32(cmd); - msg.cmd_version = htobe32(cmd_version); - - size = sock->ops->sendmsg(sock, &msg, sizeof(msg), 0); - if (size < sizeof(msg)) { - ret = -errno; - goto error; - } - ret = 0; - -error: - return ret; -} - -/* - * Communication call with the agent. Send the payload to the given socket. The - * header MUST be sent prior to this call. - * - * Return 0 on success or else a negative errno value of sendmsg() op. - */ -static int send_payload(struct lttcomm_sock *sock, const void *data, - size_t size) -{ - int ret; - ssize_t len; - - LTTNG_ASSERT(sock); - LTTNG_ASSERT(data); - - len = sock->ops->sendmsg(sock, data, size, 0); - if (len < size) { - ret = -errno; - goto error; - } - ret = 0; - -error: - return ret; -} - -/* - * Communication call with the agent. Receive reply from the agent using the - * given socket. - * - * Return 0 on success or else a negative errno value from recvmsg() op. - */ -static int recv_reply(struct lttcomm_sock *sock, void *buf, size_t size) -{ - int ret; - ssize_t len; - - LTTNG_ASSERT(sock); - LTTNG_ASSERT(buf); - - len = sock->ops->recvmsg(sock, buf, size, 0); - if (len < size) { - ret = -errno; - goto error; - } - ret = 0; - -error: - return ret; -} - -/* - * Internal event listing for a given app. Populate events. - * - * Return number of element in the list or else a negative LTTNG_ERR* code. - * On success, the caller is responsible for freeing the memory - * allocated for "events". - */ -static ssize_t list_events(struct agent_app *app, struct lttng_event **events) -{ - int ret, i, len = 0, offset = 0; - uint32_t nb_event; - size_t data_size; - uint32_t reply_ret_code; - struct lttng_event *tmp_events = NULL; - struct lttcomm_agent_list_reply *reply = NULL; - struct lttcomm_agent_list_reply_hdr reply_hdr; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(app->sock); - LTTNG_ASSERT(events); - - DBG2("Agent listing events for app pid: %d and socket %d", app->pid, - app->sock->fd); - - ret = send_header(app->sock, 0, AGENT_CMD_LIST, 0); - if (ret < 0) { - goto error_io; - } - - /* Get list header so we know how much we'll receive. */ - ret = recv_reply(app->sock, &reply_hdr, sizeof(reply_hdr)); - if (ret < 0) { - goto error_io; - } - - reply_ret_code = be32toh(reply_hdr.ret_code); - log_reply_code(reply_ret_code); - switch (reply_ret_code) { - case AGENT_RET_CODE_SUCCESS: - data_size = be32toh(reply_hdr.data_size) + sizeof(*reply); - break; - default: - ret = LTTNG_ERR_UNK; - goto error; - } - - reply = zmalloc(data_size); - if (!reply) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - - /* Get the list with the appropriate data size. */ - ret = recv_reply(app->sock, reply, data_size); - if (ret < 0) { - goto error_io; - } - - nb_event = be32toh(reply->nb_event); - tmp_events = zmalloc(sizeof(*tmp_events) * nb_event); - if (!tmp_events) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - - for (i = 0; i < nb_event; i++) { - offset += len; - if (lttng_strncpy(tmp_events[i].name, reply->payload + offset, - sizeof(tmp_events[i].name))) { - ret = LTTNG_ERR_INVALID; - goto error; - } - tmp_events[i].pid = app->pid; - tmp_events[i].enabled = -1; - len = strlen(reply->payload + offset) + 1; - } - - *events = tmp_events; - - free(reply); - return nb_event; - -error_io: - ret = LTTNG_ERR_UST_LIST_FAIL; -error: - free(reply); - free(tmp_events); - return -ret; - -} - -/* - * Internal enable agent event on a agent application. This function - * communicates with the agent to enable a given event. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -static int enable_event(const struct agent_app *app, struct agent_event *event) -{ - int ret; - char *bytes_to_send; - uint64_t data_size; - size_t filter_expression_length; - uint32_t reply_ret_code; - struct lttcomm_agent_enable_event msg; - struct lttcomm_agent_generic_reply reply; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(app->sock); - LTTNG_ASSERT(event); - - DBG2("Agent enabling event %s for app pid: %d and socket %d", event->name, - app->pid, app->sock->fd); - - /* - * Calculate the payload's size, which is the fixed-size struct followed - * by the variable-length filter expression (+1 for the ending \0). - */ - if (!event->filter_expression) { - filter_expression_length = 0; - } else { - filter_expression_length = strlen(event->filter_expression) + 1; - } - data_size = sizeof(msg) + filter_expression_length; - - memset(&msg, 0, sizeof(msg)); - msg.loglevel_value = htobe32(event->loglevel_value); - msg.loglevel_type = htobe32(event->loglevel_type); - if (lttng_strncpy(msg.name, event->name, sizeof(msg.name))) { - ret = LTTNG_ERR_INVALID; - goto error; - } - msg.filter_expression_length = htobe32(filter_expression_length); - - ret = send_header(app->sock, data_size, AGENT_CMD_ENABLE, 0); - if (ret < 0) { - goto error_io; - } - - bytes_to_send = zmalloc(data_size); - if (!bytes_to_send) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - - memcpy(bytes_to_send, &msg, sizeof(msg)); - if (filter_expression_length > 0) { - memcpy(bytes_to_send + sizeof(msg), event->filter_expression, - filter_expression_length); - } - - ret = send_payload(app->sock, bytes_to_send, data_size); - free(bytes_to_send); - if (ret < 0) { - goto error_io; - } - - ret = recv_reply(app->sock, &reply, sizeof(reply)); - if (ret < 0) { - goto error_io; - } - - reply_ret_code = be32toh(reply.ret_code); - log_reply_code(reply_ret_code); - switch (reply_ret_code) { - case AGENT_RET_CODE_SUCCESS: - break; - case AGENT_RET_CODE_UNKNOWN_NAME: - ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; - goto error; - default: - ret = LTTNG_ERR_UNK; - goto error; - } - - return LTTNG_OK; - -error_io: - ret = LTTNG_ERR_UST_ENABLE_FAIL; -error: - return ret; -} - -/* - * Send Pascal-style string. Size is sent as a 32-bit big endian integer. - */ -static -int send_pstring(struct lttcomm_sock *sock, const char *str, uint32_t len) -{ - int ret; - uint32_t len_be; - - len_be = htobe32(len); - ret = send_payload(sock, &len_be, sizeof(len_be)); - if (ret) { - goto end; - } - - ret = send_payload(sock, str, len); - if (ret) { - goto end; - } -end: - return ret; -} - -/* - * Internal enable application context on an agent application. This function - * communicates with the agent to enable a given application context. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -static int app_context_op(const struct agent_app *app, - const struct agent_app_ctx *ctx, enum lttcomm_agent_command cmd) -{ - int ret; - uint32_t reply_ret_code; - struct lttcomm_agent_generic_reply reply; - size_t app_ctx_provider_name_len, app_ctx_name_len, data_size; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(app->sock); - LTTNG_ASSERT(ctx); - LTTNG_ASSERT(cmd == AGENT_CMD_APP_CTX_ENABLE || - cmd == AGENT_CMD_APP_CTX_DISABLE); - - DBG2("Agent %s application %s:%s for app pid: %d and socket %d", - cmd == AGENT_CMD_APP_CTX_ENABLE ? "enabling" : "disabling", - ctx->provider_name, ctx->ctx_name, - app->pid, app->sock->fd); - - /* - * Calculate the payload's size, which consists of the size (u32, BE) - * of the provider name, the NULL-terminated provider name string, the - * size (u32, BE) of the context name, followed by the NULL-terminated - * context name string. - */ - app_ctx_provider_name_len = strlen(ctx->provider_name) + 1; - app_ctx_name_len = strlen(ctx->ctx_name) + 1; - data_size = sizeof(uint32_t) + app_ctx_provider_name_len + - sizeof(uint32_t) + app_ctx_name_len; - - ret = send_header(app->sock, data_size, cmd, 0); - if (ret < 0) { - goto error_io; - } - - if (app_ctx_provider_name_len > UINT32_MAX || - app_ctx_name_len > UINT32_MAX) { - ERR("Application context name > MAX_UINT32"); - ret = LTTNG_ERR_INVALID; - goto error; - } - - ret = send_pstring(app->sock, ctx->provider_name, - (uint32_t) app_ctx_provider_name_len); - if (ret < 0) { - goto error_io; - } - - ret = send_pstring(app->sock, ctx->ctx_name, - (uint32_t) app_ctx_name_len); - if (ret < 0) { - goto error_io; - } - - ret = recv_reply(app->sock, &reply, sizeof(reply)); - if (ret < 0) { - goto error_io; - } - - reply_ret_code = be32toh(reply.ret_code); - log_reply_code(reply_ret_code); - switch (reply_ret_code) { - case AGENT_RET_CODE_SUCCESS: - break; - default: - ret = LTTNG_ERR_UNK; - goto error; - } - - return LTTNG_OK; - -error_io: - ret = LTTNG_ERR_UST_ENABLE_FAIL; -error: - return ret; -} - -/* - * Internal disable agent event call on a agent application. This function - * communicates with the agent to disable a given event. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -static int disable_event(struct agent_app *app, struct agent_event *event) -{ - int ret; - uint64_t data_size; - uint32_t reply_ret_code; - struct lttcomm_agent_disable_event msg; - struct lttcomm_agent_generic_reply reply; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(app->sock); - LTTNG_ASSERT(event); - - DBG2("Agent disabling event %s for app pid: %d and socket %d", event->name, - app->pid, app->sock->fd); - - data_size = sizeof(msg); - memset(&msg, 0, sizeof(msg)); - if (lttng_strncpy(msg.name, event->name, sizeof(msg.name))) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - ret = send_header(app->sock, data_size, AGENT_CMD_DISABLE, 0); - if (ret < 0) { - goto error_io; - } - - ret = send_payload(app->sock, &msg, sizeof(msg)); - if (ret < 0) { - goto error_io; - } - - ret = recv_reply(app->sock, &reply, sizeof(reply)); - if (ret < 0) { - goto error_io; - } - - reply_ret_code = be32toh(reply.ret_code); - log_reply_code(reply_ret_code); - switch (reply_ret_code) { - case AGENT_RET_CODE_SUCCESS: - break; - case AGENT_RET_CODE_UNKNOWN_NAME: - ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; - goto error; - default: - ret = LTTNG_ERR_UNK; - goto error; - } - - return LTTNG_OK; - -error_io: - ret = LTTNG_ERR_UST_DISABLE_FAIL; -error: - return ret; -} - -/* - * Send back the registration DONE command to a given agent application. - * - * Return 0 on success or else a negative value. - */ -int agent_send_registration_done(struct agent_app *app) -{ - LTTNG_ASSERT(app); - LTTNG_ASSERT(app->sock); - - DBG("Agent sending registration done to app socket %d", app->sock->fd); - - return send_header(app->sock, 0, AGENT_CMD_REG_DONE, 0); -} - -/* - * Enable agent event on every agent applications registered with the session - * daemon. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -int agent_enable_event(struct agent_event *event, - enum lttng_domain_type domain) -{ - int ret; - struct agent_app *app; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(event); - - rcu_read_lock(); - - cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, - node.node) { - if (app->domain != domain) { - continue; - } - - /* Enable event on agent application through TCP socket. */ - ret = enable_event(app, event); - if (ret != LTTNG_OK) { - goto error; - } - } - - event->enabled_count++; - ret = LTTNG_OK; - -error: - rcu_read_unlock(); - return ret; -} - -static -void destroy_app_ctx(struct agent_app_ctx *ctx) -{ - free(ctx->provider_name); - free(ctx->ctx_name); - free(ctx); -} - -static -struct agent_app_ctx *create_app_ctx(const struct lttng_event_context *ctx) -{ - struct agent_app_ctx *agent_ctx = NULL; - - if (!ctx) { - goto end; - } - - LTTNG_ASSERT(ctx->ctx == LTTNG_EVENT_CONTEXT_APP_CONTEXT); - agent_ctx = zmalloc(sizeof(*ctx)); - if (!agent_ctx) { - goto end; - } - - agent_ctx->provider_name = strdup(ctx->u.app_ctx.provider_name); - agent_ctx->ctx_name = strdup(ctx->u.app_ctx.ctx_name); - if (!agent_ctx->provider_name || !agent_ctx->ctx_name) { - destroy_app_ctx(agent_ctx); - agent_ctx = NULL; - } -end: - return agent_ctx; -} - -/* - * Enable agent context on every agent applications registered with the session - * daemon. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -int agent_enable_context(const struct lttng_event_context *ctx, - enum lttng_domain_type domain) -{ - int ret; - struct agent_app *app; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(ctx); - if (ctx->ctx != LTTNG_EVENT_CONTEXT_APP_CONTEXT) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - rcu_read_lock(); - - cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, - node.node) { - struct agent_app_ctx *agent_ctx; - - if (app->domain != domain) { - continue; - } - - agent_ctx = create_app_ctx(ctx); - if (!agent_ctx) { - ret = LTTNG_ERR_NOMEM; - goto error_unlock; - } - - /* Enable event on agent application through TCP socket. */ - ret = app_context_op(app, agent_ctx, AGENT_CMD_APP_CTX_ENABLE); - destroy_app_ctx(agent_ctx); - if (ret != LTTNG_OK) { - goto error_unlock; - } - } - - ret = LTTNG_OK; - -error_unlock: - rcu_read_unlock(); -error: - return ret; -} - -/* - * Disable agent event on every agent application registered with the session - * daemon. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -int agent_disable_event(struct agent_event *event, - enum lttng_domain_type domain) -{ - int ret = LTTNG_OK; - struct agent_app *app; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(event); - if (!AGENT_EVENT_IS_ENABLED(event)) { - goto end; - } - - if (--event->enabled_count != 0) { - /* - * Agent event still enabled. Disable the agent event only when - * all "users" have disabled it (event notifiers, event rules, - * etc.). - */ - ret = LTTNG_OK; - goto end; - } - - rcu_read_lock(); - - cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, - node.node) { - if (app->domain != domain) { - continue; - } - - /* Enable event on agent application through TCP socket. */ - ret = disable_event(app, event); - if (ret != LTTNG_OK) { - goto error; - } - } - - /* event->enabled_count is now 0. */ - LTTNG_ASSERT(!AGENT_EVENT_IS_ENABLED(event)); - -error: - rcu_read_unlock(); -end: - return ret; -} - -/* - * Disable agent context on every agent application registered with the session - * daemon. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -static int disable_context(struct agent_app_ctx *ctx, - enum lttng_domain_type domain) -{ - int ret = LTTNG_OK; - struct agent_app *app; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(ctx); - - rcu_read_lock(); - DBG2("Disabling agent application context %s:%s", - ctx->provider_name, ctx->ctx_name); - cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, - node.node) { - if (app->domain != domain) { - continue; - } - - ret = app_context_op(app, ctx, AGENT_CMD_APP_CTX_DISABLE); - if (ret != LTTNG_OK) { - goto end; - } - } -end: - rcu_read_unlock(); - return ret; -} - -/* - * Ask every agent for the list of possible event. Events is allocated with the - * events of every agent application. - * - * Return the number of events or else a negative value. - */ -int agent_list_events(struct lttng_event **events, - enum lttng_domain_type domain) -{ - int ret; - size_t nbmem, count = 0; - struct agent_app *app; - struct lttng_event *tmp_events = NULL; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(events); - - DBG2("Agent listing events for domain %d", domain); - - nbmem = UST_APP_EVENT_LIST_SIZE; - tmp_events = zmalloc(nbmem * sizeof(*tmp_events)); - if (!tmp_events) { - PERROR("zmalloc agent list events"); - ret = -ENOMEM; - goto error; - } - - rcu_read_lock(); - cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, - node.node) { - ssize_t nb_ev; - struct lttng_event *agent_events; - - /* Skip domain not asked by the list. */ - if (app->domain != domain) { - continue; - } - - nb_ev = list_events(app, &agent_events); - if (nb_ev < 0) { - ret = nb_ev; - goto error_unlock; - } - - if (count + nb_ev > nbmem) { - /* In case the realloc fails, we free the memory */ - struct lttng_event *new_tmp_events; - size_t new_nbmem; - - new_nbmem = max_t(size_t, count + nb_ev, nbmem << 1); - DBG2("Reallocating agent event list from %zu to %zu entries", - nbmem, new_nbmem); - new_tmp_events = realloc(tmp_events, - new_nbmem * sizeof(*new_tmp_events)); - if (!new_tmp_events) { - PERROR("realloc agent events"); - ret = -ENOMEM; - free(agent_events); - goto error_unlock; - } - /* Zero the new memory */ - memset(new_tmp_events + nbmem, 0, - (new_nbmem - nbmem) * sizeof(*new_tmp_events)); - nbmem = new_nbmem; - tmp_events = new_tmp_events; - } - memcpy(tmp_events + count, agent_events, - nb_ev * sizeof(*tmp_events)); - free(agent_events); - count += nb_ev; - } - rcu_read_unlock(); - - ret = count; - *events = tmp_events; - return ret; - -error_unlock: - rcu_read_unlock(); -error: - free(tmp_events); - return ret; -} - -/* - * Create a agent app object using the given PID. - * - * Return newly allocated object or else NULL on error. - */ -struct agent_app *agent_create_app(pid_t pid, enum lttng_domain_type domain, - struct lttcomm_sock *sock) -{ - struct agent_app *app; - - LTTNG_ASSERT(sock); - - app = zmalloc(sizeof(*app)); - if (!app) { - PERROR("Failed to allocate agent application instance"); - goto error; - } - - app->pid = pid; - app->domain = domain; - app->sock = sock; - lttng_ht_node_init_ulong(&app->node, (unsigned long) app->sock->fd); - -error: - return app; -} - -/* - * Lookup agent app by socket in the global hash table. - * - * RCU read side lock MUST be acquired. - * - * Return object if found else NULL. - */ -struct agent_app *agent_find_app_by_sock(int sock) -{ - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter iter; - struct agent_app *app; - - LTTNG_ASSERT(sock >= 0); - - lttng_ht_lookup(the_agent_apps_ht_by_sock, - (void *) ((unsigned long) sock), &iter); - node = lttng_ht_iter_get_node_ulong(&iter); - if (node == NULL) { - goto error; - } - app = caa_container_of(node, struct agent_app, node); - - DBG3("Agent app pid %d found by sock %d.", app->pid, sock); - return app; - -error: - DBG3("Agent app NOT found by sock %d.", sock); - return NULL; -} - -/* - * Add agent application object to the global hash table. - */ -void agent_add_app(struct agent_app *app) -{ - LTTNG_ASSERT(app); - - DBG3("Agent adding app sock: %d and pid: %d to ht", app->sock->fd, app->pid); - lttng_ht_add_unique_ulong(the_agent_apps_ht_by_sock, &app->node); -} - -/* - * Delete agent application from the global hash table. - * - * rcu_read_lock() must be held by the caller. - */ -void agent_delete_app(struct agent_app *app) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(app); - - DBG3("Agent deleting app pid: %d and sock: %d", app->pid, app->sock->fd); - - iter.iter.node = &app->node.node; - ret = lttng_ht_del(the_agent_apps_ht_by_sock, &iter); - LTTNG_ASSERT(!ret); -} - -/* - * Destroy an agent application object by detaching it from its corresponding - * UST app if one is connected by closing the socket. Finally, perform a - * delayed memory reclaim. - */ -void agent_destroy_app(struct agent_app *app) -{ - LTTNG_ASSERT(app); - - if (app->sock) { - app->sock->ops->close(app->sock); - lttcomm_destroy_sock(app->sock); - } - - call_rcu(&app->node.head, destroy_app_agent_rcu); -} - -/* - * Initialize an already allocated agent object. - * - * Return 0 on success or else a negative errno value. - */ -int agent_init(struct agent *agt) -{ - int ret; - - LTTNG_ASSERT(agt); - - agt->events = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); - if (!agt->events) { - ret = -ENOMEM; - goto error; - } - lttng_ht_node_init_u64(&agt->node, agt->domain); - - CDS_INIT_LIST_HEAD(&agt->app_ctx_list); - return 0; - -error: - return ret; -} - -/* - * Add agent object to the given hash table. - */ -void agent_add(struct agent *agt, struct lttng_ht *ht) -{ - LTTNG_ASSERT(agt); - LTTNG_ASSERT(ht); - - DBG3("Agent adding from domain %d", agt->domain); - - lttng_ht_add_unique_u64(ht, &agt->node); -} - -/* - * Create an agent object for the given domain. - * - * Return the allocated agent or NULL on error. - */ -struct agent *agent_create(enum lttng_domain_type domain) -{ - int ret; - struct agent *agt; - - agt = zmalloc(sizeof(struct agent)); - if (!agt) { - goto error; - } - agt->domain = domain; - - ret = agent_init(agt); - if (ret < 0) { - free(agt); - agt = NULL; - goto error; - } - -error: - return agt; -} - -/* - * Create a newly allocated agent event data structure. - * Ownership of filter_expression is taken. - * - * Return a new object else NULL on error. - */ -struct agent_event *agent_create_event(const char *name, - enum lttng_loglevel_type loglevel_type, int loglevel_value, - struct lttng_bytecode *filter, char *filter_expression) -{ - struct agent_event *event = NULL; - - DBG3("Agent create new event with name %s, loglevel type %d, \ - loglevel value %d and filter %s", - name, loglevel_type, loglevel_value, - filter_expression ? filter_expression : "NULL"); - - if (!name) { - ERR("Failed to create agent event; no name provided."); - goto error; - } - - event = zmalloc(sizeof(*event)); - if (!event) { - goto error; - } - - strncpy(event->name, name, sizeof(event->name)); - event->name[sizeof(event->name) - 1] = '\0'; - lttng_ht_node_init_str(&event->node, event->name); - - event->loglevel_value = loglevel_value; - event->loglevel_type = loglevel_type; - event->filter = filter; - event->filter_expression = filter_expression; -error: - return event; -} - -/* - * Unique add of a agent event to an agent object. - */ -void agent_add_event(struct agent_event *event, struct agent *agt) -{ - LTTNG_ASSERT(event); - LTTNG_ASSERT(agt); - LTTNG_ASSERT(agt->events); - - DBG3("Agent adding event %s", event->name); - add_unique_agent_event(agt->events, event); - agt->being_used = 1; -} - -/* - * Unique add of a agent context to an agent object. - */ -int agent_add_context(const struct lttng_event_context *ctx, struct agent *agt) -{ - int ret = LTTNG_OK; - struct agent_app_ctx *agent_ctx = NULL; - - LTTNG_ASSERT(ctx); - LTTNG_ASSERT(agt); - LTTNG_ASSERT(agt->events); - LTTNG_ASSERT(ctx->ctx == LTTNG_EVENT_CONTEXT_APP_CONTEXT); - - agent_ctx = create_app_ctx(ctx); - if (!agent_ctx) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - - DBG3("Agent adding context %s:%s", ctx->u.app_ctx.provider_name, - ctx->u.app_ctx.ctx_name); - cds_list_add_tail_rcu(&agent_ctx->list_node, &agt->app_ctx_list); -end: - return ret; -} - -/* - * Find multiple agent events sharing the given name. - * - * RCU read side lock MUST be acquired. It must be held for the - * duration of the iteration. - * - * Sets the given iterator. - */ -void agent_find_events_by_name(const char *name, struct agent *agt, - struct lttng_ht_iter* iter) -{ - struct lttng_ht *ht; - struct agent_ht_key key; - - LTTNG_ASSERT(name); - LTTNG_ASSERT(agt); - LTTNG_ASSERT(agt->events); - LTTNG_ASSERT(iter); - - ht = agt->events; - key.name = name; - - cds_lfht_lookup(ht->ht, ht->hash_fct((void *) name, lttng_ht_seed), - ht_match_event_by_name, &key, &iter->iter); -} - -/* - * Find the agent event matching a trigger. - * - * RCU read side lock MUST be acquired. It must be held 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 domain; - const struct lttng_condition *condition; - const struct lttng_event_rule *rule; - const char *name; - const char *filter_expression; - const struct lttng_log_level_rule *log_level_rule; - /* Unused when loglevel_type is 'ALL'. */ - int loglevel_value = 0; - enum lttng_loglevel_type loglevel_type; - event_rule_logging_get_name_pattern logging_get_name_pattern; - event_rule_logging_get_log_level_rule logging_get_log_level_rule; - - LTTNG_ASSERT(agt); - LTTNG_ASSERT(agt->events); - - condition = lttng_trigger_get_const_condition(trigger); - - LTTNG_ASSERT(lttng_condition_get_type(condition) == - LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); - - c_status = lttng_condition_event_rule_matches_get_rule( - condition, &rule); - LTTNG_ASSERT(c_status == LTTNG_CONDITION_STATUS_OK); - - switch (lttng_event_rule_get_type(rule)) { - case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING: - logging_get_name_pattern = - lttng_event_rule_jul_logging_get_name_pattern; - logging_get_log_level_rule = - lttng_event_rule_jul_logging_get_log_level_rule; - break; - case LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING: - logging_get_name_pattern = - lttng_event_rule_log4j_logging_get_name_pattern; - logging_get_log_level_rule = - lttng_event_rule_log4j_logging_get_log_level_rule; - break; - case LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING: - logging_get_name_pattern = - lttng_event_rule_python_logging_get_name_pattern; - logging_get_log_level_rule = - lttng_event_rule_python_logging_get_log_level_rule; - break; - default: - abort(); - break; - } - - domain = lttng_event_rule_get_domain_type(rule); - LTTNG_ASSERT(domain == LTTNG_DOMAIN_JUL || domain == LTTNG_DOMAIN_LOG4J || - domain == LTTNG_DOMAIN_PYTHON); - - /* Get the event's pattern name ('name' in the legacy terminology). */ - er_status = logging_get_name_pattern(rule, &name); - LTTNG_ASSERT(er_status == LTTNG_EVENT_RULE_STATUS_OK); - - /* Get the internal filter expression. */ - filter_expression = lttng_event_rule_get_filter(rule); - - /* Map log_level_rule to loglevel value. */ - er_status = logging_get_log_level_rule(rule, &log_level_rule); - if (er_status == LTTNG_EVENT_RULE_STATUS_UNSET) { - loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; - loglevel_value = 0; - } else if (er_status == LTTNG_EVENT_RULE_STATUS_OK) { - lttng_log_level_rule_to_loglevel(log_level_rule, &loglevel_type, &loglevel_value); - } else { - abort(); - } - - 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. - * - * The RCU read lock must be held during the iteration and for as long - * as the object the iterator points to remains in use. - */ -void agent_event_next_duplicate(const char *name, - struct agent *agt, struct lttng_ht_iter* iter) -{ - struct agent_ht_key key; - - key.name = name; - - cds_lfht_next_duplicate(agt->events->ht, ht_match_event_by_name, - &key, &iter->iter); -} - -/* - * Find a agent event in the given agent using name, loglevel and filter. - * - * 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(const char *name, - 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; - struct lttng_ht *ht; - struct agent_ht_key key; - - LTTNG_ASSERT(name); - LTTNG_ASSERT(agt); - LTTNG_ASSERT(agt->events); - - ht = agt->events; - key.name = name; - key.loglevel_value = loglevel_value; - key.loglevel_type = loglevel_type; - key.filter_expression = filter_expression; - - cds_lfht_lookup(ht->ht, ht->hash_fct((void *) name, lttng_ht_seed), - ht_match_event, &key, &iter.iter); - node = lttng_ht_iter_get_node_str(&iter); - if (node == NULL) { - goto error; - } - - DBG3("Agent event found %s.", name); - return caa_container_of(node, struct agent_event, node); - -error: - DBG3("Agent event NOT found %s.", name); - return NULL; -} - -/* - * Free given agent event. This event must not be globally visible at this - * point (only expected to be used on failure just after event creation). After - * this call, the pointer is not usable anymore. - */ -void agent_destroy_event(struct agent_event *event) -{ - LTTNG_ASSERT(event); - - free(event->filter); - free(event->filter_expression); - free(event->exclusion); - free(event); -} - -static -void destroy_app_ctx_rcu(struct rcu_head *head) -{ - struct agent_app_ctx *ctx = - caa_container_of(head, struct agent_app_ctx, rcu_node); - - destroy_app_ctx(ctx); -} - -/* - * Destroy an agent completely. - */ -void agent_destroy(struct agent *agt) -{ - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - struct agent_app_ctx *ctx; - - LTTNG_ASSERT(agt); - - DBG3("Agent destroy"); - - rcu_read_lock(); - cds_lfht_for_each_entry(agt->events->ht, &iter.iter, node, node) { - int ret; - struct agent_event *event; - - /* - * When destroying an event, we have to try to disable it on the - * agent side so the event stops generating data. The return - * value is not important since we have to continue anyway - * destroying the object. - */ - event = caa_container_of(node, struct agent_event, node); - (void) agent_disable_event(event, agt->domain); - - ret = lttng_ht_del(agt->events, &iter); - LTTNG_ASSERT(!ret); - call_rcu(&node->head, destroy_event_agent_rcu); - } - - cds_list_for_each_entry_rcu(ctx, &agt->app_ctx_list, list_node) { - (void) disable_context(ctx, agt->domain); - cds_list_del(&ctx->list_node); - call_rcu(&ctx->rcu_node, destroy_app_ctx_rcu); - } - rcu_read_unlock(); - ht_cleanup_push(agt->events); - free(agt); -} - -/* - * Allocate agent_apps_ht_by_sock. - */ -int agent_app_ht_alloc(void) -{ - the_agent_apps_ht_by_sock = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); - return the_agent_apps_ht_by_sock ? 0 : -1; -} - -/* - * Destroy a agent application by socket. - */ -void agent_destroy_app_by_sock(int sock) -{ - struct agent_app *app; - - LTTNG_ASSERT(sock >= 0); - - /* - * Not finding an application is a very important error that should NEVER - * happen. The hash table deletion is ONLY done through this call when the - * main sessiond thread is torn down. - */ - rcu_read_lock(); - app = agent_find_app_by_sock(sock); - LTTNG_ASSERT(app); - - /* RCU read side lock is assumed to be held by this function. */ - agent_delete_app(app); - - /* The application is freed in a RCU call but the socket is closed here. */ - agent_destroy_app(app); - rcu_read_unlock(); -} - -/* - * Clean-up the agent app hash table and destroy it. - */ -void agent_app_ht_clean(void) -{ - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter iter; - - if (!the_agent_apps_ht_by_sock) { - return; - } - rcu_read_lock(); - cds_lfht_for_each_entry( - the_agent_apps_ht_by_sock->ht, &iter.iter, node, node) { - struct agent_app *app; - - app = caa_container_of(node, struct agent_app, node); - agent_destroy_app_by_sock(app->sock->fd); - } - rcu_read_unlock(); - - lttng_ht_destroy(the_agent_apps_ht_by_sock); -} - -/* - * Update a agent application (given socket) using the given agent. - * - * Note that this function is most likely to be used with a tracing session - * thus the caller should make sure to hold the appropriate lock(s). - */ -void agent_update(const struct agent *agt, const struct agent_app *app) -{ - int ret; - struct agent_event *event; - struct lttng_ht_iter iter; - struct agent_app_ctx *ctx; - - LTTNG_ASSERT(agt); - LTTNG_ASSERT(app); - - DBG("Agent updating app: pid = %ld", (long) app->pid); - - rcu_read_lock(); - /* - * We are in the registration path thus if the application is gone, - * there is a serious code flow error. - */ - - cds_lfht_for_each_entry(agt->events->ht, &iter.iter, event, node.node) { - /* Skip event if disabled. */ - if (!AGENT_EVENT_IS_ENABLED(event)) { - continue; - } - - ret = enable_event(app, event); - if (ret != LTTNG_OK) { - DBG2("Agent update unable to enable event %s on app pid: %d sock %d", - event->name, app->pid, app->sock->fd); - /* Let's try the others here and don't assume the app is dead. */ - continue; - } - } - - cds_list_for_each_entry_rcu(ctx, &agt->app_ctx_list, list_node) { - ret = app_context_op(app, ctx, AGENT_CMD_APP_CTX_ENABLE); - if (ret != LTTNG_OK) { - DBG2("Agent update unable to add application context %s:%s on app pid: %d sock %d", - ctx->provider_name, ctx->ctx_name, - app->pid, app->sock->fd); - continue; - } - } - - rcu_read_unlock(); -} - -/* - * Allocate the per-event notifier domain agent hash table. It is lazily - * populated as domains are used. - */ -int agent_by_event_notifier_domain_ht_create(void) -{ - the_trigger_agents_ht_by_domain = lttng_ht_new(0, LTTNG_HT_TYPE_U64); - return the_trigger_agents_ht_by_domain ? 0 : -1; -} - -/* - * Clean-up the per-event notifier domain agent hash table and destroy it. - */ -void agent_by_event_notifier_domain_ht_destroy(void) -{ - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - - if (!the_trigger_agents_ht_by_domain) { - return; - } - - rcu_read_lock(); - cds_lfht_for_each_entry(the_trigger_agents_ht_by_domain->ht, - &iter.iter, node, node) { - struct agent *agent = - caa_container_of(node, struct agent, node); - const int ret = lttng_ht_del( - the_trigger_agents_ht_by_domain, &iter); - - LTTNG_ASSERT(ret == 0); - agent_destroy(agent); - } - - rcu_read_unlock(); - lttng_ht_destroy(the_trigger_agents_ht_by_domain); -} - -struct agent *agent_find_by_event_notifier_domain( - enum lttng_domain_type domain_type) -{ - struct agent *agt = NULL; - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - const uint64_t key = (uint64_t) domain_type; - - LTTNG_ASSERT(the_trigger_agents_ht_by_domain); - - DBG3("Per-event notifier domain agent lookup for domain '%s'", - lttng_domain_type_str(domain_type)); - - lttng_ht_lookup(the_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.cpp b/src/bin/lttng-sessiond/agent.cpp new file mode 100644 index 000000000..5be61dc36 --- /dev/null +++ b/src/bin/lttng-sessiond/agent.cpp @@ -0,0 +1,1627 @@ +/* + * Copyright (C) 2013 David Goulet + * Copyright (C) 2016 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "agent.h" +#include "ust-app.h" +#include "utils.h" +#include "common/error.h" + +typedef enum lttng_event_rule_status (*event_rule_logging_get_name_pattern)( + const struct lttng_event_rule *rule, const char **pattern); +typedef enum lttng_event_rule_status (*event_rule_logging_get_log_level_rule)( + const struct lttng_event_rule *rule, + const struct lttng_log_level_rule **log_level_rule); + +/* + * Agent application context representation. + */ +struct agent_app_ctx { + char *provider_name; + char *ctx_name; + + /* agent_app_ctx are part of the agent app_ctx_list. */ + struct cds_list_head list_node; + + /* For call_rcu teardown. */ + struct rcu_head rcu_node; +}; + +/* + * Human readable agent return code. + */ +static +const char *lttcomm_agent_ret_code_str(lttcomm_agent_ret_code code) +{ + switch (code) { + case AGENT_RET_CODE_SUCCESS: + return "Success"; + case AGENT_RET_CODE_INVALID: + return "Invalid command"; + case AGENT_RET_CODE_UNKNOWN_NAME: + return "Unknown logger name"; + default: + return "Unknown code"; + } +}; + +static +void log_reply_code(uint32_t in_reply_ret_code) +{ + int level = PRINT_DBG3; + /* + * reply_ret_code and in_reply_ret_code are kept separate to have a + * sanitized value (used to retrieve the human readable string) and the + * original value which is logged as-is. + */ + uint32_t reply_ret_code = in_reply_ret_code; + + if (reply_ret_code < AGENT_RET_CODE_SUCCESS || + reply_ret_code >= AGENT_RET_CODE_NR) { + reply_ret_code = AGENT_RET_CODE_NR; + level = PRINT_ERR; + } + + LOG(level, "Agent replied with retcode: %s (%" PRIu32 ")", + lttcomm_agent_ret_code_str((lttcomm_agent_ret_code) reply_ret_code), + in_reply_ret_code); +} + +/* + * Match function for the events hash table lookup by name. + */ +static int ht_match_event_by_name(struct cds_lfht_node *node, + const void *_key) +{ + struct agent_event *event; + const struct agent_ht_key *key; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + event = caa_container_of(node, struct agent_event, node.node); + key = (agent_ht_key *) _key; + + /* Match 1 elements of the key: name. */ + + /* Event name */ + if (strncmp(event->name, key->name, sizeof(event->name)) != 0) { + goto no_match; + } + /* Match. */ + return 1; + +no_match: + return 0; +} + +/* + * Match function for the events hash table lookup by name, log level and + * filter expression. + */ +static int ht_match_event(struct cds_lfht_node *node, + const void *_key) +{ + struct agent_event *event; + const struct agent_ht_key *key; + int ll_match; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + event = caa_container_of(node, struct agent_event, node.node); + key = (agent_ht_key *) _key; + + /* Match 2 elements of the key: name and loglevel. */ + + /* Event name */ + if (strncmp(event->name, key->name, sizeof(event->name)) != 0) { + goto no_match; + } + + /* Event loglevel value and type. */ + ll_match = loglevels_match(event->loglevel_type, + event->loglevel_value, key->loglevel_type, + key->loglevel_value, LTTNG_EVENT_LOGLEVEL_ALL); + + if (!ll_match) { + goto no_match; + } + + /* Filter expression */ + if (!!event->filter_expression != !!key->filter_expression) { + /* One has a filter expression, the other does not */ + goto no_match; + } + + if (event->filter_expression) { + if (strncmp(event->filter_expression, key->filter_expression, + strlen(event->filter_expression)) != 0) { + goto no_match; + } + } + + return 1; + +no_match: + return 0; +} + +/* + * Add unique agent event based on the event name and loglevel. + */ +static void add_unique_agent_event(struct lttng_ht *ht, + struct agent_event *event) +{ + struct cds_lfht_node *node_ptr; + struct agent_ht_key key; + + LTTNG_ASSERT(ht); + LTTNG_ASSERT(ht->ht); + LTTNG_ASSERT(event); + + key.name = event->name; + key.loglevel_value = event->loglevel_value; + key.loglevel_type = event->loglevel_type; + key.filter_expression = event->filter_expression; + + node_ptr = cds_lfht_add_unique(ht->ht, + ht->hash_fct(event->node.key, lttng_ht_seed), + ht_match_event, &key, &event->node.node); + LTTNG_ASSERT(node_ptr == &event->node.node); +} + +/* + * URCU delayed agent event reclaim. + */ +static void destroy_event_agent_rcu(struct rcu_head *head) +{ + struct lttng_ht_node_str *node = + caa_container_of(head, struct lttng_ht_node_str, head); + struct agent_event *event = + caa_container_of(node, struct agent_event, node); + + agent_destroy_event(event); +} + +/* + * URCU delayed agent app reclaim. + */ +static void destroy_app_agent_rcu(struct rcu_head *head) +{ + struct lttng_ht_node_ulong *node = + caa_container_of(head, struct lttng_ht_node_ulong, head); + struct agent_app *app = + caa_container_of(node, struct agent_app, node); + + free(app); +} + +/* + * Communication with the agent. Send the message header to the given socket in + * big endian. + * + * Return 0 on success or else a negative errno message of sendmsg() op. + */ +static int send_header(struct lttcomm_sock *sock, uint64_t data_size, + uint32_t cmd, uint32_t cmd_version) +{ + int ret; + ssize_t size; + struct lttcomm_agent_hdr msg; + + LTTNG_ASSERT(sock); + + memset(&msg, 0, sizeof(msg)); + msg.data_size = htobe64(data_size); + msg.cmd = htobe32(cmd); + msg.cmd_version = htobe32(cmd_version); + + size = sock->ops->sendmsg(sock, &msg, sizeof(msg), 0); + if (size < sizeof(msg)) { + ret = -errno; + goto error; + } + ret = 0; + +error: + return ret; +} + +/* + * Communication call with the agent. Send the payload to the given socket. The + * header MUST be sent prior to this call. + * + * Return 0 on success or else a negative errno value of sendmsg() op. + */ +static int send_payload(struct lttcomm_sock *sock, const void *data, + size_t size) +{ + int ret; + ssize_t len; + + LTTNG_ASSERT(sock); + LTTNG_ASSERT(data); + + len = sock->ops->sendmsg(sock, data, size, 0); + if (len < size) { + ret = -errno; + goto error; + } + ret = 0; + +error: + return ret; +} + +/* + * Communication call with the agent. Receive reply from the agent using the + * given socket. + * + * Return 0 on success or else a negative errno value from recvmsg() op. + */ +static int recv_reply(struct lttcomm_sock *sock, void *buf, size_t size) +{ + int ret; + ssize_t len; + + LTTNG_ASSERT(sock); + LTTNG_ASSERT(buf); + + len = sock->ops->recvmsg(sock, buf, size, 0); + if (len < size) { + ret = -errno; + goto error; + } + ret = 0; + +error: + return ret; +} + +/* + * Internal event listing for a given app. Populate events. + * + * Return number of element in the list or else a negative LTTNG_ERR* code. + * On success, the caller is responsible for freeing the memory + * allocated for "events". + */ +static ssize_t list_events(struct agent_app *app, struct lttng_event **events) +{ + int ret, i, len = 0, offset = 0; + uint32_t nb_event; + size_t data_size; + uint32_t reply_ret_code; + struct lttng_event *tmp_events = NULL; + struct lttcomm_agent_list_reply *reply = NULL; + struct lttcomm_agent_list_reply_hdr reply_hdr; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(app->sock); + LTTNG_ASSERT(events); + + DBG2("Agent listing events for app pid: %d and socket %d", app->pid, + app->sock->fd); + + ret = send_header(app->sock, 0, AGENT_CMD_LIST, 0); + if (ret < 0) { + goto error_io; + } + + /* Get list header so we know how much we'll receive. */ + ret = recv_reply(app->sock, &reply_hdr, sizeof(reply_hdr)); + if (ret < 0) { + goto error_io; + } + + reply_ret_code = be32toh(reply_hdr.ret_code); + log_reply_code(reply_ret_code); + switch (reply_ret_code) { + case AGENT_RET_CODE_SUCCESS: + data_size = be32toh(reply_hdr.data_size) + sizeof(*reply); + break; + default: + ret = LTTNG_ERR_UNK; + goto error; + } + + reply = (lttcomm_agent_list_reply *) zmalloc(data_size); + if (!reply) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + + /* Get the list with the appropriate data size. */ + ret = recv_reply(app->sock, reply, data_size); + if (ret < 0) { + goto error_io; + } + + nb_event = be32toh(reply->nb_event); + tmp_events = (lttng_event *) zmalloc(sizeof(*tmp_events) * nb_event); + if (!tmp_events) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + + for (i = 0; i < nb_event; i++) { + offset += len; + if (lttng_strncpy(tmp_events[i].name, reply->payload + offset, + sizeof(tmp_events[i].name))) { + ret = LTTNG_ERR_INVALID; + goto error; + } + tmp_events[i].pid = app->pid; + tmp_events[i].enabled = -1; + len = strlen(reply->payload + offset) + 1; + } + + *events = tmp_events; + + free(reply); + return nb_event; + +error_io: + ret = LTTNG_ERR_UST_LIST_FAIL; +error: + free(reply); + free(tmp_events); + return -ret; + +} + +/* + * Internal enable agent event on a agent application. This function + * communicates with the agent to enable a given event. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +static int enable_event(const struct agent_app *app, struct agent_event *event) +{ + int ret; + char *bytes_to_send; + uint64_t data_size; + size_t filter_expression_length; + uint32_t reply_ret_code; + struct lttcomm_agent_enable_event msg; + struct lttcomm_agent_generic_reply reply; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(app->sock); + LTTNG_ASSERT(event); + + DBG2("Agent enabling event %s for app pid: %d and socket %d", event->name, + app->pid, app->sock->fd); + + /* + * Calculate the payload's size, which is the fixed-size struct followed + * by the variable-length filter expression (+1 for the ending \0). + */ + if (!event->filter_expression) { + filter_expression_length = 0; + } else { + filter_expression_length = strlen(event->filter_expression) + 1; + } + data_size = sizeof(msg) + filter_expression_length; + + memset(&msg, 0, sizeof(msg)); + msg.loglevel_value = htobe32(event->loglevel_value); + msg.loglevel_type = htobe32(event->loglevel_type); + if (lttng_strncpy(msg.name, event->name, sizeof(msg.name))) { + ret = LTTNG_ERR_INVALID; + goto error; + } + msg.filter_expression_length = htobe32(filter_expression_length); + + ret = send_header(app->sock, data_size, AGENT_CMD_ENABLE, 0); + if (ret < 0) { + goto error_io; + } + + bytes_to_send = (char *) zmalloc(data_size); + if (!bytes_to_send) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + + memcpy(bytes_to_send, &msg, sizeof(msg)); + if (filter_expression_length > 0) { + memcpy(bytes_to_send + sizeof(msg), event->filter_expression, + filter_expression_length); + } + + ret = send_payload(app->sock, bytes_to_send, data_size); + free(bytes_to_send); + if (ret < 0) { + goto error_io; + } + + ret = recv_reply(app->sock, &reply, sizeof(reply)); + if (ret < 0) { + goto error_io; + } + + reply_ret_code = be32toh(reply.ret_code); + log_reply_code(reply_ret_code); + switch (reply_ret_code) { + case AGENT_RET_CODE_SUCCESS: + break; + case AGENT_RET_CODE_UNKNOWN_NAME: + ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; + goto error; + default: + ret = LTTNG_ERR_UNK; + goto error; + } + + return LTTNG_OK; + +error_io: + ret = LTTNG_ERR_UST_ENABLE_FAIL; +error: + return ret; +} + +/* + * Send Pascal-style string. Size is sent as a 32-bit big endian integer. + */ +static +int send_pstring(struct lttcomm_sock *sock, const char *str, uint32_t len) +{ + int ret; + uint32_t len_be; + + len_be = htobe32(len); + ret = send_payload(sock, &len_be, sizeof(len_be)); + if (ret) { + goto end; + } + + ret = send_payload(sock, str, len); + if (ret) { + goto end; + } +end: + return ret; +} + +/* + * Internal enable application context on an agent application. This function + * communicates with the agent to enable a given application context. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +static int app_context_op(const struct agent_app *app, + const struct agent_app_ctx *ctx, enum lttcomm_agent_command cmd) +{ + int ret; + uint32_t reply_ret_code; + struct lttcomm_agent_generic_reply reply; + size_t app_ctx_provider_name_len, app_ctx_name_len, data_size; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(app->sock); + LTTNG_ASSERT(ctx); + LTTNG_ASSERT(cmd == AGENT_CMD_APP_CTX_ENABLE || + cmd == AGENT_CMD_APP_CTX_DISABLE); + + DBG2("Agent %s application %s:%s for app pid: %d and socket %d", + cmd == AGENT_CMD_APP_CTX_ENABLE ? "enabling" : "disabling", + ctx->provider_name, ctx->ctx_name, + app->pid, app->sock->fd); + + /* + * Calculate the payload's size, which consists of the size (u32, BE) + * of the provider name, the NULL-terminated provider name string, the + * size (u32, BE) of the context name, followed by the NULL-terminated + * context name string. + */ + app_ctx_provider_name_len = strlen(ctx->provider_name) + 1; + app_ctx_name_len = strlen(ctx->ctx_name) + 1; + data_size = sizeof(uint32_t) + app_ctx_provider_name_len + + sizeof(uint32_t) + app_ctx_name_len; + + ret = send_header(app->sock, data_size, cmd, 0); + if (ret < 0) { + goto error_io; + } + + if (app_ctx_provider_name_len > UINT32_MAX || + app_ctx_name_len > UINT32_MAX) { + ERR("Application context name > MAX_UINT32"); + ret = LTTNG_ERR_INVALID; + goto error; + } + + ret = send_pstring(app->sock, ctx->provider_name, + (uint32_t) app_ctx_provider_name_len); + if (ret < 0) { + goto error_io; + } + + ret = send_pstring(app->sock, ctx->ctx_name, + (uint32_t) app_ctx_name_len); + if (ret < 0) { + goto error_io; + } + + ret = recv_reply(app->sock, &reply, sizeof(reply)); + if (ret < 0) { + goto error_io; + } + + reply_ret_code = be32toh(reply.ret_code); + log_reply_code(reply_ret_code); + switch (reply_ret_code) { + case AGENT_RET_CODE_SUCCESS: + break; + default: + ret = LTTNG_ERR_UNK; + goto error; + } + + return LTTNG_OK; + +error_io: + ret = LTTNG_ERR_UST_ENABLE_FAIL; +error: + return ret; +} + +/* + * Internal disable agent event call on a agent application. This function + * communicates with the agent to disable a given event. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +static int disable_event(struct agent_app *app, struct agent_event *event) +{ + int ret; + uint64_t data_size; + uint32_t reply_ret_code; + struct lttcomm_agent_disable_event msg; + struct lttcomm_agent_generic_reply reply; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(app->sock); + LTTNG_ASSERT(event); + + DBG2("Agent disabling event %s for app pid: %d and socket %d", event->name, + app->pid, app->sock->fd); + + data_size = sizeof(msg); + memset(&msg, 0, sizeof(msg)); + if (lttng_strncpy(msg.name, event->name, sizeof(msg.name))) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + ret = send_header(app->sock, data_size, AGENT_CMD_DISABLE, 0); + if (ret < 0) { + goto error_io; + } + + ret = send_payload(app->sock, &msg, sizeof(msg)); + if (ret < 0) { + goto error_io; + } + + ret = recv_reply(app->sock, &reply, sizeof(reply)); + if (ret < 0) { + goto error_io; + } + + reply_ret_code = be32toh(reply.ret_code); + log_reply_code(reply_ret_code); + switch (reply_ret_code) { + case AGENT_RET_CODE_SUCCESS: + break; + case AGENT_RET_CODE_UNKNOWN_NAME: + ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; + goto error; + default: + ret = LTTNG_ERR_UNK; + goto error; + } + + return LTTNG_OK; + +error_io: + ret = LTTNG_ERR_UST_DISABLE_FAIL; +error: + return ret; +} + +/* + * Send back the registration DONE command to a given agent application. + * + * Return 0 on success or else a negative value. + */ +int agent_send_registration_done(struct agent_app *app) +{ + LTTNG_ASSERT(app); + LTTNG_ASSERT(app->sock); + + DBG("Agent sending registration done to app socket %d", app->sock->fd); + + return send_header(app->sock, 0, AGENT_CMD_REG_DONE, 0); +} + +/* + * Enable agent event on every agent applications registered with the session + * daemon. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int agent_enable_event(struct agent_event *event, + enum lttng_domain_type domain) +{ + int ret; + struct agent_app *app; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(event); + + rcu_read_lock(); + + cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, + node.node) { + if (app->domain != domain) { + continue; + } + + /* Enable event on agent application through TCP socket. */ + ret = enable_event(app, event); + if (ret != LTTNG_OK) { + goto error; + } + } + + event->enabled_count++; + ret = LTTNG_OK; + +error: + rcu_read_unlock(); + return ret; +} + +static +void destroy_app_ctx(struct agent_app_ctx *ctx) +{ + free(ctx->provider_name); + free(ctx->ctx_name); + free(ctx); +} + +static +struct agent_app_ctx *create_app_ctx(const struct lttng_event_context *ctx) +{ + struct agent_app_ctx *agent_ctx = NULL; + + if (!ctx) { + goto end; + } + + LTTNG_ASSERT(ctx->ctx == LTTNG_EVENT_CONTEXT_APP_CONTEXT); + agent_ctx = (agent_app_ctx *) zmalloc(sizeof(*ctx)); + if (!agent_ctx) { + goto end; + } + + agent_ctx->provider_name = strdup(ctx->u.app_ctx.provider_name); + agent_ctx->ctx_name = strdup(ctx->u.app_ctx.ctx_name); + if (!agent_ctx->provider_name || !agent_ctx->ctx_name) { + destroy_app_ctx(agent_ctx); + agent_ctx = NULL; + } +end: + return agent_ctx; +} + +/* + * Enable agent context on every agent applications registered with the session + * daemon. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int agent_enable_context(const struct lttng_event_context *ctx, + enum lttng_domain_type domain) +{ + int ret; + struct agent_app *app; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(ctx); + if (ctx->ctx != LTTNG_EVENT_CONTEXT_APP_CONTEXT) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + rcu_read_lock(); + + cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, + node.node) { + struct agent_app_ctx *agent_ctx; + + if (app->domain != domain) { + continue; + } + + agent_ctx = create_app_ctx(ctx); + if (!agent_ctx) { + ret = LTTNG_ERR_NOMEM; + goto error_unlock; + } + + /* Enable event on agent application through TCP socket. */ + ret = app_context_op(app, agent_ctx, AGENT_CMD_APP_CTX_ENABLE); + destroy_app_ctx(agent_ctx); + if (ret != LTTNG_OK) { + goto error_unlock; + } + } + + ret = LTTNG_OK; + +error_unlock: + rcu_read_unlock(); +error: + return ret; +} + +/* + * Disable agent event on every agent application registered with the session + * daemon. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int agent_disable_event(struct agent_event *event, + enum lttng_domain_type domain) +{ + int ret = LTTNG_OK; + struct agent_app *app; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(event); + if (!AGENT_EVENT_IS_ENABLED(event)) { + goto end; + } + + if (--event->enabled_count != 0) { + /* + * Agent event still enabled. Disable the agent event only when + * all "users" have disabled it (event notifiers, event rules, + * etc.). + */ + ret = LTTNG_OK; + goto end; + } + + rcu_read_lock(); + + cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, + node.node) { + if (app->domain != domain) { + continue; + } + + /* Enable event on agent application through TCP socket. */ + ret = disable_event(app, event); + if (ret != LTTNG_OK) { + goto error; + } + } + + /* event->enabled_count is now 0. */ + LTTNG_ASSERT(!AGENT_EVENT_IS_ENABLED(event)); + +error: + rcu_read_unlock(); +end: + return ret; +} + +/* + * Disable agent context on every agent application registered with the session + * daemon. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +static int disable_context(struct agent_app_ctx *ctx, + enum lttng_domain_type domain) +{ + int ret = LTTNG_OK; + struct agent_app *app; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(ctx); + + rcu_read_lock(); + DBG2("Disabling agent application context %s:%s", + ctx->provider_name, ctx->ctx_name); + cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, + node.node) { + if (app->domain != domain) { + continue; + } + + ret = app_context_op(app, ctx, AGENT_CMD_APP_CTX_DISABLE); + if (ret != LTTNG_OK) { + goto end; + } + } +end: + rcu_read_unlock(); + return ret; +} + +/* + * Ask every agent for the list of possible event. Events is allocated with the + * events of every agent application. + * + * Return the number of events or else a negative value. + */ +int agent_list_events(struct lttng_event **events, + enum lttng_domain_type domain) +{ + int ret; + size_t nbmem, count = 0; + struct agent_app *app; + struct lttng_event *tmp_events = NULL; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(events); + + DBG2("Agent listing events for domain %d", domain); + + nbmem = UST_APP_EVENT_LIST_SIZE; + tmp_events = (lttng_event *) zmalloc(nbmem * sizeof(*tmp_events)); + if (!tmp_events) { + PERROR("zmalloc agent list events"); + ret = -ENOMEM; + goto error; + } + + rcu_read_lock(); + cds_lfht_for_each_entry(the_agent_apps_ht_by_sock->ht, &iter.iter, app, + node.node) { + ssize_t nb_ev; + struct lttng_event *agent_events; + + /* Skip domain not asked by the list. */ + if (app->domain != domain) { + continue; + } + + nb_ev = list_events(app, &agent_events); + if (nb_ev < 0) { + ret = nb_ev; + goto error_unlock; + } + + if (count + nb_ev > nbmem) { + /* In case the realloc fails, we free the memory */ + struct lttng_event *new_tmp_events; + size_t new_nbmem; + + new_nbmem = std::max(count + nb_ev, nbmem << 1); + DBG2("Reallocating agent event list from %zu to %zu entries", + nbmem, new_nbmem); + new_tmp_events = (lttng_event *) realloc(tmp_events, + new_nbmem * sizeof(*new_tmp_events)); + if (!new_tmp_events) { + PERROR("realloc agent events"); + ret = -ENOMEM; + free(agent_events); + goto error_unlock; + } + /* Zero the new memory */ + memset(new_tmp_events + nbmem, 0, + (new_nbmem - nbmem) * sizeof(*new_tmp_events)); + nbmem = new_nbmem; + tmp_events = new_tmp_events; + } + memcpy(tmp_events + count, agent_events, + nb_ev * sizeof(*tmp_events)); + free(agent_events); + count += nb_ev; + } + rcu_read_unlock(); + + ret = count; + *events = tmp_events; + return ret; + +error_unlock: + rcu_read_unlock(); +error: + free(tmp_events); + return ret; +} + +/* + * Create a agent app object using the given PID. + * + * Return newly allocated object or else NULL on error. + */ +struct agent_app *agent_create_app(pid_t pid, enum lttng_domain_type domain, + struct lttcomm_sock *sock) +{ + struct agent_app *app; + + LTTNG_ASSERT(sock); + + app = (agent_app *) zmalloc(sizeof(*app)); + if (!app) { + PERROR("Failed to allocate agent application instance"); + goto error; + } + + app->pid = pid; + app->domain = domain; + app->sock = sock; + lttng_ht_node_init_ulong(&app->node, (unsigned long) app->sock->fd); + +error: + return app; +} + +/* + * Lookup agent app by socket in the global hash table. + * + * RCU read side lock MUST be acquired. + * + * Return object if found else NULL. + */ +struct agent_app *agent_find_app_by_sock(int sock) +{ + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter iter; + struct agent_app *app; + + LTTNG_ASSERT(sock >= 0); + + lttng_ht_lookup(the_agent_apps_ht_by_sock, + (void *) ((unsigned long) sock), &iter); + node = lttng_ht_iter_get_node_ulong(&iter); + if (node == NULL) { + goto error; + } + app = caa_container_of(node, struct agent_app, node); + + DBG3("Agent app pid %d found by sock %d.", app->pid, sock); + return app; + +error: + DBG3("Agent app NOT found by sock %d.", sock); + return NULL; +} + +/* + * Add agent application object to the global hash table. + */ +void agent_add_app(struct agent_app *app) +{ + LTTNG_ASSERT(app); + + DBG3("Agent adding app sock: %d and pid: %d to ht", app->sock->fd, app->pid); + lttng_ht_add_unique_ulong(the_agent_apps_ht_by_sock, &app->node); +} + +/* + * Delete agent application from the global hash table. + * + * rcu_read_lock() must be held by the caller. + */ +void agent_delete_app(struct agent_app *app) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(app); + + DBG3("Agent deleting app pid: %d and sock: %d", app->pid, app->sock->fd); + + iter.iter.node = &app->node.node; + ret = lttng_ht_del(the_agent_apps_ht_by_sock, &iter); + LTTNG_ASSERT(!ret); +} + +/* + * Destroy an agent application object by detaching it from its corresponding + * UST app if one is connected by closing the socket. Finally, perform a + * delayed memory reclaim. + */ +void agent_destroy_app(struct agent_app *app) +{ + LTTNG_ASSERT(app); + + if (app->sock) { + app->sock->ops->close(app->sock); + lttcomm_destroy_sock(app->sock); + } + + call_rcu(&app->node.head, destroy_app_agent_rcu); +} + +/* + * Initialize an already allocated agent object. + * + * Return 0 on success or else a negative errno value. + */ +int agent_init(struct agent *agt) +{ + int ret; + + LTTNG_ASSERT(agt); + + agt->events = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); + if (!agt->events) { + ret = -ENOMEM; + goto error; + } + lttng_ht_node_init_u64(&agt->node, agt->domain); + + CDS_INIT_LIST_HEAD(&agt->app_ctx_list); + return 0; + +error: + return ret; +} + +/* + * Add agent object to the given hash table. + */ +void agent_add(struct agent *agt, struct lttng_ht *ht) +{ + LTTNG_ASSERT(agt); + LTTNG_ASSERT(ht); + + DBG3("Agent adding from domain %d", agt->domain); + + lttng_ht_add_unique_u64(ht, &agt->node); +} + +/* + * Create an agent object for the given domain. + * + * Return the allocated agent or NULL on error. + */ +struct agent *agent_create(enum lttng_domain_type domain) +{ + int ret; + struct agent *agt; + + agt = (agent *) zmalloc(sizeof(struct agent)); + if (!agt) { + goto error; + } + agt->domain = domain; + + ret = agent_init(agt); + if (ret < 0) { + free(agt); + agt = NULL; + goto error; + } + +error: + return agt; +} + +/* + * Create a newly allocated agent event data structure. + * Ownership of filter_expression is taken. + * + * Return a new object else NULL on error. + */ +struct agent_event *agent_create_event(const char *name, + enum lttng_loglevel_type loglevel_type, int loglevel_value, + struct lttng_bytecode *filter, char *filter_expression) +{ + struct agent_event *event = NULL; + + DBG3("Agent create new event with name %s, loglevel type %d, \ + loglevel value %d and filter %s", + name, loglevel_type, loglevel_value, + filter_expression ? filter_expression : "NULL"); + + if (!name) { + ERR("Failed to create agent event; no name provided."); + goto error; + } + + event = (agent_event *) zmalloc(sizeof(*event)); + if (!event) { + goto error; + } + + strncpy(event->name, name, sizeof(event->name)); + event->name[sizeof(event->name) - 1] = '\0'; + lttng_ht_node_init_str(&event->node, event->name); + + event->loglevel_value = loglevel_value; + event->loglevel_type = loglevel_type; + event->filter = filter; + event->filter_expression = filter_expression; +error: + return event; +} + +/* + * Unique add of a agent event to an agent object. + */ +void agent_add_event(struct agent_event *event, struct agent *agt) +{ + LTTNG_ASSERT(event); + LTTNG_ASSERT(agt); + LTTNG_ASSERT(agt->events); + + DBG3("Agent adding event %s", event->name); + add_unique_agent_event(agt->events, event); + agt->being_used = 1; +} + +/* + * Unique add of a agent context to an agent object. + */ +int agent_add_context(const struct lttng_event_context *ctx, struct agent *agt) +{ + int ret = LTTNG_OK; + struct agent_app_ctx *agent_ctx = NULL; + + LTTNG_ASSERT(ctx); + LTTNG_ASSERT(agt); + LTTNG_ASSERT(agt->events); + LTTNG_ASSERT(ctx->ctx == LTTNG_EVENT_CONTEXT_APP_CONTEXT); + + agent_ctx = create_app_ctx(ctx); + if (!agent_ctx) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + DBG3("Agent adding context %s:%s", ctx->u.app_ctx.provider_name, + ctx->u.app_ctx.ctx_name); + cds_list_add_tail_rcu(&agent_ctx->list_node, &agt->app_ctx_list); +end: + return ret; +} + +/* + * Find multiple agent events sharing the given name. + * + * RCU read side lock MUST be acquired. It must be held for the + * duration of the iteration. + * + * Sets the given iterator. + */ +void agent_find_events_by_name(const char *name, struct agent *agt, + struct lttng_ht_iter* iter) +{ + struct lttng_ht *ht; + struct agent_ht_key key; + + LTTNG_ASSERT(name); + LTTNG_ASSERT(agt); + LTTNG_ASSERT(agt->events); + LTTNG_ASSERT(iter); + + ht = agt->events; + key.name = name; + + cds_lfht_lookup(ht->ht, ht->hash_fct((void *) name, lttng_ht_seed), + ht_match_event_by_name, &key, &iter->iter); +} + +/* + * Find the agent event matching a trigger. + * + * RCU read side lock MUST be acquired. It must be held 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 domain; + const struct lttng_condition *condition; + const struct lttng_event_rule *rule; + const char *name; + const char *filter_expression; + const struct lttng_log_level_rule *log_level_rule; + /* Unused when loglevel_type is 'ALL'. */ + int loglevel_value = 0; + enum lttng_loglevel_type loglevel_type; + event_rule_logging_get_name_pattern logging_get_name_pattern; + event_rule_logging_get_log_level_rule logging_get_log_level_rule; + + LTTNG_ASSERT(agt); + LTTNG_ASSERT(agt->events); + + condition = lttng_trigger_get_const_condition(trigger); + + LTTNG_ASSERT(lttng_condition_get_type(condition) == + LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); + + c_status = lttng_condition_event_rule_matches_get_rule( + condition, &rule); + LTTNG_ASSERT(c_status == LTTNG_CONDITION_STATUS_OK); + + switch (lttng_event_rule_get_type(rule)) { + case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING: + logging_get_name_pattern = + lttng_event_rule_jul_logging_get_name_pattern; + logging_get_log_level_rule = + lttng_event_rule_jul_logging_get_log_level_rule; + break; + case LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING: + logging_get_name_pattern = + lttng_event_rule_log4j_logging_get_name_pattern; + logging_get_log_level_rule = + lttng_event_rule_log4j_logging_get_log_level_rule; + break; + case LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING: + logging_get_name_pattern = + lttng_event_rule_python_logging_get_name_pattern; + logging_get_log_level_rule = + lttng_event_rule_python_logging_get_log_level_rule; + break; + default: + abort(); + break; + } + + domain = lttng_event_rule_get_domain_type(rule); + LTTNG_ASSERT(domain == LTTNG_DOMAIN_JUL || domain == LTTNG_DOMAIN_LOG4J || + domain == LTTNG_DOMAIN_PYTHON); + + /* Get the event's pattern name ('name' in the legacy terminology). */ + er_status = logging_get_name_pattern(rule, &name); + LTTNG_ASSERT(er_status == LTTNG_EVENT_RULE_STATUS_OK); + + /* Get the internal filter expression. */ + filter_expression = lttng_event_rule_get_filter(rule); + + /* Map log_level_rule to loglevel value. */ + er_status = logging_get_log_level_rule(rule, &log_level_rule); + if (er_status == LTTNG_EVENT_RULE_STATUS_UNSET) { + loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; + loglevel_value = 0; + } else if (er_status == LTTNG_EVENT_RULE_STATUS_OK) { + lttng_log_level_rule_to_loglevel(log_level_rule, &loglevel_type, &loglevel_value); + } else { + abort(); + } + + 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. + * + * The RCU read lock must be held during the iteration and for as long + * as the object the iterator points to remains in use. + */ +void agent_event_next_duplicate(const char *name, + struct agent *agt, struct lttng_ht_iter* iter) +{ + struct agent_ht_key key; + + key.name = name; + + cds_lfht_next_duplicate(agt->events->ht, ht_match_event_by_name, + &key, &iter->iter); +} + +/* + * Find a agent event in the given agent using name, loglevel and filter. + * + * 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(const char *name, + 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; + struct lttng_ht *ht; + struct agent_ht_key key; + + LTTNG_ASSERT(name); + LTTNG_ASSERT(agt); + LTTNG_ASSERT(agt->events); + + ht = agt->events; + key.name = name; + key.loglevel_value = loglevel_value; + key.loglevel_type = loglevel_type; + key.filter_expression = filter_expression; + + cds_lfht_lookup(ht->ht, ht->hash_fct((void *) name, lttng_ht_seed), + ht_match_event, &key, &iter.iter); + node = lttng_ht_iter_get_node_str(&iter); + if (node == NULL) { + goto error; + } + + DBG3("Agent event found %s.", name); + return caa_container_of(node, struct agent_event, node); + +error: + DBG3("Agent event NOT found %s.", name); + return NULL; +} + +/* + * Free given agent event. This event must not be globally visible at this + * point (only expected to be used on failure just after event creation). After + * this call, the pointer is not usable anymore. + */ +void agent_destroy_event(struct agent_event *event) +{ + LTTNG_ASSERT(event); + + free(event->filter); + free(event->filter_expression); + free(event->exclusion); + free(event); +} + +static +void destroy_app_ctx_rcu(struct rcu_head *head) +{ + struct agent_app_ctx *ctx = + caa_container_of(head, struct agent_app_ctx, rcu_node); + + destroy_app_ctx(ctx); +} + +/* + * Destroy an agent completely. + */ +void agent_destroy(struct agent *agt) +{ + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + struct agent_app_ctx *ctx; + + LTTNG_ASSERT(agt); + + DBG3("Agent destroy"); + + rcu_read_lock(); + cds_lfht_for_each_entry(agt->events->ht, &iter.iter, node, node) { + int ret; + struct agent_event *event; + + /* + * When destroying an event, we have to try to disable it on the + * agent side so the event stops generating data. The return + * value is not important since we have to continue anyway + * destroying the object. + */ + event = caa_container_of(node, struct agent_event, node); + (void) agent_disable_event(event, agt->domain); + + ret = lttng_ht_del(agt->events, &iter); + LTTNG_ASSERT(!ret); + call_rcu(&node->head, destroy_event_agent_rcu); + } + + cds_list_for_each_entry_rcu(ctx, &agt->app_ctx_list, list_node) { + (void) disable_context(ctx, agt->domain); + cds_list_del(&ctx->list_node); + call_rcu(&ctx->rcu_node, destroy_app_ctx_rcu); + } + rcu_read_unlock(); + ht_cleanup_push(agt->events); + free(agt); +} + +/* + * Allocate agent_apps_ht_by_sock. + */ +int agent_app_ht_alloc(void) +{ + the_agent_apps_ht_by_sock = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); + return the_agent_apps_ht_by_sock ? 0 : -1; +} + +/* + * Destroy a agent application by socket. + */ +void agent_destroy_app_by_sock(int sock) +{ + struct agent_app *app; + + LTTNG_ASSERT(sock >= 0); + + /* + * Not finding an application is a very important error that should NEVER + * happen. The hash table deletion is ONLY done through this call when the + * main sessiond thread is torn down. + */ + rcu_read_lock(); + app = agent_find_app_by_sock(sock); + LTTNG_ASSERT(app); + + /* RCU read side lock is assumed to be held by this function. */ + agent_delete_app(app); + + /* The application is freed in a RCU call but the socket is closed here. */ + agent_destroy_app(app); + rcu_read_unlock(); +} + +/* + * Clean-up the agent app hash table and destroy it. + */ +void agent_app_ht_clean(void) +{ + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter iter; + + if (!the_agent_apps_ht_by_sock) { + return; + } + rcu_read_lock(); + cds_lfht_for_each_entry( + the_agent_apps_ht_by_sock->ht, &iter.iter, node, node) { + struct agent_app *app; + + app = caa_container_of(node, struct agent_app, node); + agent_destroy_app_by_sock(app->sock->fd); + } + rcu_read_unlock(); + + lttng_ht_destroy(the_agent_apps_ht_by_sock); +} + +/* + * Update a agent application (given socket) using the given agent. + * + * Note that this function is most likely to be used with a tracing session + * thus the caller should make sure to hold the appropriate lock(s). + */ +void agent_update(const struct agent *agt, const struct agent_app *app) +{ + int ret; + struct agent_event *event; + struct lttng_ht_iter iter; + struct agent_app_ctx *ctx; + + LTTNG_ASSERT(agt); + LTTNG_ASSERT(app); + + DBG("Agent updating app: pid = %ld", (long) app->pid); + + rcu_read_lock(); + /* + * We are in the registration path thus if the application is gone, + * there is a serious code flow error. + */ + + cds_lfht_for_each_entry(agt->events->ht, &iter.iter, event, node.node) { + /* Skip event if disabled. */ + if (!AGENT_EVENT_IS_ENABLED(event)) { + continue; + } + + ret = enable_event(app, event); + if (ret != LTTNG_OK) { + DBG2("Agent update unable to enable event %s on app pid: %d sock %d", + event->name, app->pid, app->sock->fd); + /* Let's try the others here and don't assume the app is dead. */ + continue; + } + } + + cds_list_for_each_entry_rcu(ctx, &agt->app_ctx_list, list_node) { + ret = app_context_op(app, ctx, AGENT_CMD_APP_CTX_ENABLE); + if (ret != LTTNG_OK) { + DBG2("Agent update unable to add application context %s:%s on app pid: %d sock %d", + ctx->provider_name, ctx->ctx_name, + app->pid, app->sock->fd); + continue; + } + } + + rcu_read_unlock(); +} + +/* + * Allocate the per-event notifier domain agent hash table. It is lazily + * populated as domains are used. + */ +int agent_by_event_notifier_domain_ht_create(void) +{ + the_trigger_agents_ht_by_domain = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + return the_trigger_agents_ht_by_domain ? 0 : -1; +} + +/* + * Clean-up the per-event notifier domain agent hash table and destroy it. + */ +void agent_by_event_notifier_domain_ht_destroy(void) +{ + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + + if (!the_trigger_agents_ht_by_domain) { + return; + } + + rcu_read_lock(); + cds_lfht_for_each_entry(the_trigger_agents_ht_by_domain->ht, + &iter.iter, node, node) { + struct agent *agent = + caa_container_of(node, struct agent, node); + const int ret = lttng_ht_del( + the_trigger_agents_ht_by_domain, &iter); + + LTTNG_ASSERT(ret == 0); + agent_destroy(agent); + } + + rcu_read_unlock(); + lttng_ht_destroy(the_trigger_agents_ht_by_domain); +} + +struct agent *agent_find_by_event_notifier_domain( + enum lttng_domain_type domain_type) +{ + struct agent *agt = NULL; + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + const uint64_t key = (uint64_t) domain_type; + + LTTNG_ASSERT(the_trigger_agents_ht_by_domain); + + DBG3("Per-event notifier domain agent lookup for domain '%s'", + lttng_domain_type_str(domain_type)); + + lttng_ht_lookup(the_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/buffer-registry.c b/src/bin/lttng-sessiond/buffer-registry.c deleted file mode 100644 index 3390f870a..000000000 --- a/src/bin/lttng-sessiond/buffer-registry.c +++ /dev/null @@ -1,746 +0,0 @@ -/* - * Copyright (C) 2013 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include - -#include -#include - -#include "buffer-registry.h" -#include "fd-limit.h" -#include "ust-consumer.h" -#include "lttng-ust-ctl.h" -#include "lttng-ust-error.h" -#include "utils.h" - -/* - * Set in main.c during initialization process of the daemon. This contains - * buffer_reg_uid object which are global registry for per UID buffer. Object - * are indexed by session id and matched by the triplet - * . - */ -static struct lttng_ht *buffer_registry_uid; - -/* - * Initialized at the daemon start. This contains buffer_reg_pid object and - * indexed by session id. - */ -static struct lttng_ht *buffer_registry_pid; - -/* - * Match function for the per UID registry hash table. It matches a registry - * uid object with the triplet . - */ -static int ht_match_reg_uid(struct cds_lfht_node *node, const void *_key) -{ - struct buffer_reg_uid *reg; - const struct buffer_reg_uid *key; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - reg = caa_container_of(node, struct buffer_reg_uid, node.node); - LTTNG_ASSERT(reg); - key = _key; - - if (key->session_id != reg->session_id || - key->bits_per_long != reg->bits_per_long || - key->uid != reg->uid) { - goto no_match; - } - - /* Match */ - return 1; -no_match: - return 0; -} - -/* - * Hash function for the per UID registry hash table. This XOR the triplet - * together. - */ -static unsigned long ht_hash_reg_uid(const void *_key, unsigned long seed) -{ - uint64_t xored_key; - const struct buffer_reg_uid *key = _key; - - LTTNG_ASSERT(key); - - xored_key = (uint64_t)(key->session_id ^ key->bits_per_long ^ key->uid); - return hash_key_u64(&xored_key, seed); -} - -/* - * Initialize global buffer per UID registry. Should only be called ONCE!. - */ -void buffer_reg_init_uid_registry(void) -{ - /* Should be called once. */ - LTTNG_ASSERT(!buffer_registry_uid); - buffer_registry_uid = lttng_ht_new(0, LTTNG_HT_TYPE_U64); - LTTNG_ASSERT(buffer_registry_uid); - buffer_registry_uid->match_fct = ht_match_reg_uid; - buffer_registry_uid->hash_fct = ht_hash_reg_uid; - - DBG3("Global buffer per UID registry initialized"); -} - -/* - * Allocate and initialize object. Set regp with the object pointer. - * - * Return 0 on success else a negative value and regp is untouched. - */ -int buffer_reg_uid_create(uint64_t session_id, uint32_t bits_per_long, uid_t uid, - enum lttng_domain_type domain, struct buffer_reg_uid **regp, - const char *root_shm_path, const char *shm_path) -{ - int ret = 0; - struct buffer_reg_uid *reg = NULL; - - LTTNG_ASSERT(regp); - - reg = zmalloc(sizeof(*reg)); - if (!reg) { - PERROR("zmalloc buffer registry uid"); - ret = -ENOMEM; - goto error; - } - - reg->registry = zmalloc(sizeof(struct buffer_reg_session)); - if (!reg->registry) { - PERROR("zmalloc buffer registry uid session"); - ret = -ENOMEM; - goto error; - } - - reg->session_id = session_id; - reg->bits_per_long = bits_per_long; - reg->uid = uid; - reg->domain = domain; - if (shm_path[0]) { - strncpy(reg->root_shm_path, root_shm_path, sizeof(reg->root_shm_path)); - reg->root_shm_path[sizeof(reg->root_shm_path) - 1] = '\0'; - strncpy(reg->shm_path, shm_path, sizeof(reg->shm_path)); - reg->shm_path[sizeof(reg->shm_path) - 1] = '\0'; - DBG3("shm path '%s' is assigned to uid buffer registry for session id %" PRIu64, - reg->shm_path, session_id); - } - reg->registry->channels = lttng_ht_new(0, LTTNG_HT_TYPE_U64); - if (!reg->registry->channels) { - ret = -ENOMEM; - goto error_session; - } - - cds_lfht_node_init(®->node.node); - *regp = reg; - - DBG3("Buffer registry per UID created id: %" PRIu64 ", ABI: %u, uid: %d, domain: %d", - session_id, bits_per_long, uid, domain); - - return 0; - -error_session: - free(reg->registry); -error: - free(reg); - return ret; -} - -/* - * Add a buffer registry per UID object to the global registry. - */ -void buffer_reg_uid_add(struct buffer_reg_uid *reg) -{ - struct cds_lfht_node *nodep; - struct lttng_ht *ht = buffer_registry_uid; - - LTTNG_ASSERT(reg); - - DBG3("Buffer registry per UID adding to global registry with id: %" PRIu64 , - reg->session_id); - - rcu_read_lock(); - nodep = cds_lfht_add_unique(ht->ht, ht->hash_fct(reg, lttng_ht_seed), - ht->match_fct, reg, ®->node.node); - LTTNG_ASSERT(nodep == ®->node.node); - rcu_read_unlock(); -} - -/* - * Find a buffer registry per UID object with given params. RCU read side lock - * MUST be acquired before calling this and hold on to protect the object. - * - * Return the object pointer or NULL on error. - */ -struct buffer_reg_uid *buffer_reg_uid_find(uint64_t session_id, - uint32_t bits_per_long, uid_t uid) -{ - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - struct buffer_reg_uid *reg = NULL, key; - struct lttng_ht *ht = buffer_registry_uid; - - /* Setup key we are looking for. */ - key.session_id = session_id; - key.bits_per_long = bits_per_long; - key.uid = uid; - - DBG3("Buffer registry per UID find id: %" PRIu64 ", ABI: %u, uid: %d", - session_id, bits_per_long, uid); - - /* Custom lookup function since it's a different key. */ - cds_lfht_lookup(ht->ht, ht->hash_fct(&key, lttng_ht_seed), ht->match_fct, - &key, &iter.iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (!node) { - goto end; - } - reg = caa_container_of(node, struct buffer_reg_uid, node); - -end: - return reg; -} - -/* - * Initialize global buffer per PID registry. Should only be called ONCE!. - */ -void buffer_reg_init_pid_registry(void) -{ - /* Should be called once. */ - LTTNG_ASSERT(!buffer_registry_pid); - buffer_registry_pid = lttng_ht_new(0, LTTNG_HT_TYPE_U64); - LTTNG_ASSERT(buffer_registry_pid); - - DBG3("Global buffer per PID registry initialized"); -} - -/* - * Allocate and initialize object. Set regp with the object pointer. - * - * Return 0 on success else a negative value and regp is untouched. - */ -int buffer_reg_pid_create(uint64_t session_id, struct buffer_reg_pid **regp, - const char *root_shm_path, const char *shm_path) -{ - int ret = 0; - struct buffer_reg_pid *reg = NULL; - - LTTNG_ASSERT(regp); - - reg = zmalloc(sizeof(*reg)); - if (!reg) { - PERROR("zmalloc buffer registry pid"); - ret = -ENOMEM; - goto error; - } - - reg->registry = zmalloc(sizeof(struct buffer_reg_session)); - if (!reg->registry) { - PERROR("zmalloc buffer registry pid session"); - ret = -ENOMEM; - goto error; - } - - /* A cast is done here so we can use the session ID as a u64 ht node. */ - reg->session_id = session_id; - if (shm_path[0]) { - strncpy(reg->root_shm_path, root_shm_path, sizeof(reg->root_shm_path)); - reg->root_shm_path[sizeof(reg->root_shm_path) - 1] = '\0'; - strncpy(reg->shm_path, shm_path, sizeof(reg->shm_path)); - reg->shm_path[sizeof(reg->shm_path) - 1] = '\0'; - DBG3("shm path '%s' is assigned to pid buffer registry for session id %" PRIu64, - reg->shm_path, session_id); - } - reg->registry->channels = lttng_ht_new(0, LTTNG_HT_TYPE_U64); - if (!reg->registry->channels) { - ret = -ENOMEM; - goto error_session; - } - - lttng_ht_node_init_u64(®->node, reg->session_id); - *regp = reg; - - DBG3("Buffer registry per PID created with session id: %" PRIu64, - session_id); - - return 0; - -error_session: - free(reg->registry); -error: - free(reg); - return ret; -} - -/* - * Add a buffer registry per PID object to the global registry. - */ -void buffer_reg_pid_add(struct buffer_reg_pid *reg) -{ - LTTNG_ASSERT(reg); - - DBG3("Buffer registry per PID adding to global registry with id: %" PRIu64, - reg->session_id); - - rcu_read_lock(); - lttng_ht_add_unique_u64(buffer_registry_pid, ®->node); - rcu_read_unlock(); -} - -/* - * Find a buffer registry per PID object with given params. RCU read side lock - * MUST be acquired before calling this and hold on to protect the object. - * - * Return the object pointer or NULL on error. - */ -struct buffer_reg_pid *buffer_reg_pid_find(uint64_t session_id) -{ - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - struct buffer_reg_pid *reg = NULL; - struct lttng_ht *ht = buffer_registry_pid; - - DBG3("Buffer registry per PID find id: %" PRIu64, session_id); - - lttng_ht_lookup(ht, &session_id, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (!node) { - goto end; - } - reg = caa_container_of(node, struct buffer_reg_pid, node); - -end: - return reg; -} - -/* - * Find the consumer channel key from a UST session per-uid channel key. - * - * Return the matching key or -1 if not found. - */ -int buffer_reg_uid_consumer_channel_key( - struct cds_list_head *buffer_reg_uid_list, - uint64_t chan_key, uint64_t *consumer_chan_key) -{ - struct lttng_ht_iter iter; - struct buffer_reg_uid *uid_reg = NULL; - struct buffer_reg_session *session_reg = NULL; - struct buffer_reg_channel *reg_chan; - int ret = -1; - - rcu_read_lock(); - /* - * For the per-uid registry, we have to iterate since we don't have the - * uid and bitness key. - */ - cds_list_for_each_entry(uid_reg, buffer_reg_uid_list, lnode) { - session_reg = uid_reg->registry; - cds_lfht_for_each_entry(session_reg->channels->ht, - &iter.iter, reg_chan, node.node) { - if (reg_chan->key == chan_key) { - *consumer_chan_key = reg_chan->consumer_key; - ret = 0; - goto end; - } - } - } - -end: - rcu_read_unlock(); - return ret; -} - -/* - * Allocate and initialize a buffer registry channel with the given key. Set - * regp with the object pointer. - * - * Return 0 on success or else a negative value keeping regp untouched. - */ -int buffer_reg_channel_create(uint64_t key, struct buffer_reg_channel **regp) -{ - struct buffer_reg_channel *reg; - - LTTNG_ASSERT(regp); - - DBG3("Buffer registry channel create with key: %" PRIu64, key); - - reg = zmalloc(sizeof(*reg)); - if (!reg) { - PERROR("zmalloc buffer registry channel"); - return -ENOMEM; - } - - reg->key = key; - CDS_INIT_LIST_HEAD(®->streams); - pthread_mutex_init(®->stream_list_lock, NULL); - - lttng_ht_node_init_u64(®->node, key); - *regp = reg; - - return 0; -} - -/* - * Allocate and initialize a buffer registry stream. Set regp with the object - * pointer. - * - * Return 0 on success or else a negative value keeping regp untouched. - */ -int buffer_reg_stream_create(struct buffer_reg_stream **regp) -{ - struct buffer_reg_stream *reg; - - LTTNG_ASSERT(regp); - - DBG3("Buffer registry creating stream"); - - reg = zmalloc(sizeof(*reg)); - if (!reg) { - PERROR("zmalloc buffer registry stream"); - return -ENOMEM; - } - - *regp = reg; - - return 0; -} - -/* - * Add stream to the list in the channel. - */ -void buffer_reg_stream_add(struct buffer_reg_stream *stream, - struct buffer_reg_channel *channel) -{ - LTTNG_ASSERT(stream); - LTTNG_ASSERT(channel); - - pthread_mutex_lock(&channel->stream_list_lock); - cds_list_add_tail(&stream->lnode, &channel->streams); - channel->stream_count++; - pthread_mutex_unlock(&channel->stream_list_lock); -} - -/* - * Add a buffer registry channel object to the given session. - */ -void buffer_reg_channel_add(struct buffer_reg_session *session, - struct buffer_reg_channel *channel) -{ - LTTNG_ASSERT(session); - LTTNG_ASSERT(channel); - - rcu_read_lock(); - lttng_ht_add_unique_u64(session->channels, &channel->node); - rcu_read_unlock(); -} - -/* - * Find a buffer registry channel object with the given key. RCU read side lock - * MUST be acquired and hold on until the object reference is not needed - * anymore. - * - * Return the object pointer or NULL on error. - */ -struct buffer_reg_channel *buffer_reg_channel_find(uint64_t key, - struct buffer_reg_uid *reg) -{ - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - struct buffer_reg_channel *chan = NULL; - struct lttng_ht *ht; - - LTTNG_ASSERT(reg); - - switch (reg->domain) { - case LTTNG_DOMAIN_UST: - ht = reg->registry->channels; - break; - default: - abort(); - goto end; - } - - lttng_ht_lookup(ht, &key, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (!node) { - goto end; - } - chan = caa_container_of(node, struct buffer_reg_channel, node); - -end: - return chan; -} - -/* - * Destroy a buffer registry stream with the given domain. - */ -void buffer_reg_stream_destroy(struct buffer_reg_stream *regp, - enum lttng_domain_type domain) -{ - if (!regp) { - return; - } - - DBG3("Buffer registry stream destroy with handle %d", - regp->obj.ust->handle); - - switch (domain) { - case LTTNG_DOMAIN_UST: - { - int ret; - - ret = ust_app_release_object(NULL, regp->obj.ust); - if (ret < 0 && ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { - ERR("Buffer reg stream release obj handle %d failed with ret %d", - regp->obj.ust->handle, ret); - } - free(regp->obj.ust); - lttng_fd_put(LTTNG_FD_APPS, 2); - break; - } - default: - abort(); - } - - free(regp); - return; -} - -/* - * Remove buffer registry channel object from the session hash table. RCU read - * side lock MUST be acquired before calling this. - */ -void buffer_reg_channel_remove(struct buffer_reg_session *session, - struct buffer_reg_channel *regp) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(regp); - - iter.iter.node = ®p->node.node; - ret = lttng_ht_del(session->channels, &iter); - LTTNG_ASSERT(!ret); -} - -/* - * Destroy a buffer registry channel with the given domain. - */ -void buffer_reg_channel_destroy(struct buffer_reg_channel *regp, - enum lttng_domain_type domain) -{ - if (!regp) { - return; - } - - DBG3("Buffer registry channel destroy with key %" PRIu32, regp->key); - - switch (domain) { - case LTTNG_DOMAIN_UST: - { - int ret; - struct buffer_reg_stream *sreg, *stmp; - /* Wipe stream */ - cds_list_for_each_entry_safe(sreg, stmp, ®p->streams, lnode) { - cds_list_del(&sreg->lnode); - regp->stream_count--; - buffer_reg_stream_destroy(sreg, domain); - } - - if (regp->obj.ust) { - ret = ust_app_release_object(NULL, regp->obj.ust); - if (ret < 0 && ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { - ERR("Buffer reg channel release obj handle %d failed with ret %d", - regp->obj.ust->handle, ret); - } - free(regp->obj.ust); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - break; - } - default: - abort(); - } - - free(regp); - return; -} - -/* - * Destroy a buffer registry session with the given domain. - * - * Should *NOT* be called with RCU read-side lock held. - */ -static void buffer_reg_session_destroy(struct buffer_reg_session *regp, - enum lttng_domain_type domain) -{ - int ret; - struct lttng_ht_iter iter; - struct buffer_reg_channel *reg_chan; - - DBG3("Buffer registry session destroy"); - - /* Destroy all channels. */ - rcu_read_lock(); - cds_lfht_for_each_entry(regp->channels->ht, &iter.iter, reg_chan, - node.node) { - ret = lttng_ht_del(regp->channels, &iter); - LTTNG_ASSERT(!ret); - buffer_reg_channel_destroy(reg_chan, domain); - } - rcu_read_unlock(); - - ht_cleanup_push(regp->channels); - - switch (domain) { - case LTTNG_DOMAIN_UST: - ust_registry_session_destroy(regp->reg.ust); - free(regp->reg.ust); - break; - default: - abort(); - } - - free(regp); - return; -} - -/* - * Remove buffer registry UID object from the global hash table. - */ -void buffer_reg_uid_remove(struct buffer_reg_uid *regp) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(regp); - - rcu_read_lock(); - iter.iter.node = ®p->node.node; - ret = lttng_ht_del(buffer_registry_uid, &iter); - LTTNG_ASSERT(!ret); - rcu_read_unlock(); -} - -static void rcu_free_buffer_reg_uid(struct rcu_head *head) -{ - struct lttng_ht_node_u64 *node = - caa_container_of(head, struct lttng_ht_node_u64, head); - struct buffer_reg_uid *reg = - caa_container_of(node, struct buffer_reg_uid, node); - - buffer_reg_session_destroy(reg->registry, reg->domain); - free(reg); -} - -static void rcu_free_buffer_reg_pid(struct rcu_head *head) -{ - struct lttng_ht_node_u64 *node = - caa_container_of(head, struct lttng_ht_node_u64, head); - struct buffer_reg_pid *reg = - caa_container_of(node, struct buffer_reg_pid, node); - - buffer_reg_session_destroy(reg->registry, LTTNG_DOMAIN_UST); - free(reg); -} - -/* - * Destroy buffer registry per UID. The given pointer is NOT removed from any - * list or hash table. Use buffer_reg_pid_remove() before calling this function - * for the case that the object is in the global hash table. - */ -void buffer_reg_uid_destroy(struct buffer_reg_uid *regp, - struct consumer_output *consumer) -{ - struct consumer_socket *socket; - - if (!regp) { - return; - } - - DBG3("Buffer registry per UID destroy with id: %" PRIu64 ", ABI: %u, uid: %d", - regp->session_id, regp->bits_per_long, regp->uid); - - if (!consumer) { - goto destroy; - } - - rcu_read_lock(); - /* Get the right socket from the consumer object. */ - socket = consumer_find_socket_by_bitness(regp->bits_per_long, - consumer); - if (!socket) { - goto unlock; - } - - switch (regp->domain) { - case LTTNG_DOMAIN_UST: - if (regp->registry->reg.ust->metadata_key) { - /* Return value does not matter. This call will print errors. */ - (void) consumer_close_metadata(socket, - regp->registry->reg.ust->metadata_key); - } - break; - default: - abort(); - rcu_read_unlock(); - return; - } - -unlock: - rcu_read_unlock(); -destroy: - call_rcu(®p->node.head, rcu_free_buffer_reg_uid); -} - -/* - * Remove buffer registry UID object from the global hash table. RCU read side - * lock MUST be acquired before calling this. - */ -void buffer_reg_pid_remove(struct buffer_reg_pid *regp) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(regp); - - iter.iter.node = ®p->node.node; - ret = lttng_ht_del(buffer_registry_pid, &iter); - LTTNG_ASSERT(!ret); -} - -/* - * Destroy buffer registry per PID. The pointer is NOT removed from the global - * hash table. Call buffer_reg_pid_remove() before that if the object was - * previously added to the global hash table. - */ -void buffer_reg_pid_destroy(struct buffer_reg_pid *regp) -{ - if (!regp) { - return; - } - - DBG3("Buffer registry per PID destroy with id: %" PRIu64, - regp->session_id); - - /* This registry is only used by UST. */ - call_rcu(®p->node.head, rcu_free_buffer_reg_pid); -} - -/* - * Destroy per PID and UID registry hash table. - * - * Should *NOT* be called with RCU read-side lock held. - */ -void buffer_reg_destroy_registries(void) -{ - DBG3("Buffer registry destroy all registry"); - ht_cleanup_push(buffer_registry_uid); - ht_cleanup_push(buffer_registry_pid); -} diff --git a/src/bin/lttng-sessiond/buffer-registry.cpp b/src/bin/lttng-sessiond/buffer-registry.cpp new file mode 100644 index 000000000..aca8ec1f2 --- /dev/null +++ b/src/bin/lttng-sessiond/buffer-registry.cpp @@ -0,0 +1,746 @@ +/* + * Copyright (C) 2013 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include + +#include +#include + +#include "buffer-registry.h" +#include "fd-limit.h" +#include "ust-consumer.h" +#include "lttng-ust-ctl.h" +#include "lttng-ust-error.h" +#include "utils.h" + +/* + * Set in main.c during initialization process of the daemon. This contains + * buffer_reg_uid object which are global registry for per UID buffer. Object + * are indexed by session id and matched by the triplet + * . + */ +static struct lttng_ht *buffer_registry_uid; + +/* + * Initialized at the daemon start. This contains buffer_reg_pid object and + * indexed by session id. + */ +static struct lttng_ht *buffer_registry_pid; + +/* + * Match function for the per UID registry hash table. It matches a registry + * uid object with the triplet . + */ +static int ht_match_reg_uid(struct cds_lfht_node *node, const void *_key) +{ + struct buffer_reg_uid *reg; + const struct buffer_reg_uid *key; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + reg = caa_container_of(node, struct buffer_reg_uid, node.node); + LTTNG_ASSERT(reg); + key = (buffer_reg_uid *) _key; + + if (key->session_id != reg->session_id || + key->bits_per_long != reg->bits_per_long || + key->uid != reg->uid) { + goto no_match; + } + + /* Match */ + return 1; +no_match: + return 0; +} + +/* + * Hash function for the per UID registry hash table. This XOR the triplet + * together. + */ +static unsigned long ht_hash_reg_uid(const void *_key, unsigned long seed) +{ + uint64_t xored_key; + const struct buffer_reg_uid *key = (buffer_reg_uid *) _key; + + LTTNG_ASSERT(key); + + xored_key = (uint64_t)(key->session_id ^ key->bits_per_long ^ key->uid); + return hash_key_u64(&xored_key, seed); +} + +/* + * Initialize global buffer per UID registry. Should only be called ONCE!. + */ +void buffer_reg_init_uid_registry(void) +{ + /* Should be called once. */ + LTTNG_ASSERT(!buffer_registry_uid); + buffer_registry_uid = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + LTTNG_ASSERT(buffer_registry_uid); + buffer_registry_uid->match_fct = ht_match_reg_uid; + buffer_registry_uid->hash_fct = ht_hash_reg_uid; + + DBG3("Global buffer per UID registry initialized"); +} + +/* + * Allocate and initialize object. Set regp with the object pointer. + * + * Return 0 on success else a negative value and regp is untouched. + */ +int buffer_reg_uid_create(uint64_t session_id, uint32_t bits_per_long, uid_t uid, + enum lttng_domain_type domain, struct buffer_reg_uid **regp, + const char *root_shm_path, const char *shm_path) +{ + int ret = 0; + struct buffer_reg_uid *reg = NULL; + + LTTNG_ASSERT(regp); + + reg = (buffer_reg_uid *) zmalloc(sizeof(*reg)); + if (!reg) { + PERROR("zmalloc buffer registry uid"); + ret = -ENOMEM; + goto error; + } + + reg->registry = (buffer_reg_session *) zmalloc(sizeof(struct buffer_reg_session)); + if (!reg->registry) { + PERROR("zmalloc buffer registry uid session"); + ret = -ENOMEM; + goto error; + } + + reg->session_id = session_id; + reg->bits_per_long = bits_per_long; + reg->uid = uid; + reg->domain = domain; + if (shm_path[0]) { + strncpy(reg->root_shm_path, root_shm_path, sizeof(reg->root_shm_path)); + reg->root_shm_path[sizeof(reg->root_shm_path) - 1] = '\0'; + strncpy(reg->shm_path, shm_path, sizeof(reg->shm_path)); + reg->shm_path[sizeof(reg->shm_path) - 1] = '\0'; + DBG3("shm path '%s' is assigned to uid buffer registry for session id %" PRIu64, + reg->shm_path, session_id); + } + reg->registry->channels = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + if (!reg->registry->channels) { + ret = -ENOMEM; + goto error_session; + } + + cds_lfht_node_init(®->node.node); + *regp = reg; + + DBG3("Buffer registry per UID created id: %" PRIu64 ", ABI: %u, uid: %d, domain: %d", + session_id, bits_per_long, uid, domain); + + return 0; + +error_session: + free(reg->registry); +error: + free(reg); + return ret; +} + +/* + * Add a buffer registry per UID object to the global registry. + */ +void buffer_reg_uid_add(struct buffer_reg_uid *reg) +{ + struct cds_lfht_node *nodep; + struct lttng_ht *ht = buffer_registry_uid; + + LTTNG_ASSERT(reg); + + DBG3("Buffer registry per UID adding to global registry with id: %" PRIu64 , + reg->session_id); + + rcu_read_lock(); + nodep = cds_lfht_add_unique(ht->ht, ht->hash_fct(reg, lttng_ht_seed), + ht->match_fct, reg, ®->node.node); + LTTNG_ASSERT(nodep == ®->node.node); + rcu_read_unlock(); +} + +/* + * Find a buffer registry per UID object with given params. RCU read side lock + * MUST be acquired before calling this and hold on to protect the object. + * + * Return the object pointer or NULL on error. + */ +struct buffer_reg_uid *buffer_reg_uid_find(uint64_t session_id, + uint32_t bits_per_long, uid_t uid) +{ + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + struct buffer_reg_uid *reg = NULL, key; + struct lttng_ht *ht = buffer_registry_uid; + + /* Setup key we are looking for. */ + key.session_id = session_id; + key.bits_per_long = bits_per_long; + key.uid = uid; + + DBG3("Buffer registry per UID find id: %" PRIu64 ", ABI: %u, uid: %d", + session_id, bits_per_long, uid); + + /* Custom lookup function since it's a different key. */ + cds_lfht_lookup(ht->ht, ht->hash_fct(&key, lttng_ht_seed), ht->match_fct, + &key, &iter.iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (!node) { + goto end; + } + reg = caa_container_of(node, struct buffer_reg_uid, node); + +end: + return reg; +} + +/* + * Initialize global buffer per PID registry. Should only be called ONCE!. + */ +void buffer_reg_init_pid_registry(void) +{ + /* Should be called once. */ + LTTNG_ASSERT(!buffer_registry_pid); + buffer_registry_pid = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + LTTNG_ASSERT(buffer_registry_pid); + + DBG3("Global buffer per PID registry initialized"); +} + +/* + * Allocate and initialize object. Set regp with the object pointer. + * + * Return 0 on success else a negative value and regp is untouched. + */ +int buffer_reg_pid_create(uint64_t session_id, struct buffer_reg_pid **regp, + const char *root_shm_path, const char *shm_path) +{ + int ret = 0; + struct buffer_reg_pid *reg = NULL; + + LTTNG_ASSERT(regp); + + reg = (buffer_reg_pid *) zmalloc(sizeof(*reg)); + if (!reg) { + PERROR("zmalloc buffer registry pid"); + ret = -ENOMEM; + goto error; + } + + reg->registry = (buffer_reg_session *) zmalloc(sizeof(struct buffer_reg_session)); + if (!reg->registry) { + PERROR("zmalloc buffer registry pid session"); + ret = -ENOMEM; + goto error; + } + + /* A cast is done here so we can use the session ID as a u64 ht node. */ + reg->session_id = session_id; + if (shm_path[0]) { + strncpy(reg->root_shm_path, root_shm_path, sizeof(reg->root_shm_path)); + reg->root_shm_path[sizeof(reg->root_shm_path) - 1] = '\0'; + strncpy(reg->shm_path, shm_path, sizeof(reg->shm_path)); + reg->shm_path[sizeof(reg->shm_path) - 1] = '\0'; + DBG3("shm path '%s' is assigned to pid buffer registry for session id %" PRIu64, + reg->shm_path, session_id); + } + reg->registry->channels = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + if (!reg->registry->channels) { + ret = -ENOMEM; + goto error_session; + } + + lttng_ht_node_init_u64(®->node, reg->session_id); + *regp = reg; + + DBG3("Buffer registry per PID created with session id: %" PRIu64, + session_id); + + return 0; + +error_session: + free(reg->registry); +error: + free(reg); + return ret; +} + +/* + * Add a buffer registry per PID object to the global registry. + */ +void buffer_reg_pid_add(struct buffer_reg_pid *reg) +{ + LTTNG_ASSERT(reg); + + DBG3("Buffer registry per PID adding to global registry with id: %" PRIu64, + reg->session_id); + + rcu_read_lock(); + lttng_ht_add_unique_u64(buffer_registry_pid, ®->node); + rcu_read_unlock(); +} + +/* + * Find a buffer registry per PID object with given params. RCU read side lock + * MUST be acquired before calling this and hold on to protect the object. + * + * Return the object pointer or NULL on error. + */ +struct buffer_reg_pid *buffer_reg_pid_find(uint64_t session_id) +{ + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + struct buffer_reg_pid *reg = NULL; + struct lttng_ht *ht = buffer_registry_pid; + + DBG3("Buffer registry per PID find id: %" PRIu64, session_id); + + lttng_ht_lookup(ht, &session_id, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (!node) { + goto end; + } + reg = caa_container_of(node, struct buffer_reg_pid, node); + +end: + return reg; +} + +/* + * Find the consumer channel key from a UST session per-uid channel key. + * + * Return the matching key or -1 if not found. + */ +int buffer_reg_uid_consumer_channel_key( + struct cds_list_head *buffer_reg_uid_list, + uint64_t chan_key, uint64_t *consumer_chan_key) +{ + struct lttng_ht_iter iter; + struct buffer_reg_uid *uid_reg = NULL; + struct buffer_reg_session *session_reg = NULL; + struct buffer_reg_channel *reg_chan; + int ret = -1; + + rcu_read_lock(); + /* + * For the per-uid registry, we have to iterate since we don't have the + * uid and bitness key. + */ + cds_list_for_each_entry(uid_reg, buffer_reg_uid_list, lnode) { + session_reg = uid_reg->registry; + cds_lfht_for_each_entry(session_reg->channels->ht, + &iter.iter, reg_chan, node.node) { + if (reg_chan->key == chan_key) { + *consumer_chan_key = reg_chan->consumer_key; + ret = 0; + goto end; + } + } + } + +end: + rcu_read_unlock(); + return ret; +} + +/* + * Allocate and initialize a buffer registry channel with the given key. Set + * regp with the object pointer. + * + * Return 0 on success or else a negative value keeping regp untouched. + */ +int buffer_reg_channel_create(uint64_t key, struct buffer_reg_channel **regp) +{ + struct buffer_reg_channel *reg; + + LTTNG_ASSERT(regp); + + DBG3("Buffer registry channel create with key: %" PRIu64, key); + + reg = (buffer_reg_channel *) zmalloc(sizeof(*reg)); + if (!reg) { + PERROR("zmalloc buffer registry channel"); + return -ENOMEM; + } + + reg->key = key; + CDS_INIT_LIST_HEAD(®->streams); + pthread_mutex_init(®->stream_list_lock, NULL); + + lttng_ht_node_init_u64(®->node, key); + *regp = reg; + + return 0; +} + +/* + * Allocate and initialize a buffer registry stream. Set regp with the object + * pointer. + * + * Return 0 on success or else a negative value keeping regp untouched. + */ +int buffer_reg_stream_create(struct buffer_reg_stream **regp) +{ + struct buffer_reg_stream *reg; + + LTTNG_ASSERT(regp); + + DBG3("Buffer registry creating stream"); + + reg = (buffer_reg_stream *) zmalloc(sizeof(*reg)); + if (!reg) { + PERROR("zmalloc buffer registry stream"); + return -ENOMEM; + } + + *regp = reg; + + return 0; +} + +/* + * Add stream to the list in the channel. + */ +void buffer_reg_stream_add(struct buffer_reg_stream *stream, + struct buffer_reg_channel *channel) +{ + LTTNG_ASSERT(stream); + LTTNG_ASSERT(channel); + + pthread_mutex_lock(&channel->stream_list_lock); + cds_list_add_tail(&stream->lnode, &channel->streams); + channel->stream_count++; + pthread_mutex_unlock(&channel->stream_list_lock); +} + +/* + * Add a buffer registry channel object to the given session. + */ +void buffer_reg_channel_add(struct buffer_reg_session *session, + struct buffer_reg_channel *channel) +{ + LTTNG_ASSERT(session); + LTTNG_ASSERT(channel); + + rcu_read_lock(); + lttng_ht_add_unique_u64(session->channels, &channel->node); + rcu_read_unlock(); +} + +/* + * Find a buffer registry channel object with the given key. RCU read side lock + * MUST be acquired and hold on until the object reference is not needed + * anymore. + * + * Return the object pointer or NULL on error. + */ +struct buffer_reg_channel *buffer_reg_channel_find(uint64_t key, + struct buffer_reg_uid *reg) +{ + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + struct buffer_reg_channel *chan = NULL; + struct lttng_ht *ht; + + LTTNG_ASSERT(reg); + + switch (reg->domain) { + case LTTNG_DOMAIN_UST: + ht = reg->registry->channels; + break; + default: + abort(); + goto end; + } + + lttng_ht_lookup(ht, &key, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (!node) { + goto end; + } + chan = caa_container_of(node, struct buffer_reg_channel, node); + +end: + return chan; +} + +/* + * Destroy a buffer registry stream with the given domain. + */ +void buffer_reg_stream_destroy(struct buffer_reg_stream *regp, + enum lttng_domain_type domain) +{ + if (!regp) { + return; + } + + DBG3("Buffer registry stream destroy with handle %d", + regp->obj.ust->handle); + + switch (domain) { + case LTTNG_DOMAIN_UST: + { + int ret; + + ret = ust_app_release_object(NULL, regp->obj.ust); + if (ret < 0 && ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { + ERR("Buffer reg stream release obj handle %d failed with ret %d", + regp->obj.ust->handle, ret); + } + free(regp->obj.ust); + lttng_fd_put(LTTNG_FD_APPS, 2); + break; + } + default: + abort(); + } + + free(regp); + return; +} + +/* + * Remove buffer registry channel object from the session hash table. RCU read + * side lock MUST be acquired before calling this. + */ +void buffer_reg_channel_remove(struct buffer_reg_session *session, + struct buffer_reg_channel *regp) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(regp); + + iter.iter.node = ®p->node.node; + ret = lttng_ht_del(session->channels, &iter); + LTTNG_ASSERT(!ret); +} + +/* + * Destroy a buffer registry channel with the given domain. + */ +void buffer_reg_channel_destroy(struct buffer_reg_channel *regp, + enum lttng_domain_type domain) +{ + if (!regp) { + return; + } + + DBG3("Buffer registry channel destroy with key %" PRIu32, regp->key); + + switch (domain) { + case LTTNG_DOMAIN_UST: + { + int ret; + struct buffer_reg_stream *sreg, *stmp; + /* Wipe stream */ + cds_list_for_each_entry_safe(sreg, stmp, ®p->streams, lnode) { + cds_list_del(&sreg->lnode); + regp->stream_count--; + buffer_reg_stream_destroy(sreg, domain); + } + + if (regp->obj.ust) { + ret = ust_app_release_object(NULL, regp->obj.ust); + if (ret < 0 && ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { + ERR("Buffer reg channel release obj handle %d failed with ret %d", + regp->obj.ust->handle, ret); + } + free(regp->obj.ust); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + break; + } + default: + abort(); + } + + free(regp); + return; +} + +/* + * Destroy a buffer registry session with the given domain. + * + * Should *NOT* be called with RCU read-side lock held. + */ +static void buffer_reg_session_destroy(struct buffer_reg_session *regp, + enum lttng_domain_type domain) +{ + int ret; + struct lttng_ht_iter iter; + struct buffer_reg_channel *reg_chan; + + DBG3("Buffer registry session destroy"); + + /* Destroy all channels. */ + rcu_read_lock(); + cds_lfht_for_each_entry(regp->channels->ht, &iter.iter, reg_chan, + node.node) { + ret = lttng_ht_del(regp->channels, &iter); + LTTNG_ASSERT(!ret); + buffer_reg_channel_destroy(reg_chan, domain); + } + rcu_read_unlock(); + + ht_cleanup_push(regp->channels); + + switch (domain) { + case LTTNG_DOMAIN_UST: + ust_registry_session_destroy(regp->reg.ust); + free(regp->reg.ust); + break; + default: + abort(); + } + + free(regp); + return; +} + +/* + * Remove buffer registry UID object from the global hash table. + */ +void buffer_reg_uid_remove(struct buffer_reg_uid *regp) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(regp); + + rcu_read_lock(); + iter.iter.node = ®p->node.node; + ret = lttng_ht_del(buffer_registry_uid, &iter); + LTTNG_ASSERT(!ret); + rcu_read_unlock(); +} + +static void rcu_free_buffer_reg_uid(struct rcu_head *head) +{ + struct lttng_ht_node_u64 *node = + caa_container_of(head, struct lttng_ht_node_u64, head); + struct buffer_reg_uid *reg = + caa_container_of(node, struct buffer_reg_uid, node); + + buffer_reg_session_destroy(reg->registry, reg->domain); + free(reg); +} + +static void rcu_free_buffer_reg_pid(struct rcu_head *head) +{ + struct lttng_ht_node_u64 *node = + caa_container_of(head, struct lttng_ht_node_u64, head); + struct buffer_reg_pid *reg = + caa_container_of(node, struct buffer_reg_pid, node); + + buffer_reg_session_destroy(reg->registry, LTTNG_DOMAIN_UST); + free(reg); +} + +/* + * Destroy buffer registry per UID. The given pointer is NOT removed from any + * list or hash table. Use buffer_reg_pid_remove() before calling this function + * for the case that the object is in the global hash table. + */ +void buffer_reg_uid_destroy(struct buffer_reg_uid *regp, + struct consumer_output *consumer) +{ + struct consumer_socket *socket; + + if (!regp) { + return; + } + + DBG3("Buffer registry per UID destroy with id: %" PRIu64 ", ABI: %u, uid: %d", + regp->session_id, regp->bits_per_long, regp->uid); + + if (!consumer) { + goto destroy; + } + + rcu_read_lock(); + /* Get the right socket from the consumer object. */ + socket = consumer_find_socket_by_bitness(regp->bits_per_long, + consumer); + if (!socket) { + goto unlock; + } + + switch (regp->domain) { + case LTTNG_DOMAIN_UST: + if (regp->registry->reg.ust->metadata_key) { + /* Return value does not matter. This call will print errors. */ + (void) consumer_close_metadata(socket, + regp->registry->reg.ust->metadata_key); + } + break; + default: + abort(); + rcu_read_unlock(); + return; + } + +unlock: + rcu_read_unlock(); +destroy: + call_rcu(®p->node.head, rcu_free_buffer_reg_uid); +} + +/* + * Remove buffer registry UID object from the global hash table. RCU read side + * lock MUST be acquired before calling this. + */ +void buffer_reg_pid_remove(struct buffer_reg_pid *regp) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(regp); + + iter.iter.node = ®p->node.node; + ret = lttng_ht_del(buffer_registry_pid, &iter); + LTTNG_ASSERT(!ret); +} + +/* + * Destroy buffer registry per PID. The pointer is NOT removed from the global + * hash table. Call buffer_reg_pid_remove() before that if the object was + * previously added to the global hash table. + */ +void buffer_reg_pid_destroy(struct buffer_reg_pid *regp) +{ + if (!regp) { + return; + } + + DBG3("Buffer registry per PID destroy with id: %" PRIu64, + regp->session_id); + + /* This registry is only used by UST. */ + call_rcu(®p->node.head, rcu_free_buffer_reg_pid); +} + +/* + * Destroy per PID and UID registry hash table. + * + * Should *NOT* be called with RCU read-side lock held. + */ +void buffer_reg_destroy_registries(void) +{ + DBG3("Buffer registry destroy all registry"); + ht_cleanup_push(buffer_registry_uid); + ht_cleanup_push(buffer_registry_pid); +} diff --git a/src/bin/lttng-sessiond/channel.c b/src/bin/lttng-sessiond/channel.c deleted file mode 100644 index e84da039a..000000000 --- a/src/bin/lttng-sessiond/channel.c +++ /dev/null @@ -1,551 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2016 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include - -#include -#include -#include - -#include "channel.h" -#include "lttng-sessiond.h" -#include "kernel.h" -#include "lttng-ust-ctl.h" -#include "lttng-ust-error.h" -#include "utils.h" -#include "ust-app.h" -#include "agent.h" - -/* - * Return allocated channel attributes. - */ -struct lttng_channel *channel_new_default_attr(int dom, - enum lttng_buffer_type type) -{ - struct lttng_channel *chan; - const char *channel_name = DEFAULT_CHANNEL_NAME; - struct lttng_channel_extended *extended_attr = NULL; - - chan = zmalloc(sizeof(struct lttng_channel)); - if (chan == NULL) { - PERROR("zmalloc channel init"); - goto error_alloc; - } - - extended_attr = zmalloc(sizeof(struct lttng_channel_extended)); - if (!extended_attr) { - PERROR("zmalloc channel extended init"); - goto error; - } - - chan->attr.extended.ptr = extended_attr; - - /* Same for all domains. */ - chan->attr.overwrite = DEFAULT_CHANNEL_OVERWRITE; - chan->attr.tracefile_size = DEFAULT_CHANNEL_TRACEFILE_SIZE; - chan->attr.tracefile_count = DEFAULT_CHANNEL_TRACEFILE_COUNT; - - switch (dom) { - case LTTNG_DOMAIN_KERNEL: - LTTNG_ASSERT(type == LTTNG_BUFFER_GLOBAL); - chan->attr.subbuf_size = - default_get_kernel_channel_subbuf_size(); - chan->attr.num_subbuf = DEFAULT_KERNEL_CHANNEL_SUBBUF_NUM; - chan->attr.output = DEFAULT_KERNEL_CHANNEL_OUTPUT; - chan->attr.switch_timer_interval = DEFAULT_KERNEL_CHANNEL_SWITCH_TIMER; - chan->attr.read_timer_interval = DEFAULT_KERNEL_CHANNEL_READ_TIMER; - chan->attr.live_timer_interval = DEFAULT_KERNEL_CHANNEL_LIVE_TIMER; - extended_attr->blocking_timeout = DEFAULT_KERNEL_CHANNEL_BLOCKING_TIMEOUT; - extended_attr->monitor_timer_interval = - DEFAULT_KERNEL_CHANNEL_MONITOR_TIMER; - break; - case LTTNG_DOMAIN_JUL: - channel_name = DEFAULT_JUL_CHANNEL_NAME; - goto common_ust; - case LTTNG_DOMAIN_LOG4J: - channel_name = DEFAULT_LOG4J_CHANNEL_NAME; - goto common_ust; - case LTTNG_DOMAIN_PYTHON: - channel_name = DEFAULT_PYTHON_CHANNEL_NAME; - goto common_ust; - case LTTNG_DOMAIN_UST: -common_ust: - switch (type) { - case LTTNG_BUFFER_PER_UID: - chan->attr.subbuf_size = default_get_ust_uid_channel_subbuf_size(); - chan->attr.num_subbuf = DEFAULT_UST_UID_CHANNEL_SUBBUF_NUM; - chan->attr.output = DEFAULT_UST_UID_CHANNEL_OUTPUT; - chan->attr.switch_timer_interval = - DEFAULT_UST_UID_CHANNEL_SWITCH_TIMER; - chan->attr.read_timer_interval = - DEFAULT_UST_UID_CHANNEL_READ_TIMER; - chan->attr.live_timer_interval = - DEFAULT_UST_UID_CHANNEL_LIVE_TIMER; - extended_attr->blocking_timeout = DEFAULT_UST_UID_CHANNEL_BLOCKING_TIMEOUT; - extended_attr->monitor_timer_interval = - DEFAULT_UST_UID_CHANNEL_MONITOR_TIMER; - break; - case LTTNG_BUFFER_PER_PID: - default: - chan->attr.subbuf_size = default_get_ust_pid_channel_subbuf_size(); - chan->attr.num_subbuf = DEFAULT_UST_PID_CHANNEL_SUBBUF_NUM; - chan->attr.output = DEFAULT_UST_PID_CHANNEL_OUTPUT; - chan->attr.switch_timer_interval = - DEFAULT_UST_PID_CHANNEL_SWITCH_TIMER; - chan->attr.read_timer_interval = - DEFAULT_UST_PID_CHANNEL_READ_TIMER; - chan->attr.live_timer_interval = - DEFAULT_UST_PID_CHANNEL_LIVE_TIMER; - extended_attr->blocking_timeout = DEFAULT_UST_PID_CHANNEL_BLOCKING_TIMEOUT; - extended_attr->monitor_timer_interval = - DEFAULT_UST_PID_CHANNEL_MONITOR_TIMER; - break; - } - break; - default: - goto error; /* Not implemented */ - } - - if (snprintf(chan->name, sizeof(chan->name), "%s", - channel_name) < 0) { - PERROR("snprintf default channel name"); - goto error; - } - return chan; - -error: - free(extended_attr); - free(chan); -error_alloc: - return NULL; -} - -void channel_attr_destroy(struct lttng_channel *channel) -{ - if (!channel) { - return; - } - free(channel->attr.extended.ptr); - free(channel); -} - -/* - * Disable kernel channel of the kernel session. - */ -int channel_kernel_disable(struct ltt_kernel_session *ksession, - char *channel_name) -{ - int ret; - struct ltt_kernel_channel *kchan; - - LTTNG_ASSERT(ksession); - LTTNG_ASSERT(channel_name); - - kchan = trace_kernel_get_channel_by_name(channel_name, ksession); - if (kchan == NULL) { - ret = LTTNG_ERR_KERN_CHAN_NOT_FOUND; - goto error; - } - - /* Only if channel is enabled disable it. */ - if (kchan->enabled == 1) { - ret = kernel_disable_channel(kchan); - if (ret < 0 && ret != -EEXIST) { - ret = LTTNG_ERR_KERN_CHAN_DISABLE_FAIL; - goto error; - } - } - - ret = LTTNG_OK; - -error: - return ret; -} - -/* - * Enable kernel channel of the kernel session. - */ -int channel_kernel_enable(struct ltt_kernel_session *ksession, - struct ltt_kernel_channel *kchan) -{ - int ret; - - LTTNG_ASSERT(ksession); - LTTNG_ASSERT(kchan); - - if (kchan->enabled == 0) { - ret = kernel_enable_channel(kchan); - if (ret < 0) { - ret = LTTNG_ERR_KERN_CHAN_ENABLE_FAIL; - goto error; - } - } else { - ret = LTTNG_ERR_KERN_CHAN_EXIST; - goto error; - } - - ret = LTTNG_OK; - -error: - return ret; -} - -static int channel_validate(struct lttng_channel *attr) -{ - /* - * The ringbuffer (both in user space and kernel) behaves badly - * in overwrite mode and with less than 2 subbuffers so block it - * right away and send back an invalid attribute error. - */ - if (attr->attr.overwrite && attr->attr.num_subbuf < 2) { - return -1; - } - return 0; -} - -static int channel_validate_kernel(struct lttng_channel *attr) -{ - /* Kernel channels do not support blocking timeout. */ - if (((struct lttng_channel_extended *)attr->attr.extended.ptr)->blocking_timeout) { - return -1; - } - return 0; -} - -/* - * Create kernel channel of the kernel session and notify kernel thread. - */ -int channel_kernel_create(struct ltt_kernel_session *ksession, - struct lttng_channel *attr, int kernel_pipe) -{ - int ret; - struct lttng_channel *defattr = NULL; - - LTTNG_ASSERT(ksession); - - /* Creating channel attributes if needed */ - if (attr == NULL) { - defattr = channel_new_default_attr(LTTNG_DOMAIN_KERNEL, - LTTNG_BUFFER_GLOBAL); - if (defattr == NULL) { - ret = LTTNG_ERR_FATAL; - goto error; - } - attr = defattr; - } - - /* - * Set the overwrite mode for this channel based on the session - * type unless the client explicitly overrides the channel mode. - */ - if (attr->attr.overwrite == DEFAULT_CHANNEL_OVERWRITE) { - attr->attr.overwrite = !!ksession->snapshot_mode; - } - - /* Validate common channel properties. */ - if (channel_validate(attr) < 0) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - if (channel_validate_kernel(attr) < 0) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - /* Channel not found, creating it */ - ret = kernel_create_channel(ksession, attr); - if (ret < 0) { - ret = LTTNG_ERR_KERN_CHAN_FAIL; - goto error; - } - - /* Notify kernel thread that there is a new channel */ - ret = notify_thread_pipe(kernel_pipe); - if (ret < 0) { - ret = LTTNG_ERR_FATAL; - goto error; - } - - ret = LTTNG_OK; -error: - channel_attr_destroy(defattr); - return ret; -} - -/* - * Enable UST channel for session and domain. - */ -int channel_ust_enable(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan) -{ - int ret = LTTNG_OK; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(uchan); - - /* If already enabled, everything is OK */ - if (uchan->enabled) { - DBG3("Channel %s already enabled. Skipping", uchan->name); - ret = LTTNG_ERR_UST_CHAN_EXIST; - goto end; - } else { - uchan->enabled = 1; - DBG2("Channel %s enabled successfully", uchan->name); - } - - if (!usess->active) { - /* - * The channel will be activated against the apps - * when the session is started as part of the - * application channel "synchronize" operation. - */ - goto end; - } - - DBG2("Channel %s being enabled in UST domain", uchan->name); - - /* - * Enable channel for UST global domain on all applications. Ignore return - * value here since whatever error we got, it means that the channel was - * not created on one or many registered applications and we can not report - * this to the user yet. However, at this stage, the channel was - * successfully created on the session daemon side so the enable-channel - * command is a success. - */ - (void) ust_app_enable_channel_glb(usess, uchan); - - -end: - return ret; -} - -/* - * Create UST channel for session and domain. - */ -int channel_ust_create(struct ltt_ust_session *usess, - struct lttng_channel *attr, enum lttng_buffer_type type) -{ - int ret = LTTNG_OK; - struct ltt_ust_channel *uchan = NULL; - struct lttng_channel *defattr = NULL; - enum lttng_domain_type domain = LTTNG_DOMAIN_UST; - bool chan_published = false; - - LTTNG_ASSERT(usess); - - /* Creating channel attributes if needed */ - if (attr == NULL) { - defattr = channel_new_default_attr(LTTNG_DOMAIN_UST, type); - if (defattr == NULL) { - ret = LTTNG_ERR_FATAL; - goto error; - } - attr = defattr; - } else { - /* - * HACK: Set the channel's subdomain (JUL, Log4j, Python, etc.) - * based on the default name. - */ - if (!strcmp(attr->name, DEFAULT_JUL_CHANNEL_NAME)) { - domain = LTTNG_DOMAIN_JUL; - } else if (!strcmp(attr->name, DEFAULT_LOG4J_CHANNEL_NAME)) { - domain = LTTNG_DOMAIN_LOG4J; - } else if (!strcmp(attr->name, DEFAULT_PYTHON_CHANNEL_NAME)) { - domain = LTTNG_DOMAIN_PYTHON; - } - } - - /* - * Set the overwrite mode for this channel based on the session - * type unless the client explicitly overrides the channel mode. - */ - if (attr->attr.overwrite == DEFAULT_CHANNEL_OVERWRITE) { - attr->attr.overwrite = !!usess->snapshot_mode; - } - - /* Enforce mmap output for snapshot sessions. */ - if (usess->snapshot_mode) { - attr->attr.output = LTTNG_EVENT_MMAP; - } - - /* Validate common channel properties. */ - if (channel_validate(attr) < 0) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - /* - * Validate UST buffer size and number of buffers: must both be power of 2 - * and nonzero. We validate right here for UST, because applications will - * not report the error to the user (unlike kernel tracing). - */ - if (!attr->attr.subbuf_size || - (attr->attr.subbuf_size & (attr->attr.subbuf_size - 1))) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - /* - * Invalid subbuffer size if it's lower then the page size. - */ - if (attr->attr.subbuf_size < the_page_size) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - if (!attr->attr.num_subbuf || - (attr->attr.num_subbuf & (attr->attr.num_subbuf - 1))) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - if (attr->attr.output != LTTNG_EVENT_MMAP) { - ret = LTTNG_ERR_NOT_SUPPORTED; - goto error; - } - - /* - * The tracefile_size should not be < to the subbuf_size, otherwise - * we won't be able to write the packets on disk - */ - if ((attr->attr.tracefile_size > 0) && - (attr->attr.tracefile_size < attr->attr.subbuf_size)) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - /* Validate buffer type. */ - switch (type) { - case LTTNG_BUFFER_PER_PID: - break; - case LTTNG_BUFFER_PER_UID: - break; - default: - ret = LTTNG_ERR_BUFFER_NOT_SUPPORTED; - goto error; - } - - /* Create UST channel */ - uchan = trace_ust_create_channel(attr, domain); - if (uchan == NULL) { - ret = LTTNG_ERR_FATAL; - goto error; - } - - uchan->enabled = 1; - if (trace_ust_is_max_id(usess->used_channel_id)) { - ret = LTTNG_ERR_UST_CHAN_FAIL; - goto error; - } - uchan->id = trace_ust_get_next_chan_id(usess); - - DBG2("Channel %s is being created for UST with buffer %d and id %" PRIu64, - uchan->name, type, uchan->id); - - /* Flag session buffer type. */ - if (!usess->buffer_type_changed) { - usess->buffer_type = type; - usess->buffer_type_changed = 1; - } else if (usess->buffer_type != type) { - /* Buffer type was already set. Refuse to create channel. */ - ret = LTTNG_ERR_BUFFER_TYPE_MISMATCH; - goto error_free_chan; - } - - /* Adding the channel to the channel hash table. */ - rcu_read_lock(); - if (strncmp(uchan->name, DEFAULT_METADATA_NAME, - sizeof(uchan->name))) { - lttng_ht_add_unique_str(usess->domain_global.channels, &uchan->node); - chan_published = true; - } else { - /* - * Copy channel attribute to session if this is metadata so if NO - * application exists we can access that data in the shadow copy during - * the global update of newly registered application. - */ - memcpy(&usess->metadata_attr, &uchan->attr, - sizeof(usess->metadata_attr)); - } - rcu_read_unlock(); - - DBG2("Channel %s created successfully", uchan->name); - if (domain != LTTNG_DOMAIN_UST) { - struct agent *agt = trace_ust_find_agent(usess, domain); - - if (!agt) { - agt = agent_create(domain); - if (!agt) { - ret = LTTNG_ERR_NOMEM; - goto error_remove_chan; - } - agent_add(agt, usess->agents); - } - } - - channel_attr_destroy(defattr); - return LTTNG_OK; - -error_remove_chan: - if (chan_published) { - trace_ust_delete_channel(usess->domain_global.channels, uchan); - } -error_free_chan: - trace_ust_destroy_channel(uchan); -error: - channel_attr_destroy(defattr); - return ret; -} - -/* - * Disable UST channel for session and domain. - */ -int channel_ust_disable(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan) -{ - int ret = LTTNG_OK; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(uchan); - - /* Already disabled */ - if (uchan->enabled == 0) { - DBG2("Channel UST %s already disabled", uchan->name); - goto end; - } - - uchan->enabled = 0; - - /* - * If session is inactive we don't notify the tracer right away. We - * wait for the next synchronization. - */ - if (!usess->active) { - goto end; - } - - DBG2("Channel %s being disabled in UST global domain", uchan->name); - /* Disable channel for global domain */ - ret = ust_app_disable_channel_glb(usess, uchan); - if (ret < 0 && ret != -LTTNG_UST_ERR_EXIST) { - ret = LTTNG_ERR_UST_CHAN_DISABLE_FAIL; - goto error; - } - - DBG2("Channel %s disabled successfully", uchan->name); - - return LTTNG_OK; - -end: -error: - return ret; -} diff --git a/src/bin/lttng-sessiond/channel.cpp b/src/bin/lttng-sessiond/channel.cpp new file mode 100644 index 000000000..0f150972c --- /dev/null +++ b/src/bin/lttng-sessiond/channel.cpp @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2016 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include + +#include +#include +#include + +#include "channel.h" +#include "lttng-sessiond.h" +#include "kernel.h" +#include "lttng-ust-ctl.h" +#include "lttng-ust-error.h" +#include "utils.h" +#include "ust-app.h" +#include "agent.h" + +/* + * Return allocated channel attributes. + */ +struct lttng_channel *channel_new_default_attr(int dom, + enum lttng_buffer_type type) +{ + struct lttng_channel *chan; + const char *channel_name = DEFAULT_CHANNEL_NAME; + struct lttng_channel_extended *extended_attr = NULL; + + chan = (lttng_channel *) zmalloc(sizeof(struct lttng_channel)); + if (chan == NULL) { + PERROR("zmalloc channel init"); + goto error_alloc; + } + + extended_attr = (lttng_channel_extended *) zmalloc(sizeof(struct lttng_channel_extended)); + if (!extended_attr) { + PERROR("zmalloc channel extended init"); + goto error; + } + + chan->attr.extended.ptr = extended_attr; + + /* Same for all domains. */ + chan->attr.overwrite = DEFAULT_CHANNEL_OVERWRITE; + chan->attr.tracefile_size = DEFAULT_CHANNEL_TRACEFILE_SIZE; + chan->attr.tracefile_count = DEFAULT_CHANNEL_TRACEFILE_COUNT; + + switch (dom) { + case LTTNG_DOMAIN_KERNEL: + LTTNG_ASSERT(type == LTTNG_BUFFER_GLOBAL); + chan->attr.subbuf_size = + default_get_kernel_channel_subbuf_size(); + chan->attr.num_subbuf = DEFAULT_KERNEL_CHANNEL_SUBBUF_NUM; + chan->attr.output = DEFAULT_KERNEL_CHANNEL_OUTPUT; + chan->attr.switch_timer_interval = DEFAULT_KERNEL_CHANNEL_SWITCH_TIMER; + chan->attr.read_timer_interval = DEFAULT_KERNEL_CHANNEL_READ_TIMER; + chan->attr.live_timer_interval = DEFAULT_KERNEL_CHANNEL_LIVE_TIMER; + extended_attr->blocking_timeout = DEFAULT_KERNEL_CHANNEL_BLOCKING_TIMEOUT; + extended_attr->monitor_timer_interval = + DEFAULT_KERNEL_CHANNEL_MONITOR_TIMER; + break; + case LTTNG_DOMAIN_JUL: + channel_name = DEFAULT_JUL_CHANNEL_NAME; + goto common_ust; + case LTTNG_DOMAIN_LOG4J: + channel_name = DEFAULT_LOG4J_CHANNEL_NAME; + goto common_ust; + case LTTNG_DOMAIN_PYTHON: + channel_name = DEFAULT_PYTHON_CHANNEL_NAME; + goto common_ust; + case LTTNG_DOMAIN_UST: +common_ust: + switch (type) { + case LTTNG_BUFFER_PER_UID: + chan->attr.subbuf_size = default_get_ust_uid_channel_subbuf_size(); + chan->attr.num_subbuf = DEFAULT_UST_UID_CHANNEL_SUBBUF_NUM; + chan->attr.output = DEFAULT_UST_UID_CHANNEL_OUTPUT; + chan->attr.switch_timer_interval = + DEFAULT_UST_UID_CHANNEL_SWITCH_TIMER; + chan->attr.read_timer_interval = + DEFAULT_UST_UID_CHANNEL_READ_TIMER; + chan->attr.live_timer_interval = + DEFAULT_UST_UID_CHANNEL_LIVE_TIMER; + extended_attr->blocking_timeout = DEFAULT_UST_UID_CHANNEL_BLOCKING_TIMEOUT; + extended_attr->monitor_timer_interval = + DEFAULT_UST_UID_CHANNEL_MONITOR_TIMER; + break; + case LTTNG_BUFFER_PER_PID: + default: + chan->attr.subbuf_size = default_get_ust_pid_channel_subbuf_size(); + chan->attr.num_subbuf = DEFAULT_UST_PID_CHANNEL_SUBBUF_NUM; + chan->attr.output = DEFAULT_UST_PID_CHANNEL_OUTPUT; + chan->attr.switch_timer_interval = + DEFAULT_UST_PID_CHANNEL_SWITCH_TIMER; + chan->attr.read_timer_interval = + DEFAULT_UST_PID_CHANNEL_READ_TIMER; + chan->attr.live_timer_interval = + DEFAULT_UST_PID_CHANNEL_LIVE_TIMER; + extended_attr->blocking_timeout = DEFAULT_UST_PID_CHANNEL_BLOCKING_TIMEOUT; + extended_attr->monitor_timer_interval = + DEFAULT_UST_PID_CHANNEL_MONITOR_TIMER; + break; + } + break; + default: + goto error; /* Not implemented */ + } + + if (snprintf(chan->name, sizeof(chan->name), "%s", + channel_name) < 0) { + PERROR("snprintf default channel name"); + goto error; + } + return chan; + +error: + free(extended_attr); + free(chan); +error_alloc: + return NULL; +} + +void channel_attr_destroy(struct lttng_channel *channel) +{ + if (!channel) { + return; + } + free(channel->attr.extended.ptr); + free(channel); +} + +/* + * Disable kernel channel of the kernel session. + */ +int channel_kernel_disable(struct ltt_kernel_session *ksession, + char *channel_name) +{ + int ret; + struct ltt_kernel_channel *kchan; + + LTTNG_ASSERT(ksession); + LTTNG_ASSERT(channel_name); + + kchan = trace_kernel_get_channel_by_name(channel_name, ksession); + if (kchan == NULL) { + ret = LTTNG_ERR_KERN_CHAN_NOT_FOUND; + goto error; + } + + /* Only if channel is enabled disable it. */ + if (kchan->enabled == 1) { + ret = kernel_disable_channel(kchan); + if (ret < 0 && ret != -EEXIST) { + ret = LTTNG_ERR_KERN_CHAN_DISABLE_FAIL; + goto error; + } + } + + ret = LTTNG_OK; + +error: + return ret; +} + +/* + * Enable kernel channel of the kernel session. + */ +int channel_kernel_enable(struct ltt_kernel_session *ksession, + struct ltt_kernel_channel *kchan) +{ + int ret; + + LTTNG_ASSERT(ksession); + LTTNG_ASSERT(kchan); + + if (kchan->enabled == 0) { + ret = kernel_enable_channel(kchan); + if (ret < 0) { + ret = LTTNG_ERR_KERN_CHAN_ENABLE_FAIL; + goto error; + } + } else { + ret = LTTNG_ERR_KERN_CHAN_EXIST; + goto error; + } + + ret = LTTNG_OK; + +error: + return ret; +} + +static int channel_validate(struct lttng_channel *attr) +{ + /* + * The ringbuffer (both in user space and kernel) behaves badly + * in overwrite mode and with less than 2 subbuffers so block it + * right away and send back an invalid attribute error. + */ + if (attr->attr.overwrite && attr->attr.num_subbuf < 2) { + return -1; + } + return 0; +} + +static int channel_validate_kernel(struct lttng_channel *attr) +{ + /* Kernel channels do not support blocking timeout. */ + if (((struct lttng_channel_extended *)attr->attr.extended.ptr)->blocking_timeout) { + return -1; + } + return 0; +} + +/* + * Create kernel channel of the kernel session and notify kernel thread. + */ +int channel_kernel_create(struct ltt_kernel_session *ksession, + struct lttng_channel *attr, int kernel_pipe) +{ + int ret; + struct lttng_channel *defattr = NULL; + + LTTNG_ASSERT(ksession); + + /* Creating channel attributes if needed */ + if (attr == NULL) { + defattr = channel_new_default_attr(LTTNG_DOMAIN_KERNEL, + LTTNG_BUFFER_GLOBAL); + if (defattr == NULL) { + ret = LTTNG_ERR_FATAL; + goto error; + } + attr = defattr; + } + + /* + * Set the overwrite mode for this channel based on the session + * type unless the client explicitly overrides the channel mode. + */ + if (attr->attr.overwrite == DEFAULT_CHANNEL_OVERWRITE) { + attr->attr.overwrite = !!ksession->snapshot_mode; + } + + /* Validate common channel properties. */ + if (channel_validate(attr) < 0) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + if (channel_validate_kernel(attr) < 0) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + /* Channel not found, creating it */ + ret = kernel_create_channel(ksession, attr); + if (ret < 0) { + ret = LTTNG_ERR_KERN_CHAN_FAIL; + goto error; + } + + /* Notify kernel thread that there is a new channel */ + ret = notify_thread_pipe(kernel_pipe); + if (ret < 0) { + ret = LTTNG_ERR_FATAL; + goto error; + } + + ret = LTTNG_OK; +error: + channel_attr_destroy(defattr); + return ret; +} + +/* + * Enable UST channel for session and domain. + */ +int channel_ust_enable(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan) +{ + int ret = LTTNG_OK; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(uchan); + + /* If already enabled, everything is OK */ + if (uchan->enabled) { + DBG3("Channel %s already enabled. Skipping", uchan->name); + ret = LTTNG_ERR_UST_CHAN_EXIST; + goto end; + } else { + uchan->enabled = 1; + DBG2("Channel %s enabled successfully", uchan->name); + } + + if (!usess->active) { + /* + * The channel will be activated against the apps + * when the session is started as part of the + * application channel "synchronize" operation. + */ + goto end; + } + + DBG2("Channel %s being enabled in UST domain", uchan->name); + + /* + * Enable channel for UST global domain on all applications. Ignore return + * value here since whatever error we got, it means that the channel was + * not created on one or many registered applications and we can not report + * this to the user yet. However, at this stage, the channel was + * successfully created on the session daemon side so the enable-channel + * command is a success. + */ + (void) ust_app_enable_channel_glb(usess, uchan); + + +end: + return ret; +} + +/* + * Create UST channel for session and domain. + */ +int channel_ust_create(struct ltt_ust_session *usess, + struct lttng_channel *attr, enum lttng_buffer_type type) +{ + int ret = LTTNG_OK; + struct ltt_ust_channel *uchan = NULL; + struct lttng_channel *defattr = NULL; + enum lttng_domain_type domain = LTTNG_DOMAIN_UST; + bool chan_published = false; + + LTTNG_ASSERT(usess); + + /* Creating channel attributes if needed */ + if (attr == NULL) { + defattr = channel_new_default_attr(LTTNG_DOMAIN_UST, type); + if (defattr == NULL) { + ret = LTTNG_ERR_FATAL; + goto error; + } + attr = defattr; + } else { + /* + * HACK: Set the channel's subdomain (JUL, Log4j, Python, etc.) + * based on the default name. + */ + if (!strcmp(attr->name, DEFAULT_JUL_CHANNEL_NAME)) { + domain = LTTNG_DOMAIN_JUL; + } else if (!strcmp(attr->name, DEFAULT_LOG4J_CHANNEL_NAME)) { + domain = LTTNG_DOMAIN_LOG4J; + } else if (!strcmp(attr->name, DEFAULT_PYTHON_CHANNEL_NAME)) { + domain = LTTNG_DOMAIN_PYTHON; + } + } + + /* + * Set the overwrite mode for this channel based on the session + * type unless the client explicitly overrides the channel mode. + */ + if (attr->attr.overwrite == DEFAULT_CHANNEL_OVERWRITE) { + attr->attr.overwrite = !!usess->snapshot_mode; + } + + /* Enforce mmap output for snapshot sessions. */ + if (usess->snapshot_mode) { + attr->attr.output = LTTNG_EVENT_MMAP; + } + + /* Validate common channel properties. */ + if (channel_validate(attr) < 0) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + /* + * Validate UST buffer size and number of buffers: must both be power of 2 + * and nonzero. We validate right here for UST, because applications will + * not report the error to the user (unlike kernel tracing). + */ + if (!attr->attr.subbuf_size || + (attr->attr.subbuf_size & (attr->attr.subbuf_size - 1))) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + /* + * Invalid subbuffer size if it's lower then the page size. + */ + if (attr->attr.subbuf_size < the_page_size) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + if (!attr->attr.num_subbuf || + (attr->attr.num_subbuf & (attr->attr.num_subbuf - 1))) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + if (attr->attr.output != LTTNG_EVENT_MMAP) { + ret = LTTNG_ERR_NOT_SUPPORTED; + goto error; + } + + /* + * The tracefile_size should not be < to the subbuf_size, otherwise + * we won't be able to write the packets on disk + */ + if ((attr->attr.tracefile_size > 0) && + (attr->attr.tracefile_size < attr->attr.subbuf_size)) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + /* Validate buffer type. */ + switch (type) { + case LTTNG_BUFFER_PER_PID: + break; + case LTTNG_BUFFER_PER_UID: + break; + default: + ret = LTTNG_ERR_BUFFER_NOT_SUPPORTED; + goto error; + } + + /* Create UST channel */ + uchan = trace_ust_create_channel(attr, domain); + if (uchan == NULL) { + ret = LTTNG_ERR_FATAL; + goto error; + } + + uchan->enabled = 1; + if (trace_ust_is_max_id(usess->used_channel_id)) { + ret = LTTNG_ERR_UST_CHAN_FAIL; + goto error; + } + uchan->id = trace_ust_get_next_chan_id(usess); + + DBG2("Channel %s is being created for UST with buffer %d and id %" PRIu64, + uchan->name, type, uchan->id); + + /* Flag session buffer type. */ + if (!usess->buffer_type_changed) { + usess->buffer_type = type; + usess->buffer_type_changed = 1; + } else if (usess->buffer_type != type) { + /* Buffer type was already set. Refuse to create channel. */ + ret = LTTNG_ERR_BUFFER_TYPE_MISMATCH; + goto error_free_chan; + } + + /* Adding the channel to the channel hash table. */ + rcu_read_lock(); + if (strncmp(uchan->name, DEFAULT_METADATA_NAME, + sizeof(uchan->name))) { + lttng_ht_add_unique_str(usess->domain_global.channels, &uchan->node); + chan_published = true; + } else { + /* + * Copy channel attribute to session if this is metadata so if NO + * application exists we can access that data in the shadow copy during + * the global update of newly registered application. + */ + memcpy(&usess->metadata_attr, &uchan->attr, + sizeof(usess->metadata_attr)); + } + rcu_read_unlock(); + + DBG2("Channel %s created successfully", uchan->name); + if (domain != LTTNG_DOMAIN_UST) { + struct agent *agt = trace_ust_find_agent(usess, domain); + + if (!agt) { + agt = agent_create(domain); + if (!agt) { + ret = LTTNG_ERR_NOMEM; + goto error_remove_chan; + } + agent_add(agt, usess->agents); + } + } + + channel_attr_destroy(defattr); + return LTTNG_OK; + +error_remove_chan: + if (chan_published) { + trace_ust_delete_channel(usess->domain_global.channels, uchan); + } +error_free_chan: + trace_ust_destroy_channel(uchan); +error: + channel_attr_destroy(defattr); + return ret; +} + +/* + * Disable UST channel for session and domain. + */ +int channel_ust_disable(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan) +{ + int ret = LTTNG_OK; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(uchan); + + /* Already disabled */ + if (uchan->enabled == 0) { + DBG2("Channel UST %s already disabled", uchan->name); + goto end; + } + + uchan->enabled = 0; + + /* + * If session is inactive we don't notify the tracer right away. We + * wait for the next synchronization. + */ + if (!usess->active) { + goto end; + } + + DBG2("Channel %s being disabled in UST global domain", uchan->name); + /* Disable channel for global domain */ + ret = ust_app_disable_channel_glb(usess, uchan); + if (ret < 0 && ret != -LTTNG_UST_ERR_EXIST) { + ret = LTTNG_ERR_UST_CHAN_DISABLE_FAIL; + goto error; + } + + DBG2("Channel %s disabled successfully", uchan->name); + + return LTTNG_OK; + +end: +error: + return ret; +} diff --git a/src/bin/lttng-sessiond/clear.c b/src/bin/lttng-sessiond/clear.c deleted file mode 100644 index e699caffc..000000000 --- a/src/bin/lttng-sessiond/clear.c +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2019 Jonathan Rajotte - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include - -#include -#include -#include - -#include "clear.h" -#include "session.h" -#include "ust-app.h" -#include "kernel.h" -#include "cmd.h" - -struct cmd_clear_session_reply_context { - int reply_sock_fd; -}; - -static -void cmd_clear_session_reply(const struct ltt_session *session, - void *_reply_context) -{ - int ret; - ssize_t comm_ret; - const struct cmd_clear_session_reply_context *reply_context = - _reply_context; - struct lttcomm_lttng_msg llm = { - .cmd_type = LTTNG_CLEAR_SESSION, - .ret_code = LTTNG_OK, - .pid = UINT32_MAX, - .cmd_header_size = 0, - .data_size = 0, - }; - - DBG("End of clear command: replying to client"); - comm_ret = lttcomm_send_unix_sock(reply_context->reply_sock_fd, - &llm, sizeof(llm)); - if (comm_ret != (ssize_t) sizeof(llm)) { - ERR("Failed to send result of session \"%s\" clear to client", - session->name); - } - ret = close(reply_context->reply_sock_fd); - if (ret) { - PERROR("Failed to close client socket in deferred session clear reply"); - } - free(_reply_context); -} - -int cmd_clear_session(struct ltt_session *session, int *sock_fd) -{ - int ret = LTTNG_OK; - struct cmd_clear_session_reply_context *reply_context = NULL; - bool session_was_active = false; - struct ltt_kernel_session *ksession; - struct ltt_ust_session *usess; - - ksession = session->kernel_session; - usess = session->ust_session; - - if (sock_fd) { - reply_context = zmalloc(sizeof(*reply_context)); - if (!reply_context) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - reply_context->reply_sock_fd = *sock_fd; - } - - if (!session->has_been_started) { - /* - * Nothing to be cleared, this is not an error: there is - * indeed nothing to do, and there is no reason why we - * should return an error to the user. - */ - goto end; - } - - /* Unsupported feature in lttng-relayd before 2.11. */ - if (session->consumer->type == CONSUMER_DST_NET && - (session->consumer->relay_major_version == 2 && - session->consumer->relay_minor_version < 12)) { - ret = LTTNG_ERR_CLEAR_NOT_AVAILABLE_RELAY; - goto end; - } - if (session->consumer->type == CONSUMER_DST_NET && - !session->consumer->relay_allows_clear) { - ret = LTTNG_ERR_CLEAR_NOT_AVAILABLE_RELAY; - goto end; - } - - /* - * After a stop followed by a clear, all subsequent clear are - * effect-less until start is performed. - */ - if (session->cleared_after_last_stop) { - ret = LTTNG_OK; - goto end; - } - - /* - * After a stop followed by a rotation, all subsequent clear are effect-less - * until start is performed. - */ - if (session->rotated_after_last_stop) { - ret = LTTNG_OK; - goto end; - } - - session_was_active = session->active; - if (session_was_active) { - ret = stop_kernel_session(ksession); - if (ret != LTTNG_OK) { - goto end; - } - if (usess && usess->active) { - ret = ust_app_stop_trace_all(usess); - if (ret < 0) { - ret = LTTNG_ERR_UST_STOP_FAIL; - goto end; - } - } - } - - /* - * Clear active kernel and UST session buffers. - */ - if (session->kernel_session) { - ret = kernel_clear_session(session); - if (ret != LTTNG_OK) { - goto end; - } - } - if (session->ust_session) { - ret = ust_app_clear_session(session); - if (ret != LTTNG_OK) { - goto end; - } - } - - if (session->output_traces) { - /* - * Use rotation to delete local and remote stream files. - */ - if (reply_context) { - ret = session_add_clear_notifier(session, - cmd_clear_session_reply, - (void *) reply_context); - if (ret) { - ret = LTTNG_ERR_FATAL; - goto end; - } - /* - * On success, ownership of reply_context has been - * passed to session_add_clear_notifier(). - */ - reply_context = NULL; - *sock_fd = -1; - } - ret = cmd_rotate_session(session, NULL, true, - LTTNG_TRACE_CHUNK_COMMAND_TYPE_DELETE); - if (ret != LTTNG_OK) { - goto end; - } - } - if (!session->active) { - session->cleared_after_last_stop = true; - } - if (session_was_active) { - /* Kernel tracing */ - if (ksession != NULL) { - DBG("Start kernel tracing session \"%s\"", - session->name); - ret = start_kernel_session(ksession); - if (ret != LTTNG_OK) { - goto end; - } - } - - /* Flag session that trace should start automatically */ - if (usess) { - int int_ret = ust_app_start_trace_all(usess); - - if (int_ret < 0) { - ret = LTTNG_ERR_UST_START_FAIL; - goto end; - } - } - - /* - * Open a packet in every stream of the session to ensure that - * viewers can correctly identify the boundaries of the periods - * during which tracing was active for this session. - */ - ret = session_open_packets(session); - if (ret != LTTNG_OK) { - goto end; - } - } - ret = LTTNG_OK; -end: - free(reply_context); - return ret; -} diff --git a/src/bin/lttng-sessiond/clear.cpp b/src/bin/lttng-sessiond/clear.cpp new file mode 100644 index 000000000..1239c5f24 --- /dev/null +++ b/src/bin/lttng-sessiond/clear.cpp @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2019 Jonathan Rajotte + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include + +#include +#include +#include + +#include "clear.h" +#include "session.h" +#include "ust-app.h" +#include "kernel.h" +#include "cmd.h" + +struct cmd_clear_session_reply_context { + int reply_sock_fd; +}; + +static +void cmd_clear_session_reply(const struct ltt_session *session, + void *_reply_context) +{ + int ret; + ssize_t comm_ret; + const struct cmd_clear_session_reply_context *reply_context = + (cmd_clear_session_reply_context *) _reply_context; + struct lttcomm_lttng_msg llm = { + .cmd_type = LTTNG_CLEAR_SESSION, + .ret_code = LTTNG_OK, + .pid = UINT32_MAX, + .cmd_header_size = 0, + .data_size = 0, + }; + + DBG("End of clear command: replying to client"); + comm_ret = lttcomm_send_unix_sock(reply_context->reply_sock_fd, + &llm, sizeof(llm)); + if (comm_ret != (ssize_t) sizeof(llm)) { + ERR("Failed to send result of session \"%s\" clear to client", + session->name); + } + ret = close(reply_context->reply_sock_fd); + if (ret) { + PERROR("Failed to close client socket in deferred session clear reply"); + } + free(_reply_context); +} + +int cmd_clear_session(struct ltt_session *session, int *sock_fd) +{ + int ret = LTTNG_OK; + struct cmd_clear_session_reply_context *reply_context = NULL; + bool session_was_active = false; + struct ltt_kernel_session *ksession; + struct ltt_ust_session *usess; + + ksession = session->kernel_session; + usess = session->ust_session; + + if (sock_fd) { + reply_context = (cmd_clear_session_reply_context *) zmalloc(sizeof(*reply_context)); + if (!reply_context) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + reply_context->reply_sock_fd = *sock_fd; + } + + if (!session->has_been_started) { + /* + * Nothing to be cleared, this is not an error: there is + * indeed nothing to do, and there is no reason why we + * should return an error to the user. + */ + goto end; + } + + /* Unsupported feature in lttng-relayd before 2.11. */ + if (session->consumer->type == CONSUMER_DST_NET && + (session->consumer->relay_major_version == 2 && + session->consumer->relay_minor_version < 12)) { + ret = LTTNG_ERR_CLEAR_NOT_AVAILABLE_RELAY; + goto end; + } + if (session->consumer->type == CONSUMER_DST_NET && + !session->consumer->relay_allows_clear) { + ret = LTTNG_ERR_CLEAR_NOT_AVAILABLE_RELAY; + goto end; + } + + /* + * After a stop followed by a clear, all subsequent clear are + * effect-less until start is performed. + */ + if (session->cleared_after_last_stop) { + ret = LTTNG_OK; + goto end; + } + + /* + * After a stop followed by a rotation, all subsequent clear are effect-less + * until start is performed. + */ + if (session->rotated_after_last_stop) { + ret = LTTNG_OK; + goto end; + } + + session_was_active = session->active; + if (session_was_active) { + ret = stop_kernel_session(ksession); + if (ret != LTTNG_OK) { + goto end; + } + if (usess && usess->active) { + ret = ust_app_stop_trace_all(usess); + if (ret < 0) { + ret = LTTNG_ERR_UST_STOP_FAIL; + goto end; + } + } + } + + /* + * Clear active kernel and UST session buffers. + */ + if (session->kernel_session) { + ret = kernel_clear_session(session); + if (ret != LTTNG_OK) { + goto end; + } + } + if (session->ust_session) { + ret = ust_app_clear_session(session); + if (ret != LTTNG_OK) { + goto end; + } + } + + if (session->output_traces) { + /* + * Use rotation to delete local and remote stream files. + */ + if (reply_context) { + ret = session_add_clear_notifier(session, + cmd_clear_session_reply, + (void *) reply_context); + if (ret) { + ret = LTTNG_ERR_FATAL; + goto end; + } + /* + * On success, ownership of reply_context has been + * passed to session_add_clear_notifier(). + */ + reply_context = NULL; + *sock_fd = -1; + } + ret = cmd_rotate_session(session, NULL, true, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_DELETE); + if (ret != LTTNG_OK) { + goto end; + } + } + if (!session->active) { + session->cleared_after_last_stop = true; + } + if (session_was_active) { + /* Kernel tracing */ + if (ksession != NULL) { + DBG("Start kernel tracing session \"%s\"", + session->name); + ret = start_kernel_session(ksession); + if (ret != LTTNG_OK) { + goto end; + } + } + + /* Flag session that trace should start automatically */ + if (usess) { + int int_ret = ust_app_start_trace_all(usess); + + if (int_ret < 0) { + ret = LTTNG_ERR_UST_START_FAIL; + goto end; + } + } + + /* + * Open a packet in every stream of the session to ensure that + * viewers can correctly identify the boundaries of the periods + * during which tracing was active for this session. + */ + ret = session_open_packets(session); + if (ret != LTTNG_OK) { + goto end; + } + } + ret = LTTNG_OK; +end: + free(reply_context); + return ret; +} diff --git a/src/bin/lttng-sessiond/client.c b/src/bin/lttng-sessiond/client.c deleted file mode 100644 index ed9f2af3e..000000000 --- a/src/bin/lttng-sessiond/client.c +++ /dev/null @@ -1,2906 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "common/buffer-view.h" -#include "common/compat/socket.h" -#include "common/dynamic-array.h" -#include "common/dynamic-buffer.h" -#include "common/fd-handle.h" -#include "common/payload-view.h" -#include "common/payload.h" -#include "common/sessiond-comm/sessiond-comm.h" -#include "lttng/lttng-error.h" -#include "lttng/tracker.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "agent-thread.h" -#include "clear.h" -#include "client.h" -#include "cmd.h" -#include "health-sessiond.h" -#include "kernel.h" -#include "lttng-sessiond.h" -#include "manage-consumer.h" -#include "save.h" -#include "testpoint.h" -#include "utils.h" - -static bool is_root; - -static struct thread_state { - sem_t ready; - bool running; - int client_sock; -} thread_state; - -static void set_thread_status(bool running) -{ - DBG("Marking client thread's state as %s", running ? "running" : "error"); - thread_state.running = running; - sem_post(&thread_state.ready); -} - -static bool wait_thread_status(void) -{ - DBG("Waiting for client thread to be ready"); - sem_wait(&thread_state.ready); - if (thread_state.running) { - DBG("Client thread is ready"); - } else { - ERR("Initialization of client thread failed"); - } - - return thread_state.running; -} - -/* - * Setup the outgoing data buffer for the response (llm) by allocating the - * right amount of memory and copying the original information from the lsm - * structure. - * - * Return 0 on success, negative value on error. - */ -static int setup_lttng_msg(struct command_ctx *cmd_ctx, - const void *payload_buf, size_t payload_len, - const void *cmd_header_buf, size_t cmd_header_len) -{ - int ret = 0; - const size_t header_len = sizeof(struct lttcomm_lttng_msg); - const size_t total_msg_size = header_len + cmd_header_len + payload_len; - const struct lttcomm_lttng_msg llm = { - .cmd_type = cmd_ctx->lsm.cmd_type, - .pid = cmd_ctx->lsm.domain.attr.pid, - .cmd_header_size = cmd_header_len, - .data_size = payload_len, - }; - - ret = lttng_dynamic_buffer_set_size(&cmd_ctx->reply_payload.buffer, 0); - if (ret) { - goto end; - } - - lttng_dynamic_pointer_array_clear(&cmd_ctx->reply_payload._fd_handles); - - cmd_ctx->lttng_msg_size = total_msg_size; - - /* Append reply header. */ - ret = lttng_dynamic_buffer_append( - &cmd_ctx->reply_payload.buffer, &llm, sizeof(llm)); - if (ret) { - goto end; - } - - /* Append command header. */ - if (cmd_header_len) { - ret = lttng_dynamic_buffer_append( - &cmd_ctx->reply_payload.buffer, cmd_header_buf, - cmd_header_len); - if (ret) { - goto end; - } - } - - /* Append payload. */ - if (payload_len) { - ret = lttng_dynamic_buffer_append( - &cmd_ctx->reply_payload.buffer, payload_buf, - payload_len); - if (ret) { - goto end; - } - } - -end: - return ret; -} - -static int setup_empty_lttng_msg(struct command_ctx *cmd_ctx) -{ - int ret; - const struct lttcomm_lttng_msg llm = {}; - - ret = lttng_dynamic_buffer_set_size(&cmd_ctx->reply_payload.buffer, 0); - if (ret) { - goto end; - } - - /* Append place-holder reply header. */ - ret = lttng_dynamic_buffer_append( - &cmd_ctx->reply_payload.buffer, &llm, sizeof(llm)); - if (ret) { - goto end; - } - - cmd_ctx->lttng_msg_size = sizeof(llm); -end: - return ret; -} - -static void update_lttng_msg(struct command_ctx *cmd_ctx, size_t cmd_header_len, - size_t payload_len) -{ - const size_t header_len = sizeof(struct lttcomm_lttng_msg); - const size_t total_msg_size = header_len + cmd_header_len + payload_len; - const struct lttcomm_lttng_msg llm = { - .cmd_type = cmd_ctx->lsm.cmd_type, - .pid = cmd_ctx->lsm.domain.attr.pid, - .cmd_header_size = cmd_header_len, - .data_size = payload_len, - }; - struct lttcomm_lttng_msg *p_llm; - - LTTNG_ASSERT(cmd_ctx->reply_payload.buffer.size >= sizeof(llm)); - - p_llm = (typeof(p_llm)) cmd_ctx->reply_payload.buffer.data; - - /* Update existing header. */ - memcpy(p_llm, &llm, sizeof(llm)); - - cmd_ctx->lttng_msg_size = total_msg_size; -} - -/* - * Start the thread_manage_consumer. This must be done after a lttng-consumerd - * exec or it will fail. - */ -static int spawn_consumer_thread(struct consumer_data *consumer_data) -{ - return launch_consumer_management_thread(consumer_data) ? 0 : -1; -} - -/* - * Fork and exec a consumer daemon (consumerd). - * - * Return pid if successful else -1. - */ -static pid_t spawn_consumerd(struct consumer_data *consumer_data) -{ - int ret; - pid_t pid; - const char *consumer_to_use; - const char *verbosity; - struct stat st; - - DBG("Spawning consumerd"); - - pid = fork(); - if (pid == 0) { - /* - * Exec consumerd. - */ - if (the_config.verbose_consumer) { - verbosity = "--verbose"; - } else if (lttng_opt_quiet) { - verbosity = "--quiet"; - } else { - verbosity = ""; - } - - switch (consumer_data->type) { - case LTTNG_CONSUMER_KERNEL: - /* - * Find out which consumerd to execute. We will first try the - * 64-bit path, then the sessiond's installation directory, and - * fallback on the 32-bit one, - */ - DBG3("Looking for a kernel consumer at these locations:"); - DBG3(" 1) %s", the_config.consumerd64_bin_path.value ? : "NULL"); - DBG3(" 2) %s/%s", INSTALL_BIN_PATH, DEFAULT_CONSUMERD_FILE); - DBG3(" 3) %s", the_config.consumerd32_bin_path.value ? : "NULL"); - if (stat(the_config.consumerd64_bin_path.value, &st) == 0) { - DBG3("Found location #1"); - consumer_to_use = the_config.consumerd64_bin_path.value; - } else if (stat(INSTALL_BIN_PATH "/" DEFAULT_CONSUMERD_FILE, &st) == 0) { - DBG3("Found location #2"); - consumer_to_use = INSTALL_BIN_PATH "/" DEFAULT_CONSUMERD_FILE; - } else if (the_config.consumerd32_bin_path.value && - stat(the_config.consumerd32_bin_path.value, &st) == 0) { - DBG3("Found location #3"); - consumer_to_use = the_config.consumerd32_bin_path.value; - } else { - DBG("Could not find any valid consumerd executable"); - ret = -EINVAL; - goto error; - } - DBG("Using kernel consumer at: %s", consumer_to_use); - (void) execl(consumer_to_use, "lttng-consumerd", - verbosity, "-k", "--consumerd-cmd-sock", - consumer_data->cmd_unix_sock_path, - "--consumerd-err-sock", - consumer_data->err_unix_sock_path, - "--group", - the_config.tracing_group_name.value, - NULL); - break; - case LTTNG_CONSUMER64_UST: - { - if (the_config.consumerd64_lib_dir.value) { - const char *tmp; - size_t tmplen; - char *tmpnew; - - tmp = lttng_secure_getenv("LD_LIBRARY_PATH"); - if (!tmp) { - tmp = ""; - } - tmplen = strlen(the_config.consumerd64_lib_dir.value) + 1 /* : */ + strlen(tmp); - tmpnew = zmalloc(tmplen + 1 /* \0 */); - if (!tmpnew) { - ret = -ENOMEM; - goto error; - } - strcat(tmpnew, the_config.consumerd64_lib_dir.value); - if (tmp[0] != '\0') { - strcat(tmpnew, ":"); - strcat(tmpnew, tmp); - } - ret = setenv("LD_LIBRARY_PATH", tmpnew, 1); - free(tmpnew); - if (ret) { - ret = -errno; - goto error; - } - } - DBG("Using 64-bit UST consumer at: %s", - the_config.consumerd64_bin_path.value); - (void) execl(the_config.consumerd64_bin_path.value, - "lttng-consumerd", verbosity, "-u", - "--consumerd-cmd-sock", - consumer_data->cmd_unix_sock_path, - "--consumerd-err-sock", - consumer_data->err_unix_sock_path, - "--group", - the_config.tracing_group_name.value, - NULL); - break; - } - case LTTNG_CONSUMER32_UST: - { - if (the_config.consumerd32_lib_dir.value) { - const char *tmp; - size_t tmplen; - char *tmpnew; - - tmp = lttng_secure_getenv("LD_LIBRARY_PATH"); - if (!tmp) { - tmp = ""; - } - tmplen = strlen(the_config.consumerd32_lib_dir.value) + 1 /* : */ + strlen(tmp); - tmpnew = zmalloc(tmplen + 1 /* \0 */); - if (!tmpnew) { - ret = -ENOMEM; - goto error; - } - strcat(tmpnew, the_config.consumerd32_lib_dir.value); - if (tmp[0] != '\0') { - strcat(tmpnew, ":"); - strcat(tmpnew, tmp); - } - ret = setenv("LD_LIBRARY_PATH", tmpnew, 1); - free(tmpnew); - if (ret) { - ret = -errno; - goto error; - } - } - DBG("Using 32-bit UST consumer at: %s", - the_config.consumerd32_bin_path.value); - (void) execl(the_config.consumerd32_bin_path.value, - "lttng-consumerd", verbosity, "-u", - "--consumerd-cmd-sock", - consumer_data->cmd_unix_sock_path, - "--consumerd-err-sock", - consumer_data->err_unix_sock_path, - "--group", - the_config.tracing_group_name.value, - NULL); - break; - } - default: - ERR("unknown consumer type"); - errno = 0; - } - if (errno != 0) { - PERROR("Consumer execl()"); - } - /* Reaching this point, we got a failure on our execl(). */ - exit(EXIT_FAILURE); - } else if (pid > 0) { - ret = pid; - } else { - PERROR("start consumer fork"); - ret = -errno; - } -error: - return ret; -} - -/* - * Spawn the consumerd daemon and session daemon thread. - */ -static int start_consumerd(struct consumer_data *consumer_data) -{ - int ret; - - /* - * Set the listen() state on the socket since there is a possible race - * between the exec() of the consumer daemon and this call if place in the - * consumer thread. See bug #366 for more details. - */ - ret = lttcomm_listen_unix_sock(consumer_data->err_sock); - if (ret < 0) { - goto error; - } - - pthread_mutex_lock(&consumer_data->pid_mutex); - if (consumer_data->pid != 0) { - pthread_mutex_unlock(&consumer_data->pid_mutex); - goto end; - } - - ret = spawn_consumerd(consumer_data); - if (ret < 0) { - ERR("Spawning consumerd failed"); - pthread_mutex_unlock(&consumer_data->pid_mutex); - goto error; - } - - /* Setting up the consumer_data pid */ - consumer_data->pid = ret; - DBG2("Consumer pid %d", consumer_data->pid); - pthread_mutex_unlock(&consumer_data->pid_mutex); - - DBG2("Spawning consumer control thread"); - ret = spawn_consumer_thread(consumer_data); - if (ret < 0) { - ERR("Fatal error spawning consumer control thread"); - goto error; - } - -end: - return 0; - -error: - /* Cleanup already created sockets on error. */ - if (consumer_data->err_sock >= 0) { - int err; - - err = close(consumer_data->err_sock); - if (err < 0) { - PERROR("close consumer data error socket"); - } - } - return ret; -} - -/* - * Copy consumer output from the tracing session to the domain session. The - * function also applies the right modification on a per domain basis for the - * trace files destination directory. - * - * Should *NOT* be called with RCU read-side lock held. - */ -static int copy_session_consumer(int domain, struct ltt_session *session) -{ - int ret; - const char *dir_name; - struct consumer_output *consumer; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(session->consumer); - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - DBG3("Copying tracing session consumer output in kernel session"); - /* - * XXX: We should audit the session creation and what this function - * does "extra" in order to avoid a destroy since this function is used - * in the domain session creation (kernel and ust) only. Same for UST - * domain. - */ - if (session->kernel_session->consumer) { - consumer_output_put(session->kernel_session->consumer); - } - session->kernel_session->consumer = - consumer_copy_output(session->consumer); - /* Ease our life a bit for the next part */ - consumer = session->kernel_session->consumer; - dir_name = DEFAULT_KERNEL_TRACE_DIR; - break; - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_PYTHON: - case LTTNG_DOMAIN_UST: - DBG3("Copying tracing session consumer output in UST session"); - if (session->ust_session->consumer) { - consumer_output_put(session->ust_session->consumer); - } - session->ust_session->consumer = - consumer_copy_output(session->consumer); - /* Ease our life a bit for the next part */ - consumer = session->ust_session->consumer; - dir_name = DEFAULT_UST_TRACE_DIR; - break; - default: - ret = LTTNG_ERR_UNKNOWN_DOMAIN; - goto error; - } - - /* Append correct directory to subdir */ - ret = lttng_strncpy(consumer->domain_subdir, dir_name, - sizeof(consumer->domain_subdir)); - if (ret) { - ret = LTTNG_ERR_UNK; - goto error; - } - DBG3("Copy session consumer subdir %s", consumer->domain_subdir); - ret = LTTNG_OK; - -error: - return ret; -} - -/* - * Create an UST session and add it to the session ust list. - * - * Should *NOT* be called with RCU read-side lock held. - */ -static int create_ust_session(struct ltt_session *session, - const struct lttng_domain *domain) -{ - int ret; - struct ltt_ust_session *lus = NULL; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(domain); - LTTNG_ASSERT(session->consumer); - - switch (domain->type) { - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_PYTHON: - case LTTNG_DOMAIN_UST: - break; - default: - ERR("Unknown UST domain on create session %d", domain->type); - ret = LTTNG_ERR_UNKNOWN_DOMAIN; - goto error; - } - - DBG("Creating UST session"); - - lus = trace_ust_create_session(session->id); - if (lus == NULL) { - ret = LTTNG_ERR_UST_SESS_FAIL; - goto error; - } - - lus->uid = session->uid; - lus->gid = session->gid; - lus->output_traces = session->output_traces; - lus->snapshot_mode = session->snapshot_mode; - lus->live_timer_interval = session->live_timer; - session->ust_session = lus; - if (session->shm_path[0]) { - strncpy(lus->root_shm_path, session->shm_path, - sizeof(lus->root_shm_path)); - lus->root_shm_path[sizeof(lus->root_shm_path) - 1] = '\0'; - strncpy(lus->shm_path, session->shm_path, - sizeof(lus->shm_path)); - lus->shm_path[sizeof(lus->shm_path) - 1] = '\0'; - strncat(lus->shm_path, "/ust", - sizeof(lus->shm_path) - strlen(lus->shm_path) - 1); - } - /* Copy session output to the newly created UST session */ - ret = copy_session_consumer(domain->type, session); - if (ret != LTTNG_OK) { - goto error; - } - - return LTTNG_OK; - -error: - free(lus); - session->ust_session = NULL; - return ret; -} - -/* - * Create a kernel tracer session then create the default channel. - */ -static int create_kernel_session(struct ltt_session *session) -{ - int ret; - - DBG("Creating kernel session"); - - ret = kernel_create_session(session); - if (ret < 0) { - ret = LTTNG_ERR_KERN_SESS_FAIL; - goto error_create; - } - - /* Code flow safety */ - LTTNG_ASSERT(session->kernel_session); - - /* Copy session output to the newly created Kernel session */ - ret = copy_session_consumer(LTTNG_DOMAIN_KERNEL, session); - if (ret != LTTNG_OK) { - goto error; - } - - session->kernel_session->uid = session->uid; - session->kernel_session->gid = session->gid; - session->kernel_session->output_traces = session->output_traces; - session->kernel_session->snapshot_mode = session->snapshot_mode; - session->kernel_session->is_live_session = session->live_timer != 0; - - return LTTNG_OK; - -error: - trace_kernel_destroy_session(session->kernel_session); - session->kernel_session = NULL; -error_create: - return ret; -} - -/* - * Count number of session permitted by uid/gid. - */ -static unsigned int lttng_sessions_count(uid_t uid, gid_t gid) -{ - unsigned int i = 0; - struct ltt_session *session; - const struct ltt_session_list *session_list = session_get_list(); - - DBG("Counting number of available session for UID %d", uid); - cds_list_for_each_entry(session, &session_list->head, list) { - if (!session_get(session)) { - continue; - } - session_lock(session); - /* Only count the sessions the user can control. */ - if (session_access_ok(session, uid) && - !session->destroyed) { - i++; - } - session_unlock(session); - session_put(session); - } - return i; -} - -static int receive_userspace_probe(struct command_ctx *cmd_ctx, int sock, - int *sock_error, struct lttng_event *event) -{ - int fd = -1, ret; - struct lttng_userspace_probe_location *probe_location; - struct lttng_payload probe_location_payload; - struct fd_handle *handle = NULL; - - /* - * Create a payload to store the serialized version of the probe - * location. - */ - lttng_payload_init(&probe_location_payload); - - ret = lttng_dynamic_buffer_set_size(&probe_location_payload.buffer, - cmd_ctx->lsm.u.enable.userspace_probe_location_len); - if (ret) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - - /* - * Receive the probe location. - */ - ret = lttcomm_recv_unix_sock(sock, probe_location_payload.buffer.data, - probe_location_payload.buffer.size); - if (ret <= 0) { - DBG("Nothing recv() from client var len data... continuing"); - *sock_error = 1; - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - - /* - * 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"); - *sock_error = 1; - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - - handle = fd_handle_create(fd); - if (!handle) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - - /* Transferred to the handle. */ - fd = -1; - - ret = lttng_payload_push_fd_handle(&probe_location_payload, handle); - if (ret) { - ERR("Failed to add userspace probe file descriptor to payload"); - ret = LTTNG_ERR_NOMEM; - goto error; - } - - fd_handle_put(handle); - handle = NULL; - - { - struct lttng_payload_view view = lttng_payload_view_from_payload( - &probe_location_payload, 0, -1); - - /* Extract the probe location from the serialized version. */ - ret = lttng_userspace_probe_location_create_from_payload( - &view, &probe_location); - } - if (ret < 0) { - WARN("Failed to create a userspace probe location from the received buffer"); - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - - /* Attach the probe location to the event. */ - ret = lttng_event_set_userspace_probe_location(event, probe_location); - if (ret) { - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - -error: - if (fd >= 0) { - if (close(fd)) { - PERROR("Failed to close userspace probe location binary fd"); - } - } - - fd_handle_put(handle); - lttng_payload_reset(&probe_location_payload); - return ret; -} - -static enum lttng_error_code receive_lttng_trigger(struct command_ctx *cmd_ctx, - int sock, - int *sock_error, - struct lttng_trigger **_trigger) -{ - int ret; - size_t trigger_len; - ssize_t sock_recv_len; - enum lttng_error_code ret_code; - struct lttng_payload trigger_payload; - struct lttng_trigger *trigger = NULL; - - lttng_payload_init(&trigger_payload); - trigger_len = (size_t) cmd_ctx->lsm.u.trigger.length; - ret = lttng_dynamic_buffer_set_size( - &trigger_payload.buffer, trigger_len); - if (ret) { - ret_code = LTTNG_ERR_NOMEM; - goto end; - } - - sock_recv_len = lttcomm_recv_unix_sock( - sock, trigger_payload.buffer.data, trigger_len); - if (sock_recv_len < 0 || sock_recv_len != trigger_len) { - ERR("Failed to receive trigger in command payload"); - *sock_error = 1; - ret_code = LTTNG_ERR_INVALID_PROTOCOL; - goto end; - } - - /* Receive fds, if any. */ - if (cmd_ctx->lsm.fd_count > 0) { - sock_recv_len = lttcomm_recv_payload_fds_unix_sock( - sock, cmd_ctx->lsm.fd_count, &trigger_payload); - if (sock_recv_len > 0 && - sock_recv_len != cmd_ctx->lsm.fd_count * sizeof(int)) { - ERR("Failed to receive all file descriptors for trigger in command payload: expected fd count = %u, ret = %d", - cmd_ctx->lsm.fd_count, (int) ret); - ret_code = LTTNG_ERR_INVALID_PROTOCOL; - *sock_error = 1; - goto end; - } else if (sock_recv_len <= 0) { - ERR("Failed to receive file descriptors for trigger in command payload: expected fd count = %u, ret = %d", - cmd_ctx->lsm.fd_count, (int) ret); - ret_code = LTTNG_ERR_FATAL; - *sock_error = 1; - goto end; - } - } - - /* Deserialize trigger. */ - { - struct lttng_payload_view view = - lttng_payload_view_from_payload( - &trigger_payload, 0, -1); - - if (lttng_trigger_create_from_payload(&view, &trigger) != - trigger_len) { - ERR("Invalid trigger received as part of command payload"); - ret_code = LTTNG_ERR_INVALID_TRIGGER; - lttng_trigger_put(trigger); - goto end; - } - } - - *_trigger = trigger; - ret_code = LTTNG_OK; - -end: - lttng_payload_reset(&trigger_payload); - return ret_code; -} - -static enum lttng_error_code receive_lttng_error_query(struct command_ctx *cmd_ctx, - int sock, - int *sock_error, - struct lttng_error_query **_query) -{ - int ret; - size_t query_len; - ssize_t sock_recv_len; - enum lttng_error_code ret_code; - struct lttng_payload query_payload; - struct lttng_error_query *query = NULL; - - lttng_payload_init(&query_payload); - query_len = (size_t) cmd_ctx->lsm.u.error_query.length; - ret = lttng_dynamic_buffer_set_size(&query_payload.buffer, query_len); - if (ret) { - ret_code = LTTNG_ERR_NOMEM; - goto end; - } - - sock_recv_len = lttcomm_recv_unix_sock( - sock, query_payload.buffer.data, query_len); - if (sock_recv_len < 0 || sock_recv_len != query_len) { - ERR("Failed to receive error query in command payload"); - *sock_error = 1; - ret_code = LTTNG_ERR_INVALID_PROTOCOL; - goto end; - } - - /* Receive fds, if any. */ - if (cmd_ctx->lsm.fd_count > 0) { - sock_recv_len = lttcomm_recv_payload_fds_unix_sock( - sock, cmd_ctx->lsm.fd_count, &query_payload); - if (sock_recv_len > 0 && - sock_recv_len != cmd_ctx->lsm.fd_count * sizeof(int)) { - ERR("Failed to receive all file descriptors for error query in command payload: expected fd count = %u, ret = %d", - cmd_ctx->lsm.fd_count, (int) ret); - ret_code = LTTNG_ERR_INVALID_PROTOCOL; - *sock_error = 1; - goto end; - } else if (sock_recv_len <= 0) { - ERR("Failed to receive file descriptors for error query in command payload: expected fd count = %u, ret = %d", - cmd_ctx->lsm.fd_count, (int) ret); - ret_code = LTTNG_ERR_FATAL; - *sock_error = 1; - goto end; - } - } - - /* Deserialize error query. */ - { - struct lttng_payload_view view = - lttng_payload_view_from_payload( - &query_payload, 0, -1); - - if (lttng_error_query_create_from_payload(&view, &query) != - query_len) { - ERR("Invalid error query received as part of command payload"); - ret_code = LTTNG_ERR_INVALID_PROTOCOL; - goto end; - } - } - - *_query = query; - ret_code = LTTNG_OK; - -end: - lttng_payload_reset(&query_payload); - return ret_code; -} - -/* - * Version of setup_lttng_msg() without command header. - */ -static int setup_lttng_msg_no_cmd_header(struct command_ctx *cmd_ctx, - void *payload_buf, size_t payload_len) -{ - return setup_lttng_msg(cmd_ctx, payload_buf, payload_len, NULL, 0); -} - -/* - * Check if the current kernel tracer supports the session rotation feature. - * Return 1 if it does, 0 otherwise. - */ -static int check_rotate_compatible(void) -{ - int ret = 1; - - if (the_kernel_tracer_version.major != 2 || - the_kernel_tracer_version.minor < 11) { - DBG("Kernel tracer version is not compatible with the rotation feature"); - ret = 0; - } - - return ret; -} - -/* - * Send data on a unix socket using the liblttsessiondcomm API. - * - * Return lttcomm error code. - */ -static int send_unix_sock(int sock, struct lttng_payload_view *view) -{ - int ret; - const int fd_count = lttng_payload_view_get_fd_handle_count(view); - - /* Check valid length */ - if (view->buffer.size == 0) { - ret = -1; - goto end; - } - - ret = lttcomm_send_unix_sock( - sock, view->buffer.data, view->buffer.size); - if (ret < 0) { - goto end; - } - - if (fd_count > 0) { - ret = lttcomm_send_payload_view_fds_unix_sock(sock, view); - if (ret < 0) { - goto end; - } - } - -end: - return ret; -} - -/* - * Process the command requested by the lttng client within the command - * context structure. This function make sure that the return structure (llm) - * is set and ready for transmission before returning. - * - * Return any error encountered or 0 for success. - * - * "sock" is only used for special-case var. len data. - * A command may assume the ownership of the socket, in which case its value - * should be set to -1. - * - * Should *NOT* be called with RCU read-side lock held. - */ -static int process_client_msg(struct command_ctx *cmd_ctx, int *sock, - int *sock_error) -{ - int ret = LTTNG_OK; - bool need_tracing_session = true; - bool need_domain; - bool need_consumerd; - - DBG("Processing client command '%s\' (%d)", - lttcomm_sessiond_command_str(cmd_ctx->lsm.cmd_type), - cmd_ctx->lsm.cmd_type); - - LTTNG_ASSERT(!rcu_read_ongoing()); - - *sock_error = 0; - - switch (cmd_ctx->lsm.cmd_type) { - case LTTNG_CREATE_SESSION_EXT: - case LTTNG_DESTROY_SESSION: - case LTTNG_LIST_SESSIONS: - case LTTNG_LIST_DOMAINS: - case LTTNG_START_TRACE: - case LTTNG_STOP_TRACE: - case LTTNG_DATA_PENDING: - case LTTNG_SNAPSHOT_ADD_OUTPUT: - case LTTNG_SNAPSHOT_DEL_OUTPUT: - case LTTNG_SNAPSHOT_LIST_OUTPUT: - case LTTNG_SNAPSHOT_RECORD: - case LTTNG_SAVE_SESSION: - case LTTNG_SET_SESSION_SHM_PATH: - case LTTNG_REGENERATE_METADATA: - case LTTNG_REGENERATE_STATEDUMP: - 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: - case LTTNG_EXECUTE_ERROR_QUERY: - need_domain = false; - break; - default: - need_domain = true; - } - - /* Needs a functioning consumerd? */ - switch (cmd_ctx->lsm.cmd_type) { - case LTTNG_REGISTER_TRIGGER: - case LTTNG_UNREGISTER_TRIGGER: - case LTTNG_EXECUTE_ERROR_QUERY: - need_consumerd = false; - break; - default: - need_consumerd = true; - break; - } - - if (the_config.no_kernel && need_domain && - cmd_ctx->lsm.domain.type == LTTNG_DOMAIN_KERNEL) { - if (!is_root) { - ret = LTTNG_ERR_NEED_ROOT_SESSIOND; - } else { - ret = LTTNG_ERR_KERN_NA; - } - goto error; - } - - /* Deny register consumer if we already have a spawned consumer. */ - if (cmd_ctx->lsm.cmd_type == LTTNG_REGISTER_CONSUMER) { - pthread_mutex_lock(&the_kconsumer_data.pid_mutex); - if (the_kconsumer_data.pid > 0) { - ret = LTTNG_ERR_KERN_CONSUMER_FAIL; - pthread_mutex_unlock(&the_kconsumer_data.pid_mutex); - goto error; - } - pthread_mutex_unlock(&the_kconsumer_data.pid_mutex); - } - - /* - * Check for command that don't needs to allocate a returned payload. We do - * this here so we don't have to make the call for no payload at each - * command. - */ - switch(cmd_ctx->lsm.cmd_type) { - case LTTNG_LIST_SESSIONS: - case LTTNG_LIST_TRACEPOINTS: - case LTTNG_LIST_TRACEPOINT_FIELDS: - case LTTNG_LIST_DOMAINS: - case LTTNG_LIST_CHANNELS: - case LTTNG_LIST_EVENTS: - case LTTNG_LIST_SYSCALLS: - case LTTNG_SESSION_LIST_ROTATION_SCHEDULES: - case LTTNG_PROCESS_ATTR_TRACKER_GET_POLICY: - case LTTNG_PROCESS_ATTR_TRACKER_GET_INCLUSION_SET: - case LTTNG_DATA_PENDING: - case LTTNG_ROTATE_SESSION: - case LTTNG_ROTATION_GET_INFO: - case LTTNG_REGISTER_TRIGGER: - case LTTNG_LIST_TRIGGERS: - case LTTNG_EXECUTE_ERROR_QUERY: - break; - default: - /* Setup lttng message with no payload */ - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, NULL, 0); - if (ret < 0) { - /* This label does not try to unlock the session */ - goto init_setup_error; - } - } - - /* Commands that DO NOT need a session. */ - switch (cmd_ctx->lsm.cmd_type) { - case LTTNG_CREATE_SESSION_EXT: - case LTTNG_LIST_SESSIONS: - case LTTNG_LIST_TRACEPOINTS: - case LTTNG_LIST_SYSCALLS: - case LTTNG_LIST_TRACEPOINT_FIELDS: - case LTTNG_SAVE_SESSION: - case LTTNG_REGISTER_TRIGGER: - case LTTNG_UNREGISTER_TRIGGER: - case LTTNG_LIST_TRIGGERS: - case LTTNG_EXECUTE_ERROR_QUERY: - need_tracing_session = false; - break; - default: - DBG("Getting session %s by name", cmd_ctx->lsm.session.name); - /* - * We keep the session list lock across _all_ commands - * for now, because the per-session lock does not - * handle teardown properly. - */ - session_lock_list(); - cmd_ctx->session = session_find_by_name(cmd_ctx->lsm.session.name); - if (cmd_ctx->session == NULL) { - ret = LTTNG_ERR_SESS_NOT_FOUND; - goto error; - } else { - /* Acquire lock for the session */ - session_lock(cmd_ctx->session); - } - break; - } - - /* - * Commands that need a valid session but should NOT create one if none - * exists. Instead of creating one and destroying it when the command is - * handled, process that right before so we save some round trip in useless - * code path. - */ - switch (cmd_ctx->lsm.cmd_type) { - case LTTNG_DISABLE_CHANNEL: - case LTTNG_DISABLE_EVENT: - switch (cmd_ctx->lsm.domain.type) { - case LTTNG_DOMAIN_KERNEL: - if (!cmd_ctx->session->kernel_session) { - ret = LTTNG_ERR_NO_CHANNEL; - goto error; - } - break; - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_PYTHON: - case LTTNG_DOMAIN_UST: - if (!cmd_ctx->session->ust_session) { - ret = LTTNG_ERR_NO_CHANNEL; - goto error; - } - break; - default: - ret = LTTNG_ERR_UNKNOWN_DOMAIN; - goto error; - } - default: - break; - } - - if (!need_domain) { - goto skip_domain; - } - - /* - * Check domain type for specific "pre-action". - */ - switch (cmd_ctx->lsm.domain.type) { - case LTTNG_DOMAIN_KERNEL: - if (!is_root) { - ret = LTTNG_ERR_NEED_ROOT_SESSIOND; - goto error; - } - - /* Kernel tracer check */ - if (!kernel_tracer_is_initialized()) { - /* Basically, load kernel tracer modules */ - ret = init_kernel_tracer(); - if (ret != 0) { - goto error; - } - } - - /* Consumer is in an ERROR state. Report back to client */ - if (need_consumerd && uatomic_read(&the_kernel_consumerd_state) == - CONSUMER_ERROR) { - ret = LTTNG_ERR_NO_KERNCONSUMERD; - goto error; - } - - /* Need a session for kernel command */ - if (need_tracing_session) { - if (cmd_ctx->session->kernel_session == NULL) { - ret = create_kernel_session(cmd_ctx->session); - if (ret != LTTNG_OK) { - ret = LTTNG_ERR_KERN_SESS_FAIL; - goto error; - } - } - - /* Start the kernel consumer daemon */ - pthread_mutex_lock(&the_kconsumer_data.pid_mutex); - if (the_kconsumer_data.pid == 0 && - cmd_ctx->lsm.cmd_type != LTTNG_REGISTER_CONSUMER) { - pthread_mutex_unlock(&the_kconsumer_data.pid_mutex); - ret = start_consumerd(&the_kconsumer_data); - if (ret < 0) { - ret = LTTNG_ERR_KERN_CONSUMER_FAIL; - goto error; - } - uatomic_set(&the_kernel_consumerd_state, CONSUMER_STARTED); - } else { - pthread_mutex_unlock(&the_kconsumer_data.pid_mutex); - } - - /* - * The consumer was just spawned so we need to add the socket to - * the consumer output of the session if exist. - */ - ret = consumer_create_socket(&the_kconsumer_data, - cmd_ctx->session->kernel_session->consumer); - if (ret < 0) { - goto error; - } - } - - break; - 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 (need_consumerd && - uatomic_read(&the_ust_consumerd_state) == - CONSUMER_ERROR) { - ret = LTTNG_ERR_NO_USTCONSUMERD; - goto error; - } - - if (need_tracing_session) { - /* Create UST session if none exist. */ - if (cmd_ctx->session->ust_session == NULL) { - ret = create_ust_session(cmd_ctx->session, - ALIGNED_CONST_PTR(cmd_ctx->lsm.domain)); - if (ret != LTTNG_OK) { - goto error; - } - } - - /* Start the UST consumer daemons */ - /* 64-bit */ - pthread_mutex_lock(&the_ustconsumer64_data.pid_mutex); - if (the_config.consumerd64_bin_path.value && - the_ustconsumer64_data.pid == 0 && - cmd_ctx->lsm.cmd_type != LTTNG_REGISTER_CONSUMER) { - pthread_mutex_unlock(&the_ustconsumer64_data.pid_mutex); - ret = start_consumerd(&the_ustconsumer64_data); - if (ret < 0) { - ret = LTTNG_ERR_UST_CONSUMER64_FAIL; - uatomic_set(&the_ust_consumerd64_fd, -EINVAL); - goto error; - } - - uatomic_set(&the_ust_consumerd64_fd, the_ustconsumer64_data.cmd_sock); - uatomic_set(&the_ust_consumerd_state, CONSUMER_STARTED); - } else { - pthread_mutex_unlock(&the_ustconsumer64_data.pid_mutex); - } - - /* - * Setup socket for consumer 64 bit. No need for atomic access - * since it was set above and can ONLY be set in this thread. - */ - ret = consumer_create_socket(&the_ustconsumer64_data, - cmd_ctx->session->ust_session->consumer); - if (ret < 0) { - goto error; - } - - /* 32-bit */ - pthread_mutex_lock(&the_ustconsumer32_data.pid_mutex); - if (the_config.consumerd32_bin_path.value && - the_ustconsumer32_data.pid == 0 && - cmd_ctx->lsm.cmd_type != LTTNG_REGISTER_CONSUMER) { - pthread_mutex_unlock(&the_ustconsumer32_data.pid_mutex); - ret = start_consumerd(&the_ustconsumer32_data); - if (ret < 0) { - ret = LTTNG_ERR_UST_CONSUMER32_FAIL; - uatomic_set(&the_ust_consumerd32_fd, -EINVAL); - goto error; - } - - uatomic_set(&the_ust_consumerd32_fd, the_ustconsumer32_data.cmd_sock); - uatomic_set(&the_ust_consumerd_state, CONSUMER_STARTED); - } else { - pthread_mutex_unlock(&the_ustconsumer32_data.pid_mutex); - } - - /* - * Setup socket for consumer 32 bit. No need for atomic access - * since it was set above and can ONLY be set in this thread. - */ - ret = consumer_create_socket(&the_ustconsumer32_data, - cmd_ctx->session->ust_session->consumer); - if (ret < 0) { - goto error; - } - } - break; - } - default: - break; - } -skip_domain: - - /* Validate consumer daemon state when start/stop trace command */ - if (cmd_ctx->lsm.cmd_type == LTTNG_START_TRACE || - cmd_ctx->lsm.cmd_type == LTTNG_STOP_TRACE) { - switch (cmd_ctx->lsm.domain.type) { - case LTTNG_DOMAIN_NONE: - break; - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_PYTHON: - case LTTNG_DOMAIN_UST: - if (uatomic_read(&the_ust_consumerd_state) != CONSUMER_STARTED) { - ret = LTTNG_ERR_NO_USTCONSUMERD; - goto error; - } - break; - case LTTNG_DOMAIN_KERNEL: - if (uatomic_read(&the_kernel_consumerd_state) != CONSUMER_STARTED) { - ret = LTTNG_ERR_NO_KERNCONSUMERD; - goto error; - } - break; - default: - ret = LTTNG_ERR_UNKNOWN_DOMAIN; - goto error; - } - } - - /* - * Check that the UID matches that of the tracing session. - * The root user can interact with all sessions. - */ - if (need_tracing_session) { - if (!session_access_ok(cmd_ctx->session, - LTTNG_SOCK_GET_UID_CRED(&cmd_ctx->creds)) || - cmd_ctx->session->destroyed) { - ret = LTTNG_ERR_EPERM; - goto error; - } - } - - /* - * Send relayd information to consumer as soon as we have a domain and a - * session defined. - */ - if (cmd_ctx->session && need_domain) { - /* - * Setup relayd if not done yet. If the relayd information was already - * sent to the consumer, this call will gracefully return. - */ - ret = cmd_setup_relayd(cmd_ctx->session); - if (ret != LTTNG_OK) { - goto error; - } - } - - /* Process by command type */ - switch (cmd_ctx->lsm.cmd_type) { - case LTTNG_ADD_CONTEXT: - { - /* - * An LTTNG_ADD_CONTEXT command might have a supplementary - * payload if the context being added is an application context. - */ - if (cmd_ctx->lsm.u.context.ctx.ctx == - LTTNG_EVENT_CONTEXT_APP_CONTEXT) { - char *provider_name = NULL, *context_name = NULL; - size_t provider_name_len = - cmd_ctx->lsm.u.context.provider_name_len; - size_t context_name_len = - cmd_ctx->lsm.u.context.context_name_len; - - if (provider_name_len == 0 || context_name_len == 0) { - /* - * Application provider and context names MUST - * be provided. - */ - ret = -LTTNG_ERR_INVALID; - goto error; - } - - provider_name = zmalloc(provider_name_len + 1); - if (!provider_name) { - ret = -LTTNG_ERR_NOMEM; - goto error; - } - cmd_ctx->lsm.u.context.ctx.u.app_ctx.provider_name = - provider_name; - - context_name = zmalloc(context_name_len + 1); - if (!context_name) { - ret = -LTTNG_ERR_NOMEM; - goto error_add_context; - } - cmd_ctx->lsm.u.context.ctx.u.app_ctx.ctx_name = - context_name; - - ret = lttcomm_recv_unix_sock(*sock, provider_name, - provider_name_len); - if (ret < 0) { - goto error_add_context; - } - - ret = lttcomm_recv_unix_sock(*sock, context_name, - context_name_len); - if (ret < 0) { - goto error_add_context; - } - } - - /* - * cmd_add_context assumes ownership of the provider and context - * names. - */ - ret = cmd_add_context(cmd_ctx->session, - cmd_ctx->lsm.domain.type, - cmd_ctx->lsm.u.context.channel_name, - ALIGNED_CONST_PTR(cmd_ctx->lsm.u.context.ctx), - the_kernel_poll_pipe[1]); - - cmd_ctx->lsm.u.context.ctx.u.app_ctx.provider_name = NULL; - cmd_ctx->lsm.u.context.ctx.u.app_ctx.ctx_name = NULL; -error_add_context: - free(cmd_ctx->lsm.u.context.ctx.u.app_ctx.provider_name); - free(cmd_ctx->lsm.u.context.ctx.u.app_ctx.ctx_name); - if (ret < 0) { - goto error; - } - break; - } - case LTTNG_DISABLE_CHANNEL: - { - ret = cmd_disable_channel(cmd_ctx->session, cmd_ctx->lsm.domain.type, - cmd_ctx->lsm.u.disable.channel_name); - break; - } - case LTTNG_DISABLE_EVENT: - { - - /* - * FIXME: handle filter; for now we just receive the filter's - * bytecode along with the filter expression which are sent by - * liblttng-ctl and discard them. - * - * This fixes an issue where the client may block while sending - * the filter payload and encounter an error because the session - * daemon closes the socket without ever handling this data. - */ - size_t count = cmd_ctx->lsm.u.disable.expression_len + - cmd_ctx->lsm.u.disable.bytecode_len; - - if (count) { - char data[LTTNG_FILTER_MAX_LEN]; - - DBG("Discarding disable event command payload of size %zu", count); - while (count) { - ret = lttcomm_recv_unix_sock(*sock, data, - count > sizeof(data) ? sizeof(data) : count); - if (ret < 0) { - goto error; - } - - count -= (size_t) ret; - } - } - ret = cmd_disable_event(cmd_ctx->session, cmd_ctx->lsm.domain.type, - cmd_ctx->lsm.u.disable.channel_name, - ALIGNED_CONST_PTR(cmd_ctx->lsm.u.disable.event)); - break; - } - case LTTNG_ENABLE_CHANNEL: - { - cmd_ctx->lsm.u.channel.chan.attr.extended.ptr = - (struct lttng_channel_extended *) &cmd_ctx->lsm.u.channel.extended; - ret = cmd_enable_channel(cmd_ctx->session, - ALIGNED_CONST_PTR(cmd_ctx->lsm.domain), - ALIGNED_CONST_PTR(cmd_ctx->lsm.u.channel.chan), - the_kernel_poll_pipe[1]); - break; - } - case LTTNG_PROCESS_ATTR_TRACKER_ADD_INCLUDE_VALUE: - case LTTNG_PROCESS_ATTR_TRACKER_REMOVE_INCLUDE_VALUE: - { - struct lttng_dynamic_buffer payload; - struct lttng_buffer_view payload_view; - const bool add_value = - cmd_ctx->lsm.cmd_type == - LTTNG_PROCESS_ATTR_TRACKER_ADD_INCLUDE_VALUE; - const size_t name_len = - cmd_ctx->lsm.u.process_attr_tracker_add_remove_include_value - .name_len; - const enum lttng_domain_type domain_type = - (enum lttng_domain_type) - cmd_ctx->lsm.domain.type; - const enum lttng_process_attr process_attr = - (enum lttng_process_attr) cmd_ctx->lsm.u - .process_attr_tracker_add_remove_include_value - .process_attr; - const enum lttng_process_attr_value_type value_type = - (enum lttng_process_attr_value_type) cmd_ctx - ->lsm.u - .process_attr_tracker_add_remove_include_value - .value_type; - struct process_attr_value *value; - enum lttng_error_code ret_code; - long login_name_max; - - login_name_max = sysconf(_SC_LOGIN_NAME_MAX); - if (login_name_max < 0) { - PERROR("Failed to get _SC_LOGIN_NAME_MAX system configuration"); - ret = LTTNG_ERR_INVALID; - goto error; - } - - /* Receive remaining variable length payload if applicable. */ - if (name_len > login_name_max) { - /* - * POSIX mandates user and group names that are at least - * 8 characters long. Note that although shadow-utils - * (useradd, groupaadd, etc.) use 32 chars as their - * limit (from bits/utmp.h, UT_NAMESIZE), - * LOGIN_NAME_MAX is defined to 256. - */ - ERR("Rejecting process attribute tracker value %s as the provided exceeds the maximal allowed length: argument length = %zu, maximal length = %ld", - add_value ? "addition" : "removal", - name_len, login_name_max); - ret = LTTNG_ERR_INVALID; - goto error; - } - - lttng_dynamic_buffer_init(&payload); - if (name_len != 0) { - /* - * Receive variable payload for user/group name - * arguments. - */ - ret = lttng_dynamic_buffer_set_size(&payload, name_len); - if (ret) { - ERR("Failed to allocate buffer to receive payload of %s process attribute tracker value argument", - add_value ? "add" : "remove"); - ret = LTTNG_ERR_NOMEM; - goto error_add_remove_tracker_value; - } - - ret = lttcomm_recv_unix_sock( - *sock, payload.data, name_len); - if (ret <= 0) { - ERR("Failed to receive payload of %s process attribute tracker value argument", - add_value ? "add" : "remove"); - *sock_error = 1; - ret = LTTNG_ERR_INVALID_PROTOCOL; - goto error_add_remove_tracker_value; - } - } - - payload_view = lttng_buffer_view_from_dynamic_buffer( - &payload, 0, name_len); - if (name_len > 0 && !lttng_buffer_view_is_valid(&payload_view)) { - ret = LTTNG_ERR_INVALID_PROTOCOL; - goto error_add_remove_tracker_value; - } - - /* - * Validate the value type and domains are legal for the process - * attribute tracker that is specified and convert the value to - * add/remove to the internal sessiond representation. - */ - ret_code = process_attr_value_from_comm(domain_type, - process_attr, value_type, - &cmd_ctx->lsm.u.process_attr_tracker_add_remove_include_value - .integral_value, - &payload_view, &value); - if (ret_code != LTTNG_OK) { - ret = ret_code; - goto error_add_remove_tracker_value; - } - - if (add_value) { - ret = cmd_process_attr_tracker_inclusion_set_add_value( - cmd_ctx->session, domain_type, - process_attr, value); - } else { - ret = cmd_process_attr_tracker_inclusion_set_remove_value( - cmd_ctx->session, domain_type, - process_attr, value); - } - process_attr_value_destroy(value); - error_add_remove_tracker_value: - lttng_dynamic_buffer_reset(&payload); - break; - } - case LTTNG_PROCESS_ATTR_TRACKER_GET_POLICY: - { - enum lttng_tracking_policy tracking_policy; - const enum lttng_domain_type domain_type = - (enum lttng_domain_type) - cmd_ctx->lsm.domain.type; - const enum lttng_process_attr process_attr = - (enum lttng_process_attr) cmd_ctx->lsm.u - .process_attr_tracker_get_tracking_policy - .process_attr; - - ret = cmd_process_attr_tracker_get_tracking_policy( - cmd_ctx->session, domain_type, process_attr, - &tracking_policy); - if (ret != LTTNG_OK) { - goto error; - } - - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, - &(uint32_t){tracking_policy}, sizeof(uint32_t)); - if (ret < 0) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - ret = LTTNG_OK; - break; - } - case LTTNG_PROCESS_ATTR_TRACKER_SET_POLICY: - { - const enum lttng_tracking_policy tracking_policy = - (enum lttng_tracking_policy) cmd_ctx->lsm.u - .process_attr_tracker_set_tracking_policy - .tracking_policy; - const enum lttng_domain_type domain_type = - (enum lttng_domain_type) - cmd_ctx->lsm.domain.type; - const enum lttng_process_attr process_attr = - (enum lttng_process_attr) cmd_ctx->lsm.u - .process_attr_tracker_set_tracking_policy - .process_attr; - - ret = cmd_process_attr_tracker_set_tracking_policy( - cmd_ctx->session, domain_type, process_attr, - tracking_policy); - if (ret != LTTNG_OK) { - goto error; - } - break; - } - case LTTNG_PROCESS_ATTR_TRACKER_GET_INCLUSION_SET: - { - struct lttng_process_attr_values *values; - struct lttng_dynamic_buffer reply; - const enum lttng_domain_type domain_type = - (enum lttng_domain_type) - cmd_ctx->lsm.domain.type; - const enum lttng_process_attr process_attr = - (enum lttng_process_attr) cmd_ctx->lsm.u - .process_attr_tracker_get_inclusion_set - .process_attr; - - ret = cmd_process_attr_tracker_get_inclusion_set( - cmd_ctx->session, domain_type, process_attr, - &values); - if (ret != LTTNG_OK) { - goto error; - } - - lttng_dynamic_buffer_init(&reply); - ret = lttng_process_attr_values_serialize(values, &reply); - if (ret < 0) { - goto error_tracker_get_inclusion_set; - } - - ret = setup_lttng_msg_no_cmd_header( - cmd_ctx, reply.data, reply.size); - if (ret < 0) { - ret = LTTNG_ERR_NOMEM; - goto error_tracker_get_inclusion_set; - } - ret = LTTNG_OK; - - error_tracker_get_inclusion_set: - lttng_process_attr_values_destroy(values); - lttng_dynamic_buffer_reset(&reply); - break; - } - case LTTNG_ENABLE_EVENT: - { - struct lttng_event *ev = NULL; - struct lttng_event_exclusion *exclusion = NULL; - struct lttng_bytecode *bytecode = NULL; - char *filter_expression = NULL; - - /* Handle exclusion events and receive it from the client. */ - if (cmd_ctx->lsm.u.enable.exclusion_count > 0) { - size_t count = cmd_ctx->lsm.u.enable.exclusion_count; - - exclusion = zmalloc(sizeof(struct lttng_event_exclusion) + - (count * LTTNG_SYMBOL_NAME_LEN)); - if (!exclusion) { - ret = LTTNG_ERR_EXCLUSION_NOMEM; - goto error; - } - - DBG("Receiving var len exclusion event list from client ..."); - exclusion->count = count; - ret = lttcomm_recv_unix_sock(*sock, exclusion->names, - count * LTTNG_SYMBOL_NAME_LEN); - if (ret <= 0) { - DBG("Nothing recv() from client var len data... continuing"); - *sock_error = 1; - free(exclusion); - ret = LTTNG_ERR_EXCLUSION_INVAL; - goto error; - } - } - - /* Get filter expression from client. */ - if (cmd_ctx->lsm.u.enable.expression_len > 0) { - size_t expression_len = - cmd_ctx->lsm.u.enable.expression_len; - - if (expression_len > LTTNG_FILTER_MAX_LEN) { - ret = LTTNG_ERR_FILTER_INVAL; - free(exclusion); - goto error; - } - - filter_expression = zmalloc(expression_len); - if (!filter_expression) { - free(exclusion); - ret = LTTNG_ERR_FILTER_NOMEM; - goto error; - } - - /* Receive var. len. data */ - DBG("Receiving var len filter's expression from client ..."); - ret = lttcomm_recv_unix_sock(*sock, filter_expression, - expression_len); - if (ret <= 0) { - DBG("Nothing recv() from client var len data... continuing"); - *sock_error = 1; - free(filter_expression); - free(exclusion); - ret = LTTNG_ERR_FILTER_INVAL; - goto error; - } - } - - /* Handle filter and get bytecode from client. */ - if (cmd_ctx->lsm.u.enable.bytecode_len > 0) { - size_t bytecode_len = cmd_ctx->lsm.u.enable.bytecode_len; - - if (bytecode_len > LTTNG_FILTER_MAX_LEN) { - ret = LTTNG_ERR_FILTER_INVAL; - free(filter_expression); - free(exclusion); - goto error; - } - - bytecode = zmalloc(bytecode_len); - if (!bytecode) { - free(filter_expression); - free(exclusion); - ret = LTTNG_ERR_FILTER_NOMEM; - goto error; - } - - /* Receive var. len. data */ - DBG("Receiving var len filter's bytecode from client ..."); - ret = lttcomm_recv_unix_sock(*sock, bytecode, bytecode_len); - if (ret <= 0) { - DBG("Nothing recv() from client var len data... continuing"); - *sock_error = 1; - free(filter_expression); - free(bytecode); - free(exclusion); - ret = LTTNG_ERR_FILTER_INVAL; - goto error; - } - - if ((bytecode->len + sizeof(*bytecode)) != bytecode_len) { - free(filter_expression); - free(bytecode); - free(exclusion); - ret = LTTNG_ERR_FILTER_INVAL; - goto error; - } - } - - ev = lttng_event_copy(ALIGNED_CONST_PTR(cmd_ctx->lsm.u.enable.event)); - if (!ev) { - DBG("Failed to copy event: %s", - cmd_ctx->lsm.u.enable.event.name); - free(filter_expression); - free(bytecode); - free(exclusion); - ret = LTTNG_ERR_NOMEM; - goto error; - } - - - if (cmd_ctx->lsm.u.enable.userspace_probe_location_len > 0) { - /* Expect a userspace probe description. */ - ret = receive_userspace_probe(cmd_ctx, *sock, sock_error, ev); - if (ret) { - free(filter_expression); - free(bytecode); - free(exclusion); - lttng_event_destroy(ev); - goto error; - } - } - - ret = cmd_enable_event(cmd_ctx->session, - ALIGNED_CONST_PTR(cmd_ctx->lsm.domain), - cmd_ctx->lsm.u.enable.channel_name, - ev, - filter_expression, bytecode, exclusion, - the_kernel_poll_pipe[1]); - lttng_event_destroy(ev); - break; - } - case LTTNG_LIST_TRACEPOINTS: - { - struct lttng_event *events; - ssize_t nb_events; - - session_lock_list(); - nb_events = cmd_list_tracepoints(cmd_ctx->lsm.domain.type, &events); - session_unlock_list(); - if (nb_events < 0) { - /* Return value is a negative lttng_error_code. */ - ret = -nb_events; - goto error; - } - - /* - * Setup lttng message with payload size set to the event list size in - * bytes and then copy list into the llm payload. - */ - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, events, - sizeof(struct lttng_event) * nb_events); - free(events); - - if (ret < 0) { - goto setup_error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_LIST_TRACEPOINT_FIELDS: - { - struct lttng_event_field *fields; - ssize_t nb_fields; - - session_lock_list(); - nb_fields = cmd_list_tracepoint_fields(cmd_ctx->lsm.domain.type, - &fields); - session_unlock_list(); - if (nb_fields < 0) { - /* Return value is a negative lttng_error_code. */ - ret = -nb_fields; - goto error; - } - - /* - * Setup lttng message with payload size set to the event list size in - * bytes and then copy list into the llm payload. - */ - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, fields, - sizeof(struct lttng_event_field) * nb_fields); - free(fields); - - if (ret < 0) { - goto setup_error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_LIST_SYSCALLS: - { - struct lttng_event *events; - ssize_t nb_events; - - nb_events = cmd_list_syscalls(&events); - if (nb_events < 0) { - /* Return value is a negative lttng_error_code. */ - ret = -nb_events; - goto error; - } - - /* - * Setup lttng message with payload size set to the event list size in - * bytes and then copy list into the llm payload. - */ - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, events, - sizeof(struct lttng_event) * nb_events); - free(events); - - if (ret < 0) { - goto setup_error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_SET_CONSUMER_URI: - { - size_t nb_uri, len; - struct lttng_uri *uris; - - nb_uri = cmd_ctx->lsm.u.uri.size; - len = nb_uri * sizeof(struct lttng_uri); - - if (nb_uri == 0) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - uris = zmalloc(len); - if (uris == NULL) { - ret = LTTNG_ERR_FATAL; - goto error; - } - - /* Receive variable len data */ - DBG("Receiving %zu URI(s) from client ...", nb_uri); - ret = lttcomm_recv_unix_sock(*sock, uris, len); - if (ret <= 0) { - DBG("No URIs received from client... continuing"); - *sock_error = 1; - ret = LTTNG_ERR_SESSION_FAIL; - free(uris); - goto error; - } - - ret = cmd_set_consumer_uri(cmd_ctx->session, nb_uri, uris); - free(uris); - if (ret != LTTNG_OK) { - goto error; - } - - - break; - } - case LTTNG_START_TRACE: - { - /* - * On the first start, if we have a kernel session and we have - * enabled time or size-based rotations, we have to make sure - * the kernel tracer supports it. - */ - if (!cmd_ctx->session->has_been_started && \ - cmd_ctx->session->kernel_session && \ - (cmd_ctx->session->rotate_timer_period || \ - cmd_ctx->session->rotate_size) && \ - !check_rotate_compatible()) { - DBG("Kernel tracer version is not compatible with the rotation feature"); - ret = LTTNG_ERR_ROTATION_WRONG_VERSION; - goto error; - } - ret = cmd_start_trace(cmd_ctx->session); - break; - } - case LTTNG_STOP_TRACE: - { - ret = cmd_stop_trace(cmd_ctx->session); - break; - } - case LTTNG_DESTROY_SESSION: - { - ret = cmd_destroy_session(cmd_ctx->session, - the_notification_thread_handle, sock); - break; - } - case LTTNG_LIST_DOMAINS: - { - ssize_t nb_dom; - struct lttng_domain *domains = NULL; - - nb_dom = cmd_list_domains(cmd_ctx->session, &domains); - if (nb_dom < 0) { - /* Return value is a negative lttng_error_code. */ - ret = -nb_dom; - goto error; - } - - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, domains, - nb_dom * sizeof(struct lttng_domain)); - free(domains); - - if (ret < 0) { - goto setup_error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_LIST_CHANNELS: - { - ssize_t payload_size; - struct lttng_channel *channels = NULL; - - payload_size = cmd_list_channels(cmd_ctx->lsm.domain.type, - cmd_ctx->session, &channels); - if (payload_size < 0) { - /* Return value is a negative lttng_error_code. */ - ret = -payload_size; - goto error; - } - - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, channels, - payload_size); - free(channels); - - if (ret < 0) { - goto setup_error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_LIST_EVENTS: - { - ssize_t list_ret; - struct lttcomm_event_command_header cmd_header = {}; - size_t original_payload_size; - size_t payload_size; - - ret = setup_empty_lttng_msg(cmd_ctx); - if (ret) { - ret = LTTNG_ERR_NOMEM; - goto setup_error; - } - - original_payload_size = cmd_ctx->reply_payload.buffer.size; - - /* Extended infos are included at the end of the payload. */ - list_ret = cmd_list_events(cmd_ctx->lsm.domain.type, - cmd_ctx->session, - cmd_ctx->lsm.u.list.channel_name, - &cmd_ctx->reply_payload); - if (list_ret < 0) { - /* Return value is a negative lttng_error_code. */ - ret = -list_ret; - goto error; - } - - payload_size = cmd_ctx->reply_payload.buffer.size - - sizeof(cmd_header) - original_payload_size; - update_lttng_msg(cmd_ctx, sizeof(cmd_header), payload_size); - - ret = LTTNG_OK; - break; - } - case LTTNG_LIST_SESSIONS: - { - unsigned int nr_sessions; - void *sessions_payload; - size_t payload_len; - - session_lock_list(); - nr_sessions = lttng_sessions_count( - LTTNG_SOCK_GET_UID_CRED(&cmd_ctx->creds), - LTTNG_SOCK_GET_GID_CRED(&cmd_ctx->creds)); - - payload_len = (sizeof(struct lttng_session) * nr_sessions) + - (sizeof(struct lttng_session_extended) * nr_sessions); - sessions_payload = zmalloc(payload_len); - - if (!sessions_payload) { - session_unlock_list(); - ret = -ENOMEM; - goto setup_error; - } - - cmd_list_lttng_sessions(sessions_payload, nr_sessions, - LTTNG_SOCK_GET_UID_CRED(&cmd_ctx->creds), - LTTNG_SOCK_GET_GID_CRED(&cmd_ctx->creds)); - session_unlock_list(); - - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, sessions_payload, - payload_len); - free(sessions_payload); - - if (ret < 0) { - goto setup_error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_REGISTER_CONSUMER: - { - struct consumer_data *cdata; - - switch (cmd_ctx->lsm.domain.type) { - case LTTNG_DOMAIN_KERNEL: - cdata = &the_kconsumer_data; - break; - default: - ret = LTTNG_ERR_UND; - goto error; - } - - ret = cmd_register_consumer(cmd_ctx->session, cmd_ctx->lsm.domain.type, - cmd_ctx->lsm.u.reg.path, cdata); - break; - } - case LTTNG_DATA_PENDING: - { - int pending_ret; - uint8_t pending_ret_byte; - - pending_ret = cmd_data_pending(cmd_ctx->session); - - /* - * FIXME - * - * This function may returns 0 or 1 to indicate whether or not - * there is data pending. In case of error, it should return an - * LTTNG_ERR code. However, some code paths may still return - * a nondescript error code, which we handle by returning an - * "unknown" error. - */ - if (pending_ret == 0 || pending_ret == 1) { - /* - * ret will be set to LTTNG_OK at the end of - * this function. - */ - } else if (pending_ret < 0) { - ret = LTTNG_ERR_UNK; - goto setup_error; - } else { - ret = pending_ret; - goto setup_error; - } - - pending_ret_byte = (uint8_t) pending_ret; - - /* 1 byte to return whether or not data is pending */ - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, - &pending_ret_byte, 1); - - if (ret < 0) { - goto setup_error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_SNAPSHOT_ADD_OUTPUT: - { - uint32_t snapshot_id; - struct lttcomm_lttng_output_id reply; - - ret = cmd_snapshot_add_output(cmd_ctx->session, - ALIGNED_CONST_PTR(cmd_ctx->lsm.u.snapshot_output.output), - &snapshot_id); - if (ret != LTTNG_OK) { - goto error; - } - reply.id = snapshot_id; - - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, &reply, - sizeof(reply)); - if (ret < 0) { - goto setup_error; - } - - /* Copy output list into message payload */ - ret = LTTNG_OK; - break; - } - case LTTNG_SNAPSHOT_DEL_OUTPUT: - { - ret = cmd_snapshot_del_output(cmd_ctx->session, - ALIGNED_CONST_PTR(cmd_ctx->lsm.u.snapshot_output.output)); - break; - } - case LTTNG_SNAPSHOT_LIST_OUTPUT: - { - ssize_t nb_output; - struct lttng_snapshot_output *outputs = NULL; - - nb_output = cmd_snapshot_list_outputs(cmd_ctx->session, &outputs); - if (nb_output < 0) { - ret = -nb_output; - goto error; - } - - LTTNG_ASSERT((nb_output > 0 && outputs) || nb_output == 0); - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, outputs, - nb_output * sizeof(struct lttng_snapshot_output)); - free(outputs); - - if (ret < 0) { - goto setup_error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_SNAPSHOT_RECORD: - { - ret = cmd_snapshot_record(cmd_ctx->session, - ALIGNED_CONST_PTR(cmd_ctx->lsm.u.snapshot_record.output), - cmd_ctx->lsm.u.snapshot_record.wait); - break; - } - case LTTNG_CREATE_SESSION_EXT: - { - struct lttng_dynamic_buffer payload; - struct lttng_session_descriptor *return_descriptor = NULL; - - lttng_dynamic_buffer_init(&payload); - ret = cmd_create_session(cmd_ctx, *sock, &return_descriptor); - if (ret != LTTNG_OK) { - goto error; - } - - ret = lttng_session_descriptor_serialize(return_descriptor, - &payload); - if (ret) { - ERR("Failed to serialize session descriptor in reply to \"create session\" command"); - lttng_session_descriptor_destroy(return_descriptor); - ret = LTTNG_ERR_NOMEM; - goto error; - } - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, payload.data, - payload.size); - if (ret) { - lttng_session_descriptor_destroy(return_descriptor); - ret = LTTNG_ERR_NOMEM; - goto error; - } - lttng_dynamic_buffer_reset(&payload); - lttng_session_descriptor_destroy(return_descriptor); - ret = LTTNG_OK; - break; - } - case LTTNG_SAVE_SESSION: - { - ret = cmd_save_sessions(&cmd_ctx->lsm.u.save_session.attr, - &cmd_ctx->creds); - break; - } - case LTTNG_SET_SESSION_SHM_PATH: - { - ret = cmd_set_session_shm_path(cmd_ctx->session, - cmd_ctx->lsm.u.set_shm_path.shm_path); - break; - } - case LTTNG_REGENERATE_METADATA: - { - ret = cmd_regenerate_metadata(cmd_ctx->session); - break; - } - case LTTNG_REGENERATE_STATEDUMP: - { - ret = cmd_regenerate_statedump(cmd_ctx->session); - break; - } - case LTTNG_REGISTER_TRIGGER: - { - struct lttng_trigger *payload_trigger; - struct lttng_trigger *return_trigger; - size_t original_reply_payload_size; - size_t reply_payload_size; - const struct lttng_credentials cmd_creds = { - .uid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.uid), - .gid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.gid), - }; - - ret = setup_empty_lttng_msg(cmd_ctx); - if (ret) { - ret = LTTNG_ERR_NOMEM; - goto setup_error; - } - - ret = receive_lttng_trigger( - cmd_ctx, *sock, sock_error, &payload_trigger); - if (ret != LTTNG_OK) { - goto error; - } - - original_reply_payload_size = cmd_ctx->reply_payload.buffer.size; - - ret = cmd_register_trigger(&cmd_creds, payload_trigger, - cmd_ctx->lsm.u.trigger.is_trigger_anonymous, - the_notification_thread_handle, - &return_trigger); - if (ret != LTTNG_OK) { - lttng_trigger_put(payload_trigger); - goto error; - } - - ret = lttng_trigger_serialize(return_trigger, &cmd_ctx->reply_payload); - lttng_trigger_put(payload_trigger); - lttng_trigger_put(return_trigger); - if (ret) { - ERR("Failed to serialize trigger in reply to \"register trigger\" command"); - ret = LTTNG_ERR_NOMEM; - goto error; - } - - reply_payload_size = cmd_ctx->reply_payload.buffer.size - - original_reply_payload_size; - - update_lttng_msg(cmd_ctx, 0, reply_payload_size); - - ret = LTTNG_OK; - break; - } - case LTTNG_UNREGISTER_TRIGGER: - { - struct lttng_trigger *payload_trigger; - const struct lttng_credentials cmd_creds = { - .uid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.uid), - .gid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.gid), - }; - - ret = receive_lttng_trigger( - cmd_ctx, *sock, sock_error, &payload_trigger); - if (ret != LTTNG_OK) { - goto error; - } - - ret = cmd_unregister_trigger(&cmd_creds, payload_trigger, - the_notification_thread_handle); - lttng_trigger_put(payload_trigger); - break; - } - case LTTNG_ROTATE_SESSION: - { - struct lttng_rotate_session_return rotate_return; - - DBG("Client rotate session \"%s\"", cmd_ctx->session->name); - - memset(&rotate_return, 0, sizeof(rotate_return)); - if (cmd_ctx->session->kernel_session && !check_rotate_compatible()) { - DBG("Kernel tracer version is not compatible with the rotation feature"); - ret = LTTNG_ERR_ROTATION_WRONG_VERSION; - goto error; - } - - ret = cmd_rotate_session(cmd_ctx->session, &rotate_return, - false, - LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); - if (ret < 0) { - ret = -ret; - goto error; - } - - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, &rotate_return, - sizeof(rotate_return)); - if (ret < 0) { - ret = -ret; - goto error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_ROTATION_GET_INFO: - { - struct lttng_rotation_get_info_return get_info_return; - - memset(&get_info_return, 0, sizeof(get_info_return)); - ret = cmd_rotate_get_info(cmd_ctx->session, &get_info_return, - cmd_ctx->lsm.u.get_rotation_info.rotation_id); - if (ret < 0) { - ret = -ret; - goto error; - } - - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, &get_info_return, - sizeof(get_info_return)); - if (ret < 0) { - ret = -ret; - goto error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_ROTATION_SET_SCHEDULE: - { - bool set_schedule; - enum lttng_rotation_schedule_type schedule_type; - uint64_t value; - - if (cmd_ctx->session->kernel_session && !check_rotate_compatible()) { - DBG("Kernel tracer version does not support session rotations"); - ret = LTTNG_ERR_ROTATION_WRONG_VERSION; - goto error; - } - - set_schedule = cmd_ctx->lsm.u.rotation_set_schedule.set == 1; - schedule_type = (enum lttng_rotation_schedule_type) cmd_ctx->lsm.u.rotation_set_schedule.type; - value = cmd_ctx->lsm.u.rotation_set_schedule.value; - - ret = cmd_rotation_set_schedule(cmd_ctx->session, set_schedule, - schedule_type, value, - the_notification_thread_handle); - if (ret != LTTNG_OK) { - goto error; - } - - break; - } - case LTTNG_SESSION_LIST_ROTATION_SCHEDULES: - { - struct lttng_session_list_schedules_return schedules = { - .periodic.set = !!cmd_ctx->session->rotate_timer_period, - .periodic.value = cmd_ctx->session->rotate_timer_period, - .size.set = !!cmd_ctx->session->rotate_size, - .size.value = cmd_ctx->session->rotate_size, - }; - - ret = setup_lttng_msg_no_cmd_header(cmd_ctx, &schedules, - sizeof(schedules)); - if (ret < 0) { - ret = -ret; - goto error; - } - - ret = LTTNG_OK; - break; - } - case LTTNG_CLEAR_SESSION: - { - ret = cmd_clear_session(cmd_ctx->session, sock); - break; - } - case LTTNG_LIST_TRIGGERS: - { - struct lttng_triggers *return_triggers = NULL; - size_t original_payload_size; - size_t payload_size; - - ret = setup_empty_lttng_msg(cmd_ctx); - if (ret) { - ret = LTTNG_ERR_NOMEM; - goto setup_error; - } - - original_payload_size = cmd_ctx->reply_payload.buffer.size; - - ret = cmd_list_triggers(cmd_ctx, the_notification_thread_handle, - &return_triggers); - if (ret != LTTNG_OK) { - goto error; - } - - LTTNG_ASSERT(return_triggers); - ret = lttng_triggers_serialize( - return_triggers, &cmd_ctx->reply_payload); - lttng_triggers_destroy(return_triggers); - if (ret) { - ERR("Failed to serialize triggers in reply to `list triggers` command"); - ret = LTTNG_ERR_NOMEM; - goto error; - } - - payload_size = cmd_ctx->reply_payload.buffer.size - - original_payload_size; - - update_lttng_msg(cmd_ctx, 0, payload_size); - - ret = LTTNG_OK; - break; - } - case LTTNG_EXECUTE_ERROR_QUERY: - { - struct lttng_error_query *query; - const struct lttng_credentials cmd_creds = { - .uid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.uid), - .gid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.gid), - }; - struct lttng_error_query_results *results = NULL; - size_t original_payload_size; - size_t payload_size; - - ret = setup_empty_lttng_msg(cmd_ctx); - if (ret) { - ret = LTTNG_ERR_NOMEM; - goto setup_error; - } - - original_payload_size = cmd_ctx->reply_payload.buffer.size; - - ret = receive_lttng_error_query( - cmd_ctx, *sock, sock_error, &query); - if (ret != LTTNG_OK) { - goto error; - } - - ret = cmd_execute_error_query(&cmd_creds, query, &results, - the_notification_thread_handle); - lttng_error_query_destroy(query); - if (ret != LTTNG_OK) { - goto error; - } - - LTTNG_ASSERT(results); - ret = lttng_error_query_results_serialize( - results, &cmd_ctx->reply_payload); - lttng_error_query_results_destroy(results); - if (ret) { - ERR("Failed to serialize error query result set in reply to `execute error query` command"); - ret = LTTNG_ERR_NOMEM; - goto error; - } - - payload_size = cmd_ctx->reply_payload.buffer.size - - original_payload_size; - - update_lttng_msg(cmd_ctx, 0, payload_size); - - ret = LTTNG_OK; - - break; - } - default: - ret = LTTNG_ERR_UND; - break; - } - -error: - if (cmd_ctx->reply_payload.buffer.size == 0) { - DBG("Missing llm header, creating one."); - if (setup_lttng_msg_no_cmd_header(cmd_ctx, NULL, 0) < 0) { - goto setup_error; - } - } - /* Set return code */ - ((struct lttcomm_lttng_msg *) (cmd_ctx->reply_payload.buffer.data))->ret_code = ret; -setup_error: - if (cmd_ctx->session) { - session_unlock(cmd_ctx->session); - session_put(cmd_ctx->session); - cmd_ctx->session = NULL; - } - if (need_tracing_session) { - session_unlock_list(); - } -init_setup_error: - LTTNG_ASSERT(!rcu_read_ongoing()); - return ret; -} - -static int create_client_sock(void) -{ - int ret, client_sock; - const mode_t old_umask = umask(0); - - /* Create client tool unix socket */ - client_sock = lttcomm_create_unix_sock( - the_config.client_unix_sock_path.value); - if (client_sock < 0) { - ERR("Create unix sock failed: %s", - the_config.client_unix_sock_path.value); - ret = -1; - goto end; - } - - /* Set the cloexec flag */ - ret = utils_set_fd_cloexec(client_sock); - if (ret < 0) { - ERR("Unable to set CLOEXEC flag to the client Unix socket (fd: %d). " - "Continuing but note that the consumer daemon will have a " - "reference to this socket on exec()", client_sock); - } - - /* File permission MUST be 660 */ - ret = chmod(the_config.client_unix_sock_path.value, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); - if (ret < 0) { - ERR("Set file permissions failed: %s", - the_config.client_unix_sock_path.value); - PERROR("chmod"); - (void) lttcomm_close_unix_sock(client_sock); - ret = -1; - goto end; - } - DBG("Created client socket (fd = %i)", client_sock); - ret = client_sock; -end: - umask(old_umask); - return ret; -} - -static void cleanup_client_thread(void *data) -{ - struct lttng_pipe *quit_pipe = data; - - lttng_pipe_destroy(quit_pipe); -} - -static void thread_init_cleanup(void *data) -{ - set_thread_status(false); -} - -/* - * This thread manage all clients request using the unix client socket for - * communication. - */ -static void *thread_manage_clients(void *data) -{ - int sock = -1, ret, i, pollfd, err = -1; - int sock_error; - uint32_t revents, nb_fd; - struct lttng_poll_event events; - const int client_sock = thread_state.client_sock; - struct lttng_pipe *quit_pipe = data; - const int thread_quit_pipe_fd = lttng_pipe_get_readfd(quit_pipe); - struct command_ctx cmd_ctx = {}; - - DBG("[thread] Manage client started"); - - lttng_payload_init(&cmd_ctx.reply_payload); - - is_root = (getuid() == 0); - - pthread_cleanup_push(thread_init_cleanup, NULL); - - rcu_register_thread(); - - health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_CMD); - - health_code_update(); - - ret = lttcomm_listen_unix_sock(client_sock); - if (ret < 0) { - goto error_listen; - } - - /* - * Pass 2 as size here for the thread quit pipe and client_sock. Nothing - * more will be added to this poll set. - */ - ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); - if (ret < 0) { - goto error_create_poll; - } - - /* Add the application registration socket */ - ret = lttng_poll_add(&events, client_sock, LPOLLIN | LPOLLPRI); - if (ret < 0) { - goto error; - } - - /* Add thread quit pipe */ - ret = lttng_poll_add(&events, thread_quit_pipe_fd, LPOLLIN | LPOLLERR); - if (ret < 0) { - goto error; - } - - /* Set state as running. */ - set_thread_status(true); - pthread_cleanup_pop(0); - - /* This testpoint is after we signal readiness to the parent. */ - if (testpoint(sessiond_thread_manage_clients)) { - goto error; - } - - if (testpoint(sessiond_thread_manage_clients_before_loop)) { - goto error; - } - - health_code_update(); - - while (1) { - const struct cmd_completion_handler *cmd_completion_handler; - - cmd_ctx.creds = (lttng_sock_cred) { - .uid = UINT32_MAX, - .gid = UINT32_MAX, - }; - cmd_ctx.session = NULL; - lttng_payload_clear(&cmd_ctx.reply_payload); - cmd_ctx.lttng_msg_size = 0; - - DBG("Accepting client command ..."); - - /* Inifinite blocking call, waiting for transmission */ - restart: - health_poll_entry(); - ret = lttng_poll_wait(&events, -1); - health_poll_exit(); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - goto restart; - } - goto error; - } - - nb_fd = ret; - - for (i = 0; i < nb_fd; i++) { - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - health_code_update(); - - if (pollfd == thread_quit_pipe_fd) { - err = 0; - goto exit; - } else { - /* Event on the registration socket */ - if (revents & LPOLLIN) { - continue; - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ERR("Client socket poll error"); - goto error; - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - } - } - - DBG("Wait for client response"); - - health_code_update(); - - sock = lttcomm_accept_unix_sock(client_sock); - if (sock < 0) { - goto error; - } - - /* - * Set the CLOEXEC flag. Return code is useless because either way, the - * show must go on. - */ - (void) utils_set_fd_cloexec(sock); - - /* Set socket option for credentials retrieval */ - ret = lttcomm_setsockopt_creds_unix_sock(sock); - if (ret < 0) { - goto error; - } - - health_code_update(); - - /* - * Data is received from the lttng client. The struct - * lttcomm_session_msg (lsm) contains the command and data request of - * the client. - */ - DBG("Receiving data from client ..."); - ret = lttcomm_recv_creds_unix_sock(sock, &cmd_ctx.lsm, - sizeof(struct lttcomm_session_msg), &cmd_ctx.creds); - if (ret != sizeof(struct lttcomm_session_msg)) { - DBG("Incomplete recv() from client... continuing"); - ret = close(sock); - if (ret) { - PERROR("close"); - } - sock = -1; - continue; - } - - health_code_update(); - - // TODO: Validate cmd_ctx including sanity check for - // security purpose. - - rcu_thread_online(); - /* - * This function dispatch the work to the kernel or userspace tracer - * libs and fill the lttcomm_lttng_msg data structure of all the needed - * informations for the client. The command context struct contains - * everything this function may needs. - */ - ret = process_client_msg(&cmd_ctx, &sock, &sock_error); - rcu_thread_offline(); - if (ret < 0) { - if (sock >= 0) { - ret = close(sock); - if (ret) { - PERROR("close"); - } - } - sock = -1; - /* - * TODO: Inform client somehow of the fatal error. At - * this point, ret < 0 means that a zmalloc failed - * (ENOMEM). Error detected but still accept - * command, unless a socket error has been - * detected. - */ - continue; - } - - if (ret < LTTNG_OK || ret >= LTTNG_ERR_NR) { - WARN("Command returned an invalid status code, returning unknown error: " - "command type = %s (%d), ret = %d", - lttcomm_sessiond_command_str(cmd_ctx.lsm.cmd_type), - cmd_ctx.lsm.cmd_type, ret); - ret = LTTNG_ERR_UNK; - } - - cmd_completion_handler = cmd_pop_completion_handler(); - if (cmd_completion_handler) { - enum lttng_error_code completion_code; - - completion_code = cmd_completion_handler->run( - cmd_completion_handler->data); - if (completion_code != LTTNG_OK) { - continue; - } - } - - health_code_update(); - - if (sock >= 0) { - struct lttng_payload_view view = - lttng_payload_view_from_payload( - &cmd_ctx.reply_payload, - 0, -1); - struct lttcomm_lttng_msg *llm = (typeof( - llm)) cmd_ctx.reply_payload.buffer.data; - - LTTNG_ASSERT(cmd_ctx.reply_payload.buffer.size >= sizeof(*llm)); - LTTNG_ASSERT(cmd_ctx.lttng_msg_size == cmd_ctx.reply_payload.buffer.size); - - llm->fd_count = lttng_payload_view_get_fd_handle_count(&view); - - DBG("Sending response (size: %d, retcode: %s (%d))", - cmd_ctx.lttng_msg_size, - lttng_strerror(-llm->ret_code), - llm->ret_code); - ret = send_unix_sock(sock, &view); - if (ret < 0) { - ERR("Failed to send data back to client"); - } - - /* End of transmission */ - ret = close(sock); - if (ret) { - PERROR("close"); - } - } - sock = -1; - - health_code_update(); - } - -exit: -error: - if (sock >= 0) { - ret = close(sock); - if (ret) { - PERROR("close"); - } - } - - lttng_poll_clean(&events); - -error_listen: -error_create_poll: - unlink(the_config.client_unix_sock_path.value); - ret = close(client_sock); - if (ret) { - PERROR("close"); - } - - if (err) { - health_error(); - ERR("Health error occurred in %s", __func__); - } - - health_unregister(the_health_sessiond); - - DBG("Client thread dying"); - lttng_payload_reset(&cmd_ctx.reply_payload); - rcu_unregister_thread(); - return NULL; -} - -static -bool shutdown_client_thread(void *thread_data) -{ - struct lttng_pipe *client_quit_pipe = thread_data; - const int write_fd = lttng_pipe_get_writefd(client_quit_pipe); - - return notify_thread_pipe(write_fd) == 1; -} - -struct lttng_thread *launch_client_thread(void) -{ - bool thread_running; - struct lttng_pipe *client_quit_pipe; - struct lttng_thread *thread = NULL; - int client_sock_fd = -1; - - sem_init(&thread_state.ready, 0, 0); - client_quit_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!client_quit_pipe) { - goto error; - } - - client_sock_fd = create_client_sock(); - if (client_sock_fd < 0) { - goto error; - } - - thread_state.client_sock = client_sock_fd; - thread = lttng_thread_create("Client management", - thread_manage_clients, - shutdown_client_thread, - cleanup_client_thread, - client_quit_pipe); - if (!thread) { - goto error; - } - /* The client thread now owns the client sock fd and the quit pipe. */ - client_sock_fd = -1; - client_quit_pipe = NULL; - - /* - * This thread is part of the threads that need to be fully - * initialized before the session daemon is marked as "ready". - */ - thread_running = wait_thread_status(); - if (!thread_running) { - goto error; - } - return thread; -error: - if (client_sock_fd >= 0) { - if (close(client_sock_fd)) { - PERROR("Failed to close client socket"); - } - } - lttng_thread_put(thread); - cleanup_client_thread(client_quit_pipe); - return NULL; -} diff --git a/src/bin/lttng-sessiond/client.cpp b/src/bin/lttng-sessiond/client.cpp new file mode 100644 index 000000000..154fa4a36 --- /dev/null +++ b/src/bin/lttng-sessiond/client.cpp @@ -0,0 +1,2919 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "common/buffer-view.h" +#include "common/compat/socket.h" +#include "common/dynamic-array.h" +#include "common/dynamic-buffer.h" +#include "common/fd-handle.h" +#include "common/payload-view.h" +#include "common/payload.h" +#include "common/sessiond-comm/sessiond-comm.h" +#include "lttng/lttng-error.h" +#include "lttng/tracker.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent-thread.h" +#include "clear.h" +#include "client.h" +#include "cmd.h" +#include "health-sessiond.h" +#include "kernel.h" +#include "lttng-sessiond.h" +#include "manage-consumer.h" +#include "save.h" +#include "testpoint.h" +#include "utils.h" + +static bool is_root; + +static struct thread_state { + sem_t ready; + bool running; + int client_sock; +} thread_state; + +static void set_thread_status(bool running) +{ + DBG("Marking client thread's state as %s", running ? "running" : "error"); + thread_state.running = running; + sem_post(&thread_state.ready); +} + +static bool wait_thread_status(void) +{ + DBG("Waiting for client thread to be ready"); + sem_wait(&thread_state.ready); + if (thread_state.running) { + DBG("Client thread is ready"); + } else { + ERR("Initialization of client thread failed"); + } + + return thread_state.running; +} + +/* + * Setup the outgoing data buffer for the response (llm) by allocating the + * right amount of memory and copying the original information from the lsm + * structure. + * + * Return 0 on success, negative value on error. + */ +static int setup_lttng_msg(struct command_ctx *cmd_ctx, + const void *payload_buf, size_t payload_len, + const void *cmd_header_buf, size_t cmd_header_len) +{ + int ret = 0; + const size_t header_len = sizeof(struct lttcomm_lttng_msg); + const size_t total_msg_size = header_len + cmd_header_len + payload_len; + lttcomm_lttng_msg llm {}; + + llm.cmd_type = cmd_ctx->lsm.cmd_type; + llm.pid = (uint32_t) cmd_ctx->lsm.domain.attr.pid; + llm.cmd_header_size = (uint32_t) cmd_header_len; + llm.data_size = (uint32_t) payload_len; + + ret = lttng_dynamic_buffer_set_size(&cmd_ctx->reply_payload.buffer, 0); + if (ret) { + goto end; + } + + lttng_dynamic_pointer_array_clear(&cmd_ctx->reply_payload._fd_handles); + + cmd_ctx->lttng_msg_size = total_msg_size; + + /* Append reply header. */ + ret = lttng_dynamic_buffer_append( + &cmd_ctx->reply_payload.buffer, &llm, sizeof(llm)); + if (ret) { + goto end; + } + + /* Append command header. */ + if (cmd_header_len) { + ret = lttng_dynamic_buffer_append( + &cmd_ctx->reply_payload.buffer, cmd_header_buf, + cmd_header_len); + if (ret) { + goto end; + } + } + + /* Append payload. */ + if (payload_len) { + ret = lttng_dynamic_buffer_append( + &cmd_ctx->reply_payload.buffer, payload_buf, + payload_len); + if (ret) { + goto end; + } + } + +end: + return ret; +} + +static int setup_empty_lttng_msg(struct command_ctx *cmd_ctx) +{ + int ret; + const struct lttcomm_lttng_msg llm = {}; + + ret = lttng_dynamic_buffer_set_size(&cmd_ctx->reply_payload.buffer, 0); + if (ret) { + goto end; + } + + /* Append place-holder reply header. */ + ret = lttng_dynamic_buffer_append( + &cmd_ctx->reply_payload.buffer, &llm, sizeof(llm)); + if (ret) { + goto end; + } + + cmd_ctx->lttng_msg_size = sizeof(llm); +end: + return ret; +} + +static void update_lttng_msg(struct command_ctx *cmd_ctx, size_t cmd_header_len, + size_t payload_len) +{ + const size_t header_len = sizeof(struct lttcomm_lttng_msg); + const size_t total_msg_size = header_len + cmd_header_len + payload_len; + struct lttcomm_lttng_msg *p_llm; + lttcomm_lttng_msg llm {}; + + llm.cmd_type = cmd_ctx->lsm.cmd_type; + llm.pid = (uint32_t) cmd_ctx->lsm.domain.attr.pid; + llm.cmd_header_size = (uint32_t) cmd_header_len; + llm.data_size = (uint32_t) payload_len; + + LTTNG_ASSERT(cmd_ctx->reply_payload.buffer.size >= sizeof(llm)); + + p_llm = (typeof(p_llm)) cmd_ctx->reply_payload.buffer.data; + + /* Update existing header. */ + memcpy(p_llm, &llm, sizeof(llm)); + + cmd_ctx->lttng_msg_size = total_msg_size; +} + +/* + * Start the thread_manage_consumer. This must be done after a lttng-consumerd + * exec or it will fail. + */ +static int spawn_consumer_thread(struct consumer_data *consumer_data) +{ + return launch_consumer_management_thread(consumer_data) ? 0 : -1; +} + +/* + * Fork and exec a consumer daemon (consumerd). + * + * Return pid if successful else -1. + */ +static pid_t spawn_consumerd(struct consumer_data *consumer_data) +{ + int ret; + pid_t pid; + const char *consumer_to_use; + const char *verbosity; + struct stat st; + + DBG("Spawning consumerd"); + + pid = fork(); + if (pid == 0) { + /* + * Exec consumerd. + */ + if (the_config.verbose_consumer) { + verbosity = "--verbose"; + } else if (lttng_opt_quiet) { + verbosity = "--quiet"; + } else { + verbosity = ""; + } + + switch (consumer_data->type) { + case LTTNG_CONSUMER_KERNEL: + /* + * Find out which consumerd to execute. We will first try the + * 64-bit path, then the sessiond's installation directory, and + * fallback on the 32-bit one, + */ + DBG3("Looking for a kernel consumer at these locations:"); + DBG3(" 1) %s", the_config.consumerd64_bin_path.value ? : "NULL"); + DBG3(" 2) %s/%s", INSTALL_BIN_PATH, DEFAULT_CONSUMERD_FILE); + DBG3(" 3) %s", the_config.consumerd32_bin_path.value ? : "NULL"); + if (stat(the_config.consumerd64_bin_path.value, &st) == 0) { + DBG3("Found location #1"); + consumer_to_use = the_config.consumerd64_bin_path.value; + } else if (stat(INSTALL_BIN_PATH "/" DEFAULT_CONSUMERD_FILE, &st) == 0) { + DBG3("Found location #2"); + consumer_to_use = INSTALL_BIN_PATH "/" DEFAULT_CONSUMERD_FILE; + } else if (the_config.consumerd32_bin_path.value && + stat(the_config.consumerd32_bin_path.value, &st) == 0) { + DBG3("Found location #3"); + consumer_to_use = the_config.consumerd32_bin_path.value; + } else { + DBG("Could not find any valid consumerd executable"); + ret = -EINVAL; + goto error; + } + DBG("Using kernel consumer at: %s", consumer_to_use); + (void) execl(consumer_to_use, "lttng-consumerd", + verbosity, "-k", "--consumerd-cmd-sock", + consumer_data->cmd_unix_sock_path, + "--consumerd-err-sock", + consumer_data->err_unix_sock_path, + "--group", + the_config.tracing_group_name.value, + NULL); + break; + case LTTNG_CONSUMER64_UST: + { + if (the_config.consumerd64_lib_dir.value) { + const char *tmp; + size_t tmplen; + char *tmpnew; + + tmp = lttng_secure_getenv("LD_LIBRARY_PATH"); + if (!tmp) { + tmp = ""; + } + tmplen = strlen(the_config.consumerd64_lib_dir.value) + 1 /* : */ + strlen(tmp); + tmpnew = (char *) zmalloc(tmplen + 1 /* \0 */); + if (!tmpnew) { + ret = -ENOMEM; + goto error; + } + strcat(tmpnew, the_config.consumerd64_lib_dir.value); + if (tmp[0] != '\0') { + strcat(tmpnew, ":"); + strcat(tmpnew, tmp); + } + ret = setenv("LD_LIBRARY_PATH", tmpnew, 1); + free(tmpnew); + if (ret) { + ret = -errno; + goto error; + } + } + DBG("Using 64-bit UST consumer at: %s", + the_config.consumerd64_bin_path.value); + (void) execl(the_config.consumerd64_bin_path.value, + "lttng-consumerd", verbosity, "-u", + "--consumerd-cmd-sock", + consumer_data->cmd_unix_sock_path, + "--consumerd-err-sock", + consumer_data->err_unix_sock_path, + "--group", + the_config.tracing_group_name.value, + NULL); + break; + } + case LTTNG_CONSUMER32_UST: + { + if (the_config.consumerd32_lib_dir.value) { + const char *tmp; + size_t tmplen; + char *tmpnew; + + tmp = lttng_secure_getenv("LD_LIBRARY_PATH"); + if (!tmp) { + tmp = ""; + } + tmplen = strlen(the_config.consumerd32_lib_dir.value) + 1 /* : */ + strlen(tmp); + tmpnew = (char *) zmalloc(tmplen + 1 /* \0 */); + if (!tmpnew) { + ret = -ENOMEM; + goto error; + } + strcat(tmpnew, the_config.consumerd32_lib_dir.value); + if (tmp[0] != '\0') { + strcat(tmpnew, ":"); + strcat(tmpnew, tmp); + } + ret = setenv("LD_LIBRARY_PATH", tmpnew, 1); + free(tmpnew); + if (ret) { + ret = -errno; + goto error; + } + } + DBG("Using 32-bit UST consumer at: %s", + the_config.consumerd32_bin_path.value); + (void) execl(the_config.consumerd32_bin_path.value, + "lttng-consumerd", verbosity, "-u", + "--consumerd-cmd-sock", + consumer_data->cmd_unix_sock_path, + "--consumerd-err-sock", + consumer_data->err_unix_sock_path, + "--group", + the_config.tracing_group_name.value, + NULL); + break; + } + default: + ERR("unknown consumer type"); + errno = 0; + } + if (errno != 0) { + PERROR("Consumer execl()"); + } + /* Reaching this point, we got a failure on our execl(). */ + exit(EXIT_FAILURE); + } else if (pid > 0) { + ret = pid; + } else { + PERROR("start consumer fork"); + ret = -errno; + } +error: + return ret; +} + +/* + * Spawn the consumerd daemon and session daemon thread. + */ +static int start_consumerd(struct consumer_data *consumer_data) +{ + int ret; + + /* + * Set the listen() state on the socket since there is a possible race + * between the exec() of the consumer daemon and this call if place in the + * consumer thread. See bug #366 for more details. + */ + ret = lttcomm_listen_unix_sock(consumer_data->err_sock); + if (ret < 0) { + goto error; + } + + pthread_mutex_lock(&consumer_data->pid_mutex); + if (consumer_data->pid != 0) { + pthread_mutex_unlock(&consumer_data->pid_mutex); + goto end; + } + + ret = spawn_consumerd(consumer_data); + if (ret < 0) { + ERR("Spawning consumerd failed"); + pthread_mutex_unlock(&consumer_data->pid_mutex); + goto error; + } + + /* Setting up the consumer_data pid */ + consumer_data->pid = ret; + DBG2("Consumer pid %d", consumer_data->pid); + pthread_mutex_unlock(&consumer_data->pid_mutex); + + DBG2("Spawning consumer control thread"); + ret = spawn_consumer_thread(consumer_data); + if (ret < 0) { + ERR("Fatal error spawning consumer control thread"); + goto error; + } + +end: + return 0; + +error: + /* Cleanup already created sockets on error. */ + if (consumer_data->err_sock >= 0) { + int err; + + err = close(consumer_data->err_sock); + if (err < 0) { + PERROR("close consumer data error socket"); + } + } + return ret; +} + +/* + * Copy consumer output from the tracing session to the domain session. The + * function also applies the right modification on a per domain basis for the + * trace files destination directory. + * + * Should *NOT* be called with RCU read-side lock held. + */ +static int copy_session_consumer(int domain, struct ltt_session *session) +{ + int ret; + const char *dir_name; + struct consumer_output *consumer; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(session->consumer); + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + DBG3("Copying tracing session consumer output in kernel session"); + /* + * XXX: We should audit the session creation and what this function + * does "extra" in order to avoid a destroy since this function is used + * in the domain session creation (kernel and ust) only. Same for UST + * domain. + */ + if (session->kernel_session->consumer) { + consumer_output_put(session->kernel_session->consumer); + } + session->kernel_session->consumer = + consumer_copy_output(session->consumer); + /* Ease our life a bit for the next part */ + consumer = session->kernel_session->consumer; + dir_name = DEFAULT_KERNEL_TRACE_DIR; + break; + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + case LTTNG_DOMAIN_UST: + DBG3("Copying tracing session consumer output in UST session"); + if (session->ust_session->consumer) { + consumer_output_put(session->ust_session->consumer); + } + session->ust_session->consumer = + consumer_copy_output(session->consumer); + /* Ease our life a bit for the next part */ + consumer = session->ust_session->consumer; + dir_name = DEFAULT_UST_TRACE_DIR; + break; + default: + ret = LTTNG_ERR_UNKNOWN_DOMAIN; + goto error; + } + + /* Append correct directory to subdir */ + ret = lttng_strncpy(consumer->domain_subdir, dir_name, + sizeof(consumer->domain_subdir)); + if (ret) { + ret = LTTNG_ERR_UNK; + goto error; + } + DBG3("Copy session consumer subdir %s", consumer->domain_subdir); + ret = LTTNG_OK; + +error: + return ret; +} + +/* + * Create an UST session and add it to the session ust list. + * + * Should *NOT* be called with RCU read-side lock held. + */ +static int create_ust_session(struct ltt_session *session, + const struct lttng_domain *domain) +{ + int ret; + struct ltt_ust_session *lus = NULL; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(domain); + LTTNG_ASSERT(session->consumer); + + switch (domain->type) { + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + case LTTNG_DOMAIN_UST: + break; + default: + ERR("Unknown UST domain on create session %d", domain->type); + ret = LTTNG_ERR_UNKNOWN_DOMAIN; + goto error; + } + + DBG("Creating UST session"); + + lus = trace_ust_create_session(session->id); + if (lus == NULL) { + ret = LTTNG_ERR_UST_SESS_FAIL; + goto error; + } + + lus->uid = session->uid; + lus->gid = session->gid; + lus->output_traces = session->output_traces; + lus->snapshot_mode = session->snapshot_mode; + lus->live_timer_interval = session->live_timer; + session->ust_session = lus; + if (session->shm_path[0]) { + strncpy(lus->root_shm_path, session->shm_path, + sizeof(lus->root_shm_path)); + lus->root_shm_path[sizeof(lus->root_shm_path) - 1] = '\0'; + strncpy(lus->shm_path, session->shm_path, + sizeof(lus->shm_path)); + lus->shm_path[sizeof(lus->shm_path) - 1] = '\0'; + strncat(lus->shm_path, "/ust", + sizeof(lus->shm_path) - strlen(lus->shm_path) - 1); + } + /* Copy session output to the newly created UST session */ + ret = copy_session_consumer(domain->type, session); + if (ret != LTTNG_OK) { + goto error; + } + + return LTTNG_OK; + +error: + free(lus); + session->ust_session = NULL; + return ret; +} + +/* + * Create a kernel tracer session then create the default channel. + */ +static int create_kernel_session(struct ltt_session *session) +{ + int ret; + + DBG("Creating kernel session"); + + ret = kernel_create_session(session); + if (ret < 0) { + ret = LTTNG_ERR_KERN_SESS_FAIL; + goto error_create; + } + + /* Code flow safety */ + LTTNG_ASSERT(session->kernel_session); + + /* Copy session output to the newly created Kernel session */ + ret = copy_session_consumer(LTTNG_DOMAIN_KERNEL, session); + if (ret != LTTNG_OK) { + goto error; + } + + session->kernel_session->uid = session->uid; + session->kernel_session->gid = session->gid; + session->kernel_session->output_traces = session->output_traces; + session->kernel_session->snapshot_mode = session->snapshot_mode; + session->kernel_session->is_live_session = session->live_timer != 0; + + return LTTNG_OK; + +error: + trace_kernel_destroy_session(session->kernel_session); + session->kernel_session = NULL; +error_create: + return ret; +} + +/* + * Count number of session permitted by uid/gid. + */ +static unsigned int lttng_sessions_count(uid_t uid, gid_t gid) +{ + unsigned int i = 0; + struct ltt_session *session; + const struct ltt_session_list *session_list = session_get_list(); + + DBG("Counting number of available session for UID %d", uid); + cds_list_for_each_entry(session, &session_list->head, list) { + if (!session_get(session)) { + continue; + } + session_lock(session); + /* Only count the sessions the user can control. */ + if (session_access_ok(session, uid) && + !session->destroyed) { + i++; + } + session_unlock(session); + session_put(session); + } + return i; +} + +static int receive_userspace_probe(struct command_ctx *cmd_ctx, int sock, + int *sock_error, struct lttng_event *event) +{ + int fd = -1, ret; + struct lttng_userspace_probe_location *probe_location; + struct lttng_payload probe_location_payload; + struct fd_handle *handle = NULL; + + /* + * Create a payload to store the serialized version of the probe + * location. + */ + lttng_payload_init(&probe_location_payload); + + ret = lttng_dynamic_buffer_set_size(&probe_location_payload.buffer, + cmd_ctx->lsm.u.enable.userspace_probe_location_len); + if (ret) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + + /* + * Receive the probe location. + */ + ret = lttcomm_recv_unix_sock(sock, probe_location_payload.buffer.data, + probe_location_payload.buffer.size); + if (ret <= 0) { + DBG("Nothing recv() from client var len data... continuing"); + *sock_error = 1; + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + + /* + * 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"); + *sock_error = 1; + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + + handle = fd_handle_create(fd); + if (!handle) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + + /* Transferred to the handle. */ + fd = -1; + + ret = lttng_payload_push_fd_handle(&probe_location_payload, handle); + if (ret) { + ERR("Failed to add userspace probe file descriptor to payload"); + ret = LTTNG_ERR_NOMEM; + goto error; + } + + fd_handle_put(handle); + handle = NULL; + + { + struct lttng_payload_view view = lttng_payload_view_from_payload( + &probe_location_payload, 0, -1); + + /* Extract the probe location from the serialized version. */ + ret = lttng_userspace_probe_location_create_from_payload( + &view, &probe_location); + } + if (ret < 0) { + WARN("Failed to create a userspace probe location from the received buffer"); + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + + /* Attach the probe location to the event. */ + ret = lttng_event_set_userspace_probe_location(event, probe_location); + if (ret) { + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + +error: + if (fd >= 0) { + if (close(fd)) { + PERROR("Failed to close userspace probe location binary fd"); + } + } + + fd_handle_put(handle); + lttng_payload_reset(&probe_location_payload); + return ret; +} + +static enum lttng_error_code receive_lttng_trigger(struct command_ctx *cmd_ctx, + int sock, + int *sock_error, + struct lttng_trigger **_trigger) +{ + int ret; + size_t trigger_len; + ssize_t sock_recv_len; + enum lttng_error_code ret_code; + struct lttng_payload trigger_payload; + struct lttng_trigger *trigger = NULL; + + lttng_payload_init(&trigger_payload); + trigger_len = (size_t) cmd_ctx->lsm.u.trigger.length; + ret = lttng_dynamic_buffer_set_size( + &trigger_payload.buffer, trigger_len); + if (ret) { + ret_code = LTTNG_ERR_NOMEM; + goto end; + } + + sock_recv_len = lttcomm_recv_unix_sock( + sock, trigger_payload.buffer.data, trigger_len); + if (sock_recv_len < 0 || sock_recv_len != trigger_len) { + ERR("Failed to receive trigger in command payload"); + *sock_error = 1; + ret_code = LTTNG_ERR_INVALID_PROTOCOL; + goto end; + } + + /* Receive fds, if any. */ + if (cmd_ctx->lsm.fd_count > 0) { + sock_recv_len = lttcomm_recv_payload_fds_unix_sock( + sock, cmd_ctx->lsm.fd_count, &trigger_payload); + if (sock_recv_len > 0 && + sock_recv_len != cmd_ctx->lsm.fd_count * sizeof(int)) { + ERR("Failed to receive all file descriptors for trigger in command payload: expected fd count = %u, ret = %d", + cmd_ctx->lsm.fd_count, (int) ret); + ret_code = LTTNG_ERR_INVALID_PROTOCOL; + *sock_error = 1; + goto end; + } else if (sock_recv_len <= 0) { + ERR("Failed to receive file descriptors for trigger in command payload: expected fd count = %u, ret = %d", + cmd_ctx->lsm.fd_count, (int) ret); + ret_code = LTTNG_ERR_FATAL; + *sock_error = 1; + goto end; + } + } + + /* Deserialize trigger. */ + { + struct lttng_payload_view view = + lttng_payload_view_from_payload( + &trigger_payload, 0, -1); + + if (lttng_trigger_create_from_payload(&view, &trigger) != + trigger_len) { + ERR("Invalid trigger received as part of command payload"); + ret_code = LTTNG_ERR_INVALID_TRIGGER; + lttng_trigger_put(trigger); + goto end; + } + } + + *_trigger = trigger; + ret_code = LTTNG_OK; + +end: + lttng_payload_reset(&trigger_payload); + return ret_code; +} + +static enum lttng_error_code receive_lttng_error_query(struct command_ctx *cmd_ctx, + int sock, + int *sock_error, + struct lttng_error_query **_query) +{ + int ret; + size_t query_len; + ssize_t sock_recv_len; + enum lttng_error_code ret_code; + struct lttng_payload query_payload; + struct lttng_error_query *query = NULL; + + lttng_payload_init(&query_payload); + query_len = (size_t) cmd_ctx->lsm.u.error_query.length; + ret = lttng_dynamic_buffer_set_size(&query_payload.buffer, query_len); + if (ret) { + ret_code = LTTNG_ERR_NOMEM; + goto end; + } + + sock_recv_len = lttcomm_recv_unix_sock( + sock, query_payload.buffer.data, query_len); + if (sock_recv_len < 0 || sock_recv_len != query_len) { + ERR("Failed to receive error query in command payload"); + *sock_error = 1; + ret_code = LTTNG_ERR_INVALID_PROTOCOL; + goto end; + } + + /* Receive fds, if any. */ + if (cmd_ctx->lsm.fd_count > 0) { + sock_recv_len = lttcomm_recv_payload_fds_unix_sock( + sock, cmd_ctx->lsm.fd_count, &query_payload); + if (sock_recv_len > 0 && + sock_recv_len != cmd_ctx->lsm.fd_count * sizeof(int)) { + ERR("Failed to receive all file descriptors for error query in command payload: expected fd count = %u, ret = %d", + cmd_ctx->lsm.fd_count, (int) ret); + ret_code = LTTNG_ERR_INVALID_PROTOCOL; + *sock_error = 1; + goto end; + } else if (sock_recv_len <= 0) { + ERR("Failed to receive file descriptors for error query in command payload: expected fd count = %u, ret = %d", + cmd_ctx->lsm.fd_count, (int) ret); + ret_code = LTTNG_ERR_FATAL; + *sock_error = 1; + goto end; + } + } + + /* Deserialize error query. */ + { + struct lttng_payload_view view = + lttng_payload_view_from_payload( + &query_payload, 0, -1); + + if (lttng_error_query_create_from_payload(&view, &query) != + query_len) { + ERR("Invalid error query received as part of command payload"); + ret_code = LTTNG_ERR_INVALID_PROTOCOL; + goto end; + } + } + + *_query = query; + ret_code = LTTNG_OK; + +end: + lttng_payload_reset(&query_payload); + return ret_code; +} + +/* + * Version of setup_lttng_msg() without command header. + */ +static int setup_lttng_msg_no_cmd_header(struct command_ctx *cmd_ctx, + void *payload_buf, size_t payload_len) +{ + return setup_lttng_msg(cmd_ctx, payload_buf, payload_len, NULL, 0); +} + +/* + * Check if the current kernel tracer supports the session rotation feature. + * Return 1 if it does, 0 otherwise. + */ +static int check_rotate_compatible(void) +{ + int ret = 1; + + if (the_kernel_tracer_version.major != 2 || + the_kernel_tracer_version.minor < 11) { + DBG("Kernel tracer version is not compatible with the rotation feature"); + ret = 0; + } + + return ret; +} + +/* + * Send data on a unix socket using the liblttsessiondcomm API. + * + * Return lttcomm error code. + */ +static int send_unix_sock(int sock, struct lttng_payload_view *view) +{ + int ret; + const int fd_count = lttng_payload_view_get_fd_handle_count(view); + + /* Check valid length */ + if (view->buffer.size == 0) { + ret = -1; + goto end; + } + + ret = lttcomm_send_unix_sock( + sock, view->buffer.data, view->buffer.size); + if (ret < 0) { + goto end; + } + + if (fd_count > 0) { + ret = lttcomm_send_payload_view_fds_unix_sock(sock, view); + if (ret < 0) { + goto end; + } + } + +end: + return ret; +} + +/* + * Process the command requested by the lttng client within the command + * context structure. This function make sure that the return structure (llm) + * is set and ready for transmission before returning. + * + * Return any error encountered or 0 for success. + * + * "sock" is only used for special-case var. len data. + * A command may assume the ownership of the socket, in which case its value + * should be set to -1. + * + * Should *NOT* be called with RCU read-side lock held. + */ +static int process_client_msg(struct command_ctx *cmd_ctx, int *sock, + int *sock_error) +{ + int ret = LTTNG_OK; + bool need_tracing_session = true; + bool need_domain; + bool need_consumerd; + + DBG("Processing client command '%s\' (%d)", + lttcomm_sessiond_command_str((lttcomm_sessiond_command) cmd_ctx->lsm.cmd_type), + cmd_ctx->lsm.cmd_type); + + LTTNG_ASSERT(!rcu_read_ongoing()); + + *sock_error = 0; + + switch (cmd_ctx->lsm.cmd_type) { + case LTTNG_CREATE_SESSION_EXT: + case LTTNG_DESTROY_SESSION: + case LTTNG_LIST_SESSIONS: + case LTTNG_LIST_DOMAINS: + case LTTNG_START_TRACE: + case LTTNG_STOP_TRACE: + case LTTNG_DATA_PENDING: + case LTTNG_SNAPSHOT_ADD_OUTPUT: + case LTTNG_SNAPSHOT_DEL_OUTPUT: + case LTTNG_SNAPSHOT_LIST_OUTPUT: + case LTTNG_SNAPSHOT_RECORD: + case LTTNG_SAVE_SESSION: + case LTTNG_SET_SESSION_SHM_PATH: + case LTTNG_REGENERATE_METADATA: + case LTTNG_REGENERATE_STATEDUMP: + 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: + case LTTNG_EXECUTE_ERROR_QUERY: + need_domain = false; + break; + default: + need_domain = true; + } + + /* Needs a functioning consumerd? */ + switch (cmd_ctx->lsm.cmd_type) { + case LTTNG_REGISTER_TRIGGER: + case LTTNG_UNREGISTER_TRIGGER: + case LTTNG_EXECUTE_ERROR_QUERY: + need_consumerd = false; + break; + default: + need_consumerd = true; + break; + } + + if (the_config.no_kernel && need_domain && + cmd_ctx->lsm.domain.type == LTTNG_DOMAIN_KERNEL) { + if (!is_root) { + ret = LTTNG_ERR_NEED_ROOT_SESSIOND; + } else { + ret = LTTNG_ERR_KERN_NA; + } + goto error; + } + + /* Deny register consumer if we already have a spawned consumer. */ + if (cmd_ctx->lsm.cmd_type == LTTNG_REGISTER_CONSUMER) { + pthread_mutex_lock(&the_kconsumer_data.pid_mutex); + if (the_kconsumer_data.pid > 0) { + ret = LTTNG_ERR_KERN_CONSUMER_FAIL; + pthread_mutex_unlock(&the_kconsumer_data.pid_mutex); + goto error; + } + pthread_mutex_unlock(&the_kconsumer_data.pid_mutex); + } + + /* + * Check for command that don't needs to allocate a returned payload. We do + * this here so we don't have to make the call for no payload at each + * command. + */ + switch(cmd_ctx->lsm.cmd_type) { + case LTTNG_LIST_SESSIONS: + case LTTNG_LIST_TRACEPOINTS: + case LTTNG_LIST_TRACEPOINT_FIELDS: + case LTTNG_LIST_DOMAINS: + case LTTNG_LIST_CHANNELS: + case LTTNG_LIST_EVENTS: + case LTTNG_LIST_SYSCALLS: + case LTTNG_SESSION_LIST_ROTATION_SCHEDULES: + case LTTNG_PROCESS_ATTR_TRACKER_GET_POLICY: + case LTTNG_PROCESS_ATTR_TRACKER_GET_INCLUSION_SET: + case LTTNG_DATA_PENDING: + case LTTNG_ROTATE_SESSION: + case LTTNG_ROTATION_GET_INFO: + case LTTNG_REGISTER_TRIGGER: + case LTTNG_LIST_TRIGGERS: + case LTTNG_EXECUTE_ERROR_QUERY: + break; + default: + /* Setup lttng message with no payload */ + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, NULL, 0); + if (ret < 0) { + /* This label does not try to unlock the session */ + goto init_setup_error; + } + } + + /* Commands that DO NOT need a session. */ + switch (cmd_ctx->lsm.cmd_type) { + case LTTNG_CREATE_SESSION_EXT: + case LTTNG_LIST_SESSIONS: + case LTTNG_LIST_TRACEPOINTS: + case LTTNG_LIST_SYSCALLS: + case LTTNG_LIST_TRACEPOINT_FIELDS: + case LTTNG_SAVE_SESSION: + case LTTNG_REGISTER_TRIGGER: + case LTTNG_UNREGISTER_TRIGGER: + case LTTNG_LIST_TRIGGERS: + case LTTNG_EXECUTE_ERROR_QUERY: + need_tracing_session = false; + break; + default: + DBG("Getting session %s by name", cmd_ctx->lsm.session.name); + /* + * We keep the session list lock across _all_ commands + * for now, because the per-session lock does not + * handle teardown properly. + */ + session_lock_list(); + cmd_ctx->session = session_find_by_name(cmd_ctx->lsm.session.name); + if (cmd_ctx->session == NULL) { + ret = LTTNG_ERR_SESS_NOT_FOUND; + goto error; + } else { + /* Acquire lock for the session */ + session_lock(cmd_ctx->session); + } + break; + } + + /* + * Commands that need a valid session but should NOT create one if none + * exists. Instead of creating one and destroying it when the command is + * handled, process that right before so we save some round trip in useless + * code path. + */ + switch (cmd_ctx->lsm.cmd_type) { + case LTTNG_DISABLE_CHANNEL: + case LTTNG_DISABLE_EVENT: + switch (cmd_ctx->lsm.domain.type) { + case LTTNG_DOMAIN_KERNEL: + if (!cmd_ctx->session->kernel_session) { + ret = LTTNG_ERR_NO_CHANNEL; + goto error; + } + break; + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + case LTTNG_DOMAIN_UST: + if (!cmd_ctx->session->ust_session) { + ret = LTTNG_ERR_NO_CHANNEL; + goto error; + } + break; + default: + ret = LTTNG_ERR_UNKNOWN_DOMAIN; + goto error; + } + default: + break; + } + + if (!need_domain) { + goto skip_domain; + } + + /* + * Check domain type for specific "pre-action". + */ + switch (cmd_ctx->lsm.domain.type) { + case LTTNG_DOMAIN_KERNEL: + if (!is_root) { + ret = LTTNG_ERR_NEED_ROOT_SESSIOND; + goto error; + } + + /* Kernel tracer check */ + if (!kernel_tracer_is_initialized()) { + /* Basically, load kernel tracer modules */ + ret = init_kernel_tracer(); + if (ret != 0) { + goto error; + } + } + + /* Consumer is in an ERROR state. Report back to client */ + if (need_consumerd && uatomic_read(&the_kernel_consumerd_state) == + CONSUMER_ERROR) { + ret = LTTNG_ERR_NO_KERNCONSUMERD; + goto error; + } + + /* Need a session for kernel command */ + if (need_tracing_session) { + if (cmd_ctx->session->kernel_session == NULL) { + ret = create_kernel_session(cmd_ctx->session); + if (ret != LTTNG_OK) { + ret = LTTNG_ERR_KERN_SESS_FAIL; + goto error; + } + } + + /* Start the kernel consumer daemon */ + pthread_mutex_lock(&the_kconsumer_data.pid_mutex); + if (the_kconsumer_data.pid == 0 && + cmd_ctx->lsm.cmd_type != LTTNG_REGISTER_CONSUMER) { + pthread_mutex_unlock(&the_kconsumer_data.pid_mutex); + ret = start_consumerd(&the_kconsumer_data); + if (ret < 0) { + ret = LTTNG_ERR_KERN_CONSUMER_FAIL; + goto error; + } + uatomic_set(&the_kernel_consumerd_state, CONSUMER_STARTED); + } else { + pthread_mutex_unlock(&the_kconsumer_data.pid_mutex); + } + + /* + * The consumer was just spawned so we need to add the socket to + * the consumer output of the session if exist. + */ + ret = consumer_create_socket(&the_kconsumer_data, + cmd_ctx->session->kernel_session->consumer); + if (ret < 0) { + goto error; + } + } + + break; + 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 (need_consumerd && + uatomic_read(&the_ust_consumerd_state) == + CONSUMER_ERROR) { + ret = LTTNG_ERR_NO_USTCONSUMERD; + goto error; + } + + if (need_tracing_session) { + /* Create UST session if none exist. */ + if (cmd_ctx->session->ust_session == NULL) { + lttng_domain domain = cmd_ctx->lsm.domain; + ret = create_ust_session(cmd_ctx->session, &domain); + if (ret != LTTNG_OK) { + goto error; + } + } + + /* Start the UST consumer daemons */ + /* 64-bit */ + pthread_mutex_lock(&the_ustconsumer64_data.pid_mutex); + if (the_config.consumerd64_bin_path.value && + the_ustconsumer64_data.pid == 0 && + cmd_ctx->lsm.cmd_type != LTTNG_REGISTER_CONSUMER) { + pthread_mutex_unlock(&the_ustconsumer64_data.pid_mutex); + ret = start_consumerd(&the_ustconsumer64_data); + if (ret < 0) { + ret = LTTNG_ERR_UST_CONSUMER64_FAIL; + uatomic_set(&the_ust_consumerd64_fd, -EINVAL); + goto error; + } + + uatomic_set(&the_ust_consumerd64_fd, the_ustconsumer64_data.cmd_sock); + uatomic_set(&the_ust_consumerd_state, CONSUMER_STARTED); + } else { + pthread_mutex_unlock(&the_ustconsumer64_data.pid_mutex); + } + + /* + * Setup socket for consumer 64 bit. No need for atomic access + * since it was set above and can ONLY be set in this thread. + */ + ret = consumer_create_socket(&the_ustconsumer64_data, + cmd_ctx->session->ust_session->consumer); + if (ret < 0) { + goto error; + } + + /* 32-bit */ + pthread_mutex_lock(&the_ustconsumer32_data.pid_mutex); + if (the_config.consumerd32_bin_path.value && + the_ustconsumer32_data.pid == 0 && + cmd_ctx->lsm.cmd_type != LTTNG_REGISTER_CONSUMER) { + pthread_mutex_unlock(&the_ustconsumer32_data.pid_mutex); + ret = start_consumerd(&the_ustconsumer32_data); + if (ret < 0) { + ret = LTTNG_ERR_UST_CONSUMER32_FAIL; + uatomic_set(&the_ust_consumerd32_fd, -EINVAL); + goto error; + } + + uatomic_set(&the_ust_consumerd32_fd, the_ustconsumer32_data.cmd_sock); + uatomic_set(&the_ust_consumerd_state, CONSUMER_STARTED); + } else { + pthread_mutex_unlock(&the_ustconsumer32_data.pid_mutex); + } + + /* + * Setup socket for consumer 32 bit. No need for atomic access + * since it was set above and can ONLY be set in this thread. + */ + ret = consumer_create_socket(&the_ustconsumer32_data, + cmd_ctx->session->ust_session->consumer); + if (ret < 0) { + goto error; + } + } + break; + } + default: + break; + } +skip_domain: + + /* Validate consumer daemon state when start/stop trace command */ + if (cmd_ctx->lsm.cmd_type == LTTNG_START_TRACE || + cmd_ctx->lsm.cmd_type == LTTNG_STOP_TRACE) { + switch (cmd_ctx->lsm.domain.type) { + case LTTNG_DOMAIN_NONE: + break; + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + case LTTNG_DOMAIN_UST: + if (uatomic_read(&the_ust_consumerd_state) != CONSUMER_STARTED) { + ret = LTTNG_ERR_NO_USTCONSUMERD; + goto error; + } + break; + case LTTNG_DOMAIN_KERNEL: + if (uatomic_read(&the_kernel_consumerd_state) != CONSUMER_STARTED) { + ret = LTTNG_ERR_NO_KERNCONSUMERD; + goto error; + } + break; + default: + ret = LTTNG_ERR_UNKNOWN_DOMAIN; + goto error; + } + } + + /* + * Check that the UID matches that of the tracing session. + * The root user can interact with all sessions. + */ + if (need_tracing_session) { + if (!session_access_ok(cmd_ctx->session, + LTTNG_SOCK_GET_UID_CRED(&cmd_ctx->creds)) || + cmd_ctx->session->destroyed) { + ret = LTTNG_ERR_EPERM; + goto error; + } + } + + /* + * Send relayd information to consumer as soon as we have a domain and a + * session defined. + */ + if (cmd_ctx->session && need_domain) { + /* + * Setup relayd if not done yet. If the relayd information was already + * sent to the consumer, this call will gracefully return. + */ + ret = cmd_setup_relayd(cmd_ctx->session); + if (ret != LTTNG_OK) { + goto error; + } + } + + /* Process by command type */ + switch (cmd_ctx->lsm.cmd_type) { + case LTTNG_ADD_CONTEXT: + { + lttng_event_context ctx; + + /* + * An LTTNG_ADD_CONTEXT command might have a supplementary + * payload if the context being added is an application context. + */ + if (cmd_ctx->lsm.u.context.ctx.ctx == + LTTNG_EVENT_CONTEXT_APP_CONTEXT) { + char *provider_name = NULL, *context_name = NULL; + size_t provider_name_len = + cmd_ctx->lsm.u.context.provider_name_len; + size_t context_name_len = + cmd_ctx->lsm.u.context.context_name_len; + + if (provider_name_len == 0 || context_name_len == 0) { + /* + * Application provider and context names MUST + * be provided. + */ + ret = -LTTNG_ERR_INVALID; + goto error; + } + + provider_name = (char *) zmalloc(provider_name_len + 1); + if (!provider_name) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + cmd_ctx->lsm.u.context.ctx.u.app_ctx.provider_name = + provider_name; + + context_name = (char *) zmalloc(context_name_len + 1); + if (!context_name) { + ret = -LTTNG_ERR_NOMEM; + goto error_add_context; + } + cmd_ctx->lsm.u.context.ctx.u.app_ctx.ctx_name = + context_name; + + ret = lttcomm_recv_unix_sock(*sock, provider_name, + provider_name_len); + if (ret < 0) { + goto error_add_context; + } + + ret = lttcomm_recv_unix_sock(*sock, context_name, + context_name_len); + if (ret < 0) { + goto error_add_context; + } + } + + /* + * cmd_add_context assumes ownership of the provider and context + * names. + */ + ctx = cmd_ctx->lsm.u.context.ctx; + ret = cmd_add_context(cmd_ctx->session, + cmd_ctx->lsm.domain.type, + cmd_ctx->lsm.u.context.channel_name, + &ctx, + the_kernel_poll_pipe[1]); + + cmd_ctx->lsm.u.context.ctx.u.app_ctx.provider_name = NULL; + cmd_ctx->lsm.u.context.ctx.u.app_ctx.ctx_name = NULL; +error_add_context: + free(cmd_ctx->lsm.u.context.ctx.u.app_ctx.provider_name); + free(cmd_ctx->lsm.u.context.ctx.u.app_ctx.ctx_name); + if (ret < 0) { + goto error; + } + break; + } + case LTTNG_DISABLE_CHANNEL: + { + ret = cmd_disable_channel(cmd_ctx->session, cmd_ctx->lsm.domain.type, + cmd_ctx->lsm.u.disable.channel_name); + break; + } + case LTTNG_DISABLE_EVENT: + { + lttng_event event; + + /* + * FIXME: handle filter; for now we just receive the filter's + * bytecode along with the filter expression which are sent by + * liblttng-ctl and discard them. + * + * This fixes an issue where the client may block while sending + * the filter payload and encounter an error because the session + * daemon closes the socket without ever handling this data. + */ + size_t count = cmd_ctx->lsm.u.disable.expression_len + + cmd_ctx->lsm.u.disable.bytecode_len; + + if (count) { + char data[LTTNG_FILTER_MAX_LEN]; + + DBG("Discarding disable event command payload of size %zu", count); + while (count) { + ret = lttcomm_recv_unix_sock(*sock, data, + count > sizeof(data) ? sizeof(data) : count); + if (ret < 0) { + goto error; + } + + count -= (size_t) ret; + } + } + event = cmd_ctx->lsm.u.disable.event; + ret = cmd_disable_event(cmd_ctx->session, cmd_ctx->lsm.domain.type, + cmd_ctx->lsm.u.disable.channel_name, + &event); + break; + } + case LTTNG_ENABLE_CHANNEL: + { + cmd_ctx->lsm.u.channel.chan.attr.extended.ptr = + (struct lttng_channel_extended *) &cmd_ctx->lsm.u.channel.extended; + lttng_domain domain = cmd_ctx->lsm.domain; + lttng_channel chan = cmd_ctx->lsm.u.channel.chan; + ret = cmd_enable_channel(cmd_ctx->session, + &domain, + &chan, + the_kernel_poll_pipe[1]); + break; + } + case LTTNG_PROCESS_ATTR_TRACKER_ADD_INCLUDE_VALUE: + case LTTNG_PROCESS_ATTR_TRACKER_REMOVE_INCLUDE_VALUE: + { + struct lttng_dynamic_buffer payload; + struct lttng_buffer_view payload_view; + const bool add_value = + cmd_ctx->lsm.cmd_type == + LTTNG_PROCESS_ATTR_TRACKER_ADD_INCLUDE_VALUE; + const size_t name_len = + cmd_ctx->lsm.u.process_attr_tracker_add_remove_include_value + .name_len; + const enum lttng_domain_type domain_type = + (enum lttng_domain_type) + cmd_ctx->lsm.domain.type; + const enum lttng_process_attr process_attr = + (enum lttng_process_attr) cmd_ctx->lsm.u + .process_attr_tracker_add_remove_include_value + .process_attr; + const enum lttng_process_attr_value_type value_type = + (enum lttng_process_attr_value_type) cmd_ctx + ->lsm.u + .process_attr_tracker_add_remove_include_value + .value_type; + struct process_attr_value *value; + enum lttng_error_code ret_code; + long login_name_max; + + login_name_max = sysconf(_SC_LOGIN_NAME_MAX); + if (login_name_max < 0) { + PERROR("Failed to get _SC_LOGIN_NAME_MAX system configuration"); + ret = LTTNG_ERR_INVALID; + goto error; + } + + /* Receive remaining variable length payload if applicable. */ + if (name_len > login_name_max) { + /* + * POSIX mandates user and group names that are at least + * 8 characters long. Note that although shadow-utils + * (useradd, groupaadd, etc.) use 32 chars as their + * limit (from bits/utmp.h, UT_NAMESIZE), + * LOGIN_NAME_MAX is defined to 256. + */ + ERR("Rejecting process attribute tracker value %s as the provided exceeds the maximal allowed length: argument length = %zu, maximal length = %ld", + add_value ? "addition" : "removal", + name_len, login_name_max); + ret = LTTNG_ERR_INVALID; + goto error; + } + + lttng_dynamic_buffer_init(&payload); + if (name_len != 0) { + /* + * Receive variable payload for user/group name + * arguments. + */ + ret = lttng_dynamic_buffer_set_size(&payload, name_len); + if (ret) { + ERR("Failed to allocate buffer to receive payload of %s process attribute tracker value argument", + add_value ? "add" : "remove"); + ret = LTTNG_ERR_NOMEM; + goto error_add_remove_tracker_value; + } + + ret = lttcomm_recv_unix_sock( + *sock, payload.data, name_len); + if (ret <= 0) { + ERR("Failed to receive payload of %s process attribute tracker value argument", + add_value ? "add" : "remove"); + *sock_error = 1; + ret = LTTNG_ERR_INVALID_PROTOCOL; + goto error_add_remove_tracker_value; + } + } + + payload_view = lttng_buffer_view_from_dynamic_buffer( + &payload, 0, name_len); + if (name_len > 0 && !lttng_buffer_view_is_valid(&payload_view)) { + ret = LTTNG_ERR_INVALID_PROTOCOL; + goto error_add_remove_tracker_value; + } + + /* + * Validate the value type and domains are legal for the process + * attribute tracker that is specified and convert the value to + * add/remove to the internal sessiond representation. + */ + ret_code = process_attr_value_from_comm(domain_type, + process_attr, value_type, + &cmd_ctx->lsm.u.process_attr_tracker_add_remove_include_value + .integral_value, + &payload_view, &value); + if (ret_code != LTTNG_OK) { + ret = ret_code; + goto error_add_remove_tracker_value; + } + + if (add_value) { + ret = cmd_process_attr_tracker_inclusion_set_add_value( + cmd_ctx->session, domain_type, + process_attr, value); + } else { + ret = cmd_process_attr_tracker_inclusion_set_remove_value( + cmd_ctx->session, domain_type, + process_attr, value); + } + process_attr_value_destroy(value); + error_add_remove_tracker_value: + lttng_dynamic_buffer_reset(&payload); + break; + } + case LTTNG_PROCESS_ATTR_TRACKER_GET_POLICY: + { + enum lttng_tracking_policy tracking_policy; + const enum lttng_domain_type domain_type = + (enum lttng_domain_type) + cmd_ctx->lsm.domain.type; + const enum lttng_process_attr process_attr = + (enum lttng_process_attr) cmd_ctx->lsm.u + .process_attr_tracker_get_tracking_policy + .process_attr; + + ret = cmd_process_attr_tracker_get_tracking_policy( + cmd_ctx->session, domain_type, process_attr, + &tracking_policy); + if (ret != LTTNG_OK) { + goto error; + } + + uint32_t tracking_policy_u32 = tracking_policy; + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, + &tracking_policy_u32, sizeof(uint32_t)); + if (ret < 0) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + ret = LTTNG_OK; + break; + } + case LTTNG_PROCESS_ATTR_TRACKER_SET_POLICY: + { + const enum lttng_tracking_policy tracking_policy = + (enum lttng_tracking_policy) cmd_ctx->lsm.u + .process_attr_tracker_set_tracking_policy + .tracking_policy; + const enum lttng_domain_type domain_type = + (enum lttng_domain_type) + cmd_ctx->lsm.domain.type; + const enum lttng_process_attr process_attr = + (enum lttng_process_attr) cmd_ctx->lsm.u + .process_attr_tracker_set_tracking_policy + .process_attr; + + ret = cmd_process_attr_tracker_set_tracking_policy( + cmd_ctx->session, domain_type, process_attr, + tracking_policy); + if (ret != LTTNG_OK) { + goto error; + } + break; + } + case LTTNG_PROCESS_ATTR_TRACKER_GET_INCLUSION_SET: + { + struct lttng_process_attr_values *values; + struct lttng_dynamic_buffer reply; + const enum lttng_domain_type domain_type = + (enum lttng_domain_type) + cmd_ctx->lsm.domain.type; + const enum lttng_process_attr process_attr = + (enum lttng_process_attr) cmd_ctx->lsm.u + .process_attr_tracker_get_inclusion_set + .process_attr; + + ret = cmd_process_attr_tracker_get_inclusion_set( + cmd_ctx->session, domain_type, process_attr, + &values); + if (ret != LTTNG_OK) { + goto error; + } + + lttng_dynamic_buffer_init(&reply); + ret = lttng_process_attr_values_serialize(values, &reply); + if (ret < 0) { + goto error_tracker_get_inclusion_set; + } + + ret = setup_lttng_msg_no_cmd_header( + cmd_ctx, reply.data, reply.size); + if (ret < 0) { + ret = LTTNG_ERR_NOMEM; + goto error_tracker_get_inclusion_set; + } + ret = LTTNG_OK; + + error_tracker_get_inclusion_set: + lttng_process_attr_values_destroy(values); + lttng_dynamic_buffer_reset(&reply); + break; + } + case LTTNG_ENABLE_EVENT: + { + struct lttng_event *ev = NULL; + struct lttng_event_exclusion *exclusion = NULL; + struct lttng_bytecode *bytecode = NULL; + char *filter_expression = NULL; + lttng_event event; + lttng_domain domain; + + /* Handle exclusion events and receive it from the client. */ + if (cmd_ctx->lsm.u.enable.exclusion_count > 0) { + size_t count = cmd_ctx->lsm.u.enable.exclusion_count; + + exclusion = (lttng_event_exclusion *) zmalloc(sizeof(struct lttng_event_exclusion) + + (count * LTTNG_SYMBOL_NAME_LEN)); + if (!exclusion) { + ret = LTTNG_ERR_EXCLUSION_NOMEM; + goto error; + } + + DBG("Receiving var len exclusion event list from client ..."); + exclusion->count = count; + ret = lttcomm_recv_unix_sock(*sock, exclusion->names, + count * LTTNG_SYMBOL_NAME_LEN); + if (ret <= 0) { + DBG("Nothing recv() from client var len data... continuing"); + *sock_error = 1; + free(exclusion); + ret = LTTNG_ERR_EXCLUSION_INVAL; + goto error; + } + } + + /* Get filter expression from client. */ + if (cmd_ctx->lsm.u.enable.expression_len > 0) { + size_t expression_len = + cmd_ctx->lsm.u.enable.expression_len; + + if (expression_len > LTTNG_FILTER_MAX_LEN) { + ret = LTTNG_ERR_FILTER_INVAL; + free(exclusion); + goto error; + } + + filter_expression = (char *) zmalloc(expression_len); + if (!filter_expression) { + free(exclusion); + ret = LTTNG_ERR_FILTER_NOMEM; + goto error; + } + + /* Receive var. len. data */ + DBG("Receiving var len filter's expression from client ..."); + ret = lttcomm_recv_unix_sock(*sock, filter_expression, + expression_len); + if (ret <= 0) { + DBG("Nothing recv() from client var len data... continuing"); + *sock_error = 1; + free(filter_expression); + free(exclusion); + ret = LTTNG_ERR_FILTER_INVAL; + goto error; + } + } + + /* Handle filter and get bytecode from client. */ + if (cmd_ctx->lsm.u.enable.bytecode_len > 0) { + size_t bytecode_len = cmd_ctx->lsm.u.enable.bytecode_len; + + if (bytecode_len > LTTNG_FILTER_MAX_LEN) { + ret = LTTNG_ERR_FILTER_INVAL; + free(filter_expression); + free(exclusion); + goto error; + } + + bytecode = (lttng_bytecode *) zmalloc(bytecode_len); + if (!bytecode) { + free(filter_expression); + free(exclusion); + ret = LTTNG_ERR_FILTER_NOMEM; + goto error; + } + + /* Receive var. len. data */ + DBG("Receiving var len filter's bytecode from client ..."); + ret = lttcomm_recv_unix_sock(*sock, bytecode, bytecode_len); + if (ret <= 0) { + DBG("Nothing recv() from client var len data... continuing"); + *sock_error = 1; + free(filter_expression); + free(bytecode); + free(exclusion); + ret = LTTNG_ERR_FILTER_INVAL; + goto error; + } + + if ((bytecode->len + sizeof(*bytecode)) != bytecode_len) { + free(filter_expression); + free(bytecode); + free(exclusion); + ret = LTTNG_ERR_FILTER_INVAL; + goto error; + } + } + + event = cmd_ctx->lsm.u.enable.event; + ev = lttng_event_copy(&event); + if (!ev) { + DBG("Failed to copy event: %s", + cmd_ctx->lsm.u.enable.event.name); + free(filter_expression); + free(bytecode); + free(exclusion); + ret = LTTNG_ERR_NOMEM; + goto error; + } + + + if (cmd_ctx->lsm.u.enable.userspace_probe_location_len > 0) { + /* Expect a userspace probe description. */ + ret = receive_userspace_probe(cmd_ctx, *sock, sock_error, ev); + if (ret) { + free(filter_expression); + free(bytecode); + free(exclusion); + lttng_event_destroy(ev); + goto error; + } + } + + domain = cmd_ctx->lsm.domain; + ret = cmd_enable_event(cmd_ctx->session, + &domain, + cmd_ctx->lsm.u.enable.channel_name, + ev, + filter_expression, bytecode, exclusion, + the_kernel_poll_pipe[1]); + lttng_event_destroy(ev); + break; + } + case LTTNG_LIST_TRACEPOINTS: + { + struct lttng_event *events; + ssize_t nb_events; + + session_lock_list(); + nb_events = cmd_list_tracepoints(cmd_ctx->lsm.domain.type, &events); + session_unlock_list(); + if (nb_events < 0) { + /* Return value is a negative lttng_error_code. */ + ret = -nb_events; + goto error; + } + + /* + * Setup lttng message with payload size set to the event list size in + * bytes and then copy list into the llm payload. + */ + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, events, + sizeof(struct lttng_event) * nb_events); + free(events); + + if (ret < 0) { + goto setup_error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_LIST_TRACEPOINT_FIELDS: + { + struct lttng_event_field *fields; + ssize_t nb_fields; + + session_lock_list(); + nb_fields = cmd_list_tracepoint_fields(cmd_ctx->lsm.domain.type, + &fields); + session_unlock_list(); + if (nb_fields < 0) { + /* Return value is a negative lttng_error_code. */ + ret = -nb_fields; + goto error; + } + + /* + * Setup lttng message with payload size set to the event list size in + * bytes and then copy list into the llm payload. + */ + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, fields, + sizeof(struct lttng_event_field) * nb_fields); + free(fields); + + if (ret < 0) { + goto setup_error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_LIST_SYSCALLS: + { + struct lttng_event *events; + ssize_t nb_events; + + nb_events = cmd_list_syscalls(&events); + if (nb_events < 0) { + /* Return value is a negative lttng_error_code. */ + ret = -nb_events; + goto error; + } + + /* + * Setup lttng message with payload size set to the event list size in + * bytes and then copy list into the llm payload. + */ + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, events, + sizeof(struct lttng_event) * nb_events); + free(events); + + if (ret < 0) { + goto setup_error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_SET_CONSUMER_URI: + { + size_t nb_uri, len; + struct lttng_uri *uris; + + nb_uri = cmd_ctx->lsm.u.uri.size; + len = nb_uri * sizeof(struct lttng_uri); + + if (nb_uri == 0) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + uris = (lttng_uri *) zmalloc(len); + if (uris == NULL) { + ret = LTTNG_ERR_FATAL; + goto error; + } + + /* Receive variable len data */ + DBG("Receiving %zu URI(s) from client ...", nb_uri); + ret = lttcomm_recv_unix_sock(*sock, uris, len); + if (ret <= 0) { + DBG("No URIs received from client... continuing"); + *sock_error = 1; + ret = LTTNG_ERR_SESSION_FAIL; + free(uris); + goto error; + } + + ret = cmd_set_consumer_uri(cmd_ctx->session, nb_uri, uris); + free(uris); + if (ret != LTTNG_OK) { + goto error; + } + + + break; + } + case LTTNG_START_TRACE: + { + /* + * On the first start, if we have a kernel session and we have + * enabled time or size-based rotations, we have to make sure + * the kernel tracer supports it. + */ + if (!cmd_ctx->session->has_been_started && \ + cmd_ctx->session->kernel_session && \ + (cmd_ctx->session->rotate_timer_period || \ + cmd_ctx->session->rotate_size) && \ + !check_rotate_compatible()) { + DBG("Kernel tracer version is not compatible with the rotation feature"); + ret = LTTNG_ERR_ROTATION_WRONG_VERSION; + goto error; + } + ret = cmd_start_trace(cmd_ctx->session); + break; + } + case LTTNG_STOP_TRACE: + { + ret = cmd_stop_trace(cmd_ctx->session); + break; + } + case LTTNG_DESTROY_SESSION: + { + ret = cmd_destroy_session(cmd_ctx->session, + the_notification_thread_handle, sock); + break; + } + case LTTNG_LIST_DOMAINS: + { + ssize_t nb_dom; + struct lttng_domain *domains = NULL; + + nb_dom = cmd_list_domains(cmd_ctx->session, &domains); + if (nb_dom < 0) { + /* Return value is a negative lttng_error_code. */ + ret = -nb_dom; + goto error; + } + + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, domains, + nb_dom * sizeof(struct lttng_domain)); + free(domains); + + if (ret < 0) { + goto setup_error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_LIST_CHANNELS: + { + ssize_t payload_size; + struct lttng_channel *channels = NULL; + + payload_size = cmd_list_channels(cmd_ctx->lsm.domain.type, + cmd_ctx->session, &channels); + if (payload_size < 0) { + /* Return value is a negative lttng_error_code. */ + ret = -payload_size; + goto error; + } + + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, channels, + payload_size); + free(channels); + + if (ret < 0) { + goto setup_error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_LIST_EVENTS: + { + ssize_t list_ret; + struct lttcomm_event_command_header cmd_header = {}; + size_t original_payload_size; + size_t payload_size; + + ret = setup_empty_lttng_msg(cmd_ctx); + if (ret) { + ret = LTTNG_ERR_NOMEM; + goto setup_error; + } + + original_payload_size = cmd_ctx->reply_payload.buffer.size; + + /* Extended infos are included at the end of the payload. */ + list_ret = cmd_list_events(cmd_ctx->lsm.domain.type, + cmd_ctx->session, + cmd_ctx->lsm.u.list.channel_name, + &cmd_ctx->reply_payload); + if (list_ret < 0) { + /* Return value is a negative lttng_error_code. */ + ret = -list_ret; + goto error; + } + + payload_size = cmd_ctx->reply_payload.buffer.size - + sizeof(cmd_header) - original_payload_size; + update_lttng_msg(cmd_ctx, sizeof(cmd_header), payload_size); + + ret = LTTNG_OK; + break; + } + case LTTNG_LIST_SESSIONS: + { + unsigned int nr_sessions; + lttng_session *sessions_payload; + size_t payload_len; + + session_lock_list(); + nr_sessions = lttng_sessions_count( + LTTNG_SOCK_GET_UID_CRED(&cmd_ctx->creds), + LTTNG_SOCK_GET_GID_CRED(&cmd_ctx->creds)); + + payload_len = (sizeof(struct lttng_session) * nr_sessions) + + (sizeof(struct lttng_session_extended) * nr_sessions); + sessions_payload = (lttng_session *) zmalloc(payload_len); + + if (!sessions_payload) { + session_unlock_list(); + ret = -ENOMEM; + goto setup_error; + } + + cmd_list_lttng_sessions(sessions_payload, nr_sessions, + LTTNG_SOCK_GET_UID_CRED(&cmd_ctx->creds), + LTTNG_SOCK_GET_GID_CRED(&cmd_ctx->creds)); + session_unlock_list(); + + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, sessions_payload, + payload_len); + free(sessions_payload); + + if (ret < 0) { + goto setup_error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_REGISTER_CONSUMER: + { + struct consumer_data *cdata; + + switch (cmd_ctx->lsm.domain.type) { + case LTTNG_DOMAIN_KERNEL: + cdata = &the_kconsumer_data; + break; + default: + ret = LTTNG_ERR_UND; + goto error; + } + + ret = cmd_register_consumer(cmd_ctx->session, cmd_ctx->lsm.domain.type, + cmd_ctx->lsm.u.reg.path, cdata); + break; + } + case LTTNG_DATA_PENDING: + { + int pending_ret; + uint8_t pending_ret_byte; + + pending_ret = cmd_data_pending(cmd_ctx->session); + + /* + * FIXME + * + * This function may returns 0 or 1 to indicate whether or not + * there is data pending. In case of error, it should return an + * LTTNG_ERR code. However, some code paths may still return + * a nondescript error code, which we handle by returning an + * "unknown" error. + */ + if (pending_ret == 0 || pending_ret == 1) { + /* + * ret will be set to LTTNG_OK at the end of + * this function. + */ + } else if (pending_ret < 0) { + ret = LTTNG_ERR_UNK; + goto setup_error; + } else { + ret = pending_ret; + goto setup_error; + } + + pending_ret_byte = (uint8_t) pending_ret; + + /* 1 byte to return whether or not data is pending */ + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, + &pending_ret_byte, 1); + + if (ret < 0) { + goto setup_error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_SNAPSHOT_ADD_OUTPUT: + { + uint32_t snapshot_id; + struct lttcomm_lttng_output_id reply; + lttng_snapshot_output output = cmd_ctx->lsm.u.snapshot_output.output; + + ret = cmd_snapshot_add_output(cmd_ctx->session, + &output, + &snapshot_id); + if (ret != LTTNG_OK) { + goto error; + } + reply.id = snapshot_id; + + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, &reply, + sizeof(reply)); + if (ret < 0) { + goto setup_error; + } + + /* Copy output list into message payload */ + ret = LTTNG_OK; + break; + } + case LTTNG_SNAPSHOT_DEL_OUTPUT: + { + lttng_snapshot_output output = cmd_ctx->lsm.u.snapshot_output.output; + ret = cmd_snapshot_del_output(cmd_ctx->session, &output); + break; + } + case LTTNG_SNAPSHOT_LIST_OUTPUT: + { + ssize_t nb_output; + struct lttng_snapshot_output *outputs = NULL; + + nb_output = cmd_snapshot_list_outputs(cmd_ctx->session, &outputs); + if (nb_output < 0) { + ret = -nb_output; + goto error; + } + + LTTNG_ASSERT((nb_output > 0 && outputs) || nb_output == 0); + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, outputs, + nb_output * sizeof(struct lttng_snapshot_output)); + free(outputs); + + if (ret < 0) { + goto setup_error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_SNAPSHOT_RECORD: + { + lttng_snapshot_output output = cmd_ctx->lsm.u.snapshot_record.output; + ret = cmd_snapshot_record(cmd_ctx->session, + &output, + cmd_ctx->lsm.u.snapshot_record.wait); + break; + } + case LTTNG_CREATE_SESSION_EXT: + { + struct lttng_dynamic_buffer payload; + struct lttng_session_descriptor *return_descriptor = NULL; + + lttng_dynamic_buffer_init(&payload); + ret = cmd_create_session(cmd_ctx, *sock, &return_descriptor); + if (ret != LTTNG_OK) { + goto error; + } + + ret = lttng_session_descriptor_serialize(return_descriptor, + &payload); + if (ret) { + ERR("Failed to serialize session descriptor in reply to \"create session\" command"); + lttng_session_descriptor_destroy(return_descriptor); + ret = LTTNG_ERR_NOMEM; + goto error; + } + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, payload.data, + payload.size); + if (ret) { + lttng_session_descriptor_destroy(return_descriptor); + ret = LTTNG_ERR_NOMEM; + goto error; + } + lttng_dynamic_buffer_reset(&payload); + lttng_session_descriptor_destroy(return_descriptor); + ret = LTTNG_OK; + break; + } + case LTTNG_SAVE_SESSION: + { + ret = cmd_save_sessions(&cmd_ctx->lsm.u.save_session.attr, + &cmd_ctx->creds); + break; + } + case LTTNG_SET_SESSION_SHM_PATH: + { + ret = cmd_set_session_shm_path(cmd_ctx->session, + cmd_ctx->lsm.u.set_shm_path.shm_path); + break; + } + case LTTNG_REGENERATE_METADATA: + { + ret = cmd_regenerate_metadata(cmd_ctx->session); + break; + } + case LTTNG_REGENERATE_STATEDUMP: + { + ret = cmd_regenerate_statedump(cmd_ctx->session); + break; + } + case LTTNG_REGISTER_TRIGGER: + { + struct lttng_trigger *payload_trigger; + struct lttng_trigger *return_trigger; + size_t original_reply_payload_size; + size_t reply_payload_size; + const struct lttng_credentials cmd_creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.gid), + }; + + ret = setup_empty_lttng_msg(cmd_ctx); + if (ret) { + ret = LTTNG_ERR_NOMEM; + goto setup_error; + } + + ret = receive_lttng_trigger( + cmd_ctx, *sock, sock_error, &payload_trigger); + if (ret != LTTNG_OK) { + goto error; + } + + original_reply_payload_size = cmd_ctx->reply_payload.buffer.size; + + ret = cmd_register_trigger(&cmd_creds, payload_trigger, + cmd_ctx->lsm.u.trigger.is_trigger_anonymous, + the_notification_thread_handle, + &return_trigger); + if (ret != LTTNG_OK) { + lttng_trigger_put(payload_trigger); + goto error; + } + + ret = lttng_trigger_serialize(return_trigger, &cmd_ctx->reply_payload); + lttng_trigger_put(payload_trigger); + lttng_trigger_put(return_trigger); + if (ret) { + ERR("Failed to serialize trigger in reply to \"register trigger\" command"); + ret = LTTNG_ERR_NOMEM; + goto error; + } + + reply_payload_size = cmd_ctx->reply_payload.buffer.size - + original_reply_payload_size; + + update_lttng_msg(cmd_ctx, 0, reply_payload_size); + + ret = LTTNG_OK; + break; + } + case LTTNG_UNREGISTER_TRIGGER: + { + struct lttng_trigger *payload_trigger; + const struct lttng_credentials cmd_creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.gid), + }; + + ret = receive_lttng_trigger( + cmd_ctx, *sock, sock_error, &payload_trigger); + if (ret != LTTNG_OK) { + goto error; + } + + ret = cmd_unregister_trigger(&cmd_creds, payload_trigger, + the_notification_thread_handle); + lttng_trigger_put(payload_trigger); + break; + } + case LTTNG_ROTATE_SESSION: + { + struct lttng_rotate_session_return rotate_return; + + DBG("Client rotate session \"%s\"", cmd_ctx->session->name); + + memset(&rotate_return, 0, sizeof(rotate_return)); + if (cmd_ctx->session->kernel_session && !check_rotate_compatible()) { + DBG("Kernel tracer version is not compatible with the rotation feature"); + ret = LTTNG_ERR_ROTATION_WRONG_VERSION; + goto error; + } + + ret = cmd_rotate_session(cmd_ctx->session, &rotate_return, + false, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); + if (ret < 0) { + ret = -ret; + goto error; + } + + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, &rotate_return, + sizeof(rotate_return)); + if (ret < 0) { + ret = -ret; + goto error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_ROTATION_GET_INFO: + { + struct lttng_rotation_get_info_return get_info_return; + + memset(&get_info_return, 0, sizeof(get_info_return)); + ret = cmd_rotate_get_info(cmd_ctx->session, &get_info_return, + cmd_ctx->lsm.u.get_rotation_info.rotation_id); + if (ret < 0) { + ret = -ret; + goto error; + } + + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, &get_info_return, + sizeof(get_info_return)); + if (ret < 0) { + ret = -ret; + goto error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_ROTATION_SET_SCHEDULE: + { + bool set_schedule; + enum lttng_rotation_schedule_type schedule_type; + uint64_t value; + + if (cmd_ctx->session->kernel_session && !check_rotate_compatible()) { + DBG("Kernel tracer version does not support session rotations"); + ret = LTTNG_ERR_ROTATION_WRONG_VERSION; + goto error; + } + + set_schedule = cmd_ctx->lsm.u.rotation_set_schedule.set == 1; + schedule_type = (enum lttng_rotation_schedule_type) cmd_ctx->lsm.u.rotation_set_schedule.type; + value = cmd_ctx->lsm.u.rotation_set_schedule.value; + + ret = cmd_rotation_set_schedule(cmd_ctx->session, set_schedule, + schedule_type, value, + the_notification_thread_handle); + if (ret != LTTNG_OK) { + goto error; + } + + break; + } + case LTTNG_SESSION_LIST_ROTATION_SCHEDULES: + { + lttng_session_list_schedules_return schedules; + + schedules.periodic.set = !!cmd_ctx->session->rotate_timer_period; + schedules.periodic.value = cmd_ctx->session->rotate_timer_period; + schedules.size.set = !!cmd_ctx->session->rotate_size; + schedules.size.value = cmd_ctx->session->rotate_size; + + ret = setup_lttng_msg_no_cmd_header(cmd_ctx, &schedules, + sizeof(schedules)); + if (ret < 0) { + ret = -ret; + goto error; + } + + ret = LTTNG_OK; + break; + } + case LTTNG_CLEAR_SESSION: + { + ret = cmd_clear_session(cmd_ctx->session, sock); + break; + } + case LTTNG_LIST_TRIGGERS: + { + struct lttng_triggers *return_triggers = NULL; + size_t original_payload_size; + size_t payload_size; + + ret = setup_empty_lttng_msg(cmd_ctx); + if (ret) { + ret = LTTNG_ERR_NOMEM; + goto setup_error; + } + + original_payload_size = cmd_ctx->reply_payload.buffer.size; + + ret = cmd_list_triggers(cmd_ctx, the_notification_thread_handle, + &return_triggers); + if (ret != LTTNG_OK) { + goto error; + } + + LTTNG_ASSERT(return_triggers); + ret = lttng_triggers_serialize( + return_triggers, &cmd_ctx->reply_payload); + lttng_triggers_destroy(return_triggers); + if (ret) { + ERR("Failed to serialize triggers in reply to `list triggers` command"); + ret = LTTNG_ERR_NOMEM; + goto error; + } + + payload_size = cmd_ctx->reply_payload.buffer.size - + original_payload_size; + + update_lttng_msg(cmd_ctx, 0, payload_size); + + ret = LTTNG_OK; + break; + } + case LTTNG_EXECUTE_ERROR_QUERY: + { + struct lttng_error_query *query; + const struct lttng_credentials cmd_creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(cmd_ctx->creds.gid), + }; + struct lttng_error_query_results *results = NULL; + size_t original_payload_size; + size_t payload_size; + + ret = setup_empty_lttng_msg(cmd_ctx); + if (ret) { + ret = LTTNG_ERR_NOMEM; + goto setup_error; + } + + original_payload_size = cmd_ctx->reply_payload.buffer.size; + + ret = receive_lttng_error_query( + cmd_ctx, *sock, sock_error, &query); + if (ret != LTTNG_OK) { + goto error; + } + + ret = cmd_execute_error_query(&cmd_creds, query, &results, + the_notification_thread_handle); + lttng_error_query_destroy(query); + if (ret != LTTNG_OK) { + goto error; + } + + LTTNG_ASSERT(results); + ret = lttng_error_query_results_serialize( + results, &cmd_ctx->reply_payload); + lttng_error_query_results_destroy(results); + if (ret) { + ERR("Failed to serialize error query result set in reply to `execute error query` command"); + ret = LTTNG_ERR_NOMEM; + goto error; + } + + payload_size = cmd_ctx->reply_payload.buffer.size - + original_payload_size; + + update_lttng_msg(cmd_ctx, 0, payload_size); + + ret = LTTNG_OK; + + break; + } + default: + ret = LTTNG_ERR_UND; + break; + } + +error: + if (cmd_ctx->reply_payload.buffer.size == 0) { + DBG("Missing llm header, creating one."); + if (setup_lttng_msg_no_cmd_header(cmd_ctx, NULL, 0) < 0) { + goto setup_error; + } + } + /* Set return code */ + ((struct lttcomm_lttng_msg *) (cmd_ctx->reply_payload.buffer.data))->ret_code = ret; +setup_error: + if (cmd_ctx->session) { + session_unlock(cmd_ctx->session); + session_put(cmd_ctx->session); + cmd_ctx->session = NULL; + } + if (need_tracing_session) { + session_unlock_list(); + } +init_setup_error: + LTTNG_ASSERT(!rcu_read_ongoing()); + return ret; +} + +static int create_client_sock(void) +{ + int ret, client_sock; + const mode_t old_umask = umask(0); + + /* Create client tool unix socket */ + client_sock = lttcomm_create_unix_sock( + the_config.client_unix_sock_path.value); + if (client_sock < 0) { + ERR("Create unix sock failed: %s", + the_config.client_unix_sock_path.value); + ret = -1; + goto end; + } + + /* Set the cloexec flag */ + ret = utils_set_fd_cloexec(client_sock); + if (ret < 0) { + ERR("Unable to set CLOEXEC flag to the client Unix socket (fd: %d). " + "Continuing but note that the consumer daemon will have a " + "reference to this socket on exec()", client_sock); + } + + /* File permission MUST be 660 */ + ret = chmod(the_config.client_unix_sock_path.value, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if (ret < 0) { + ERR("Set file permissions failed: %s", + the_config.client_unix_sock_path.value); + PERROR("chmod"); + (void) lttcomm_close_unix_sock(client_sock); + ret = -1; + goto end; + } + DBG("Created client socket (fd = %i)", client_sock); + ret = client_sock; +end: + umask(old_umask); + return ret; +} + +static void cleanup_client_thread(void *data) +{ + struct lttng_pipe *quit_pipe = (lttng_pipe *) data; + + lttng_pipe_destroy(quit_pipe); +} + +static void thread_init_cleanup(void *data) +{ + set_thread_status(false); +} + +/* + * This thread manage all clients request using the unix client socket for + * communication. + */ +static void *thread_manage_clients(void *data) +{ + int sock = -1, ret, i, pollfd, err = -1; + int sock_error; + uint32_t revents, nb_fd; + struct lttng_poll_event events; + const int client_sock = thread_state.client_sock; + struct lttng_pipe *quit_pipe = (lttng_pipe *) data; + const int thread_quit_pipe_fd = lttng_pipe_get_readfd(quit_pipe); + struct command_ctx cmd_ctx = {}; + + DBG("[thread] Manage client started"); + + lttng_payload_init(&cmd_ctx.reply_payload); + + is_root = (getuid() == 0); + + pthread_cleanup_push(thread_init_cleanup, NULL); + + rcu_register_thread(); + + health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_CMD); + + health_code_update(); + + ret = lttcomm_listen_unix_sock(client_sock); + if (ret < 0) { + goto error_listen; + } + + /* + * Pass 2 as size here for the thread quit pipe and client_sock. Nothing + * more will be added to this poll set. + */ + ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); + if (ret < 0) { + goto error_create_poll; + } + + /* Add the application registration socket */ + ret = lttng_poll_add(&events, client_sock, LPOLLIN | LPOLLPRI); + if (ret < 0) { + goto error; + } + + /* Add thread quit pipe */ + ret = lttng_poll_add(&events, thread_quit_pipe_fd, LPOLLIN | LPOLLERR); + if (ret < 0) { + goto error; + } + + /* Set state as running. */ + set_thread_status(true); + pthread_cleanup_pop(0); + + /* This testpoint is after we signal readiness to the parent. */ + if (testpoint(sessiond_thread_manage_clients)) { + goto error; + } + + if (testpoint(sessiond_thread_manage_clients_before_loop)) { + goto error; + } + + health_code_update(); + + while (1) { + const struct cmd_completion_handler *cmd_completion_handler; + + cmd_ctx.creds.uid = UINT32_MAX; + cmd_ctx.creds.gid = UINT32_MAX; + cmd_ctx.creds.pid = 0; + cmd_ctx.session = NULL; + lttng_payload_clear(&cmd_ctx.reply_payload); + cmd_ctx.lttng_msg_size = 0; + + DBG("Accepting client command ..."); + + /* Inifinite blocking call, waiting for transmission */ + restart: + health_poll_entry(); + ret = lttng_poll_wait(&events, -1); + health_poll_exit(); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + goto restart; + } + goto error; + } + + nb_fd = ret; + + for (i = 0; i < nb_fd; i++) { + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + health_code_update(); + + if (pollfd == thread_quit_pipe_fd) { + err = 0; + goto exit; + } else { + /* Event on the registration socket */ + if (revents & LPOLLIN) { + continue; + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ERR("Client socket poll error"); + goto error; + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + } + } + + DBG("Wait for client response"); + + health_code_update(); + + sock = lttcomm_accept_unix_sock(client_sock); + if (sock < 0) { + goto error; + } + + /* + * Set the CLOEXEC flag. Return code is useless because either way, the + * show must go on. + */ + (void) utils_set_fd_cloexec(sock); + + /* Set socket option for credentials retrieval */ + ret = lttcomm_setsockopt_creds_unix_sock(sock); + if (ret < 0) { + goto error; + } + + health_code_update(); + + /* + * Data is received from the lttng client. The struct + * lttcomm_session_msg (lsm) contains the command and data request of + * the client. + */ + DBG("Receiving data from client ..."); + ret = lttcomm_recv_creds_unix_sock(sock, &cmd_ctx.lsm, + sizeof(struct lttcomm_session_msg), &cmd_ctx.creds); + if (ret != sizeof(struct lttcomm_session_msg)) { + DBG("Incomplete recv() from client... continuing"); + ret = close(sock); + if (ret) { + PERROR("close"); + } + sock = -1; + continue; + } + + health_code_update(); + + // TODO: Validate cmd_ctx including sanity check for + // security purpose. + + rcu_thread_online(); + /* + * This function dispatch the work to the kernel or userspace tracer + * libs and fill the lttcomm_lttng_msg data structure of all the needed + * informations for the client. The command context struct contains + * everything this function may needs. + */ + ret = process_client_msg(&cmd_ctx, &sock, &sock_error); + rcu_thread_offline(); + if (ret < 0) { + if (sock >= 0) { + ret = close(sock); + if (ret) { + PERROR("close"); + } + } + sock = -1; + /* + * TODO: Inform client somehow of the fatal error. At + * this point, ret < 0 means that a zmalloc failed + * (ENOMEM). Error detected but still accept + * command, unless a socket error has been + * detected. + */ + continue; + } + + if (ret < LTTNG_OK || ret >= LTTNG_ERR_NR) { + WARN("Command returned an invalid status code, returning unknown error: " + "command type = %s (%d), ret = %d", + lttcomm_sessiond_command_str((lttcomm_sessiond_command) cmd_ctx.lsm.cmd_type), + cmd_ctx.lsm.cmd_type, ret); + ret = LTTNG_ERR_UNK; + } + + cmd_completion_handler = cmd_pop_completion_handler(); + if (cmd_completion_handler) { + enum lttng_error_code completion_code; + + completion_code = cmd_completion_handler->run( + cmd_completion_handler->data); + if (completion_code != LTTNG_OK) { + continue; + } + } + + health_code_update(); + + if (sock >= 0) { + struct lttng_payload_view view = + lttng_payload_view_from_payload( + &cmd_ctx.reply_payload, + 0, -1); + struct lttcomm_lttng_msg *llm = (typeof( + llm)) cmd_ctx.reply_payload.buffer.data; + + LTTNG_ASSERT(cmd_ctx.reply_payload.buffer.size >= sizeof(*llm)); + LTTNG_ASSERT(cmd_ctx.lttng_msg_size == cmd_ctx.reply_payload.buffer.size); + + llm->fd_count = lttng_payload_view_get_fd_handle_count(&view); + + DBG("Sending response (size: %d, retcode: %s (%d))", + cmd_ctx.lttng_msg_size, + lttng_strerror(-llm->ret_code), + llm->ret_code); + ret = send_unix_sock(sock, &view); + if (ret < 0) { + ERR("Failed to send data back to client"); + } + + /* End of transmission */ + ret = close(sock); + if (ret) { + PERROR("close"); + } + } + sock = -1; + + health_code_update(); + } + +exit: +error: + if (sock >= 0) { + ret = close(sock); + if (ret) { + PERROR("close"); + } + } + + lttng_poll_clean(&events); + +error_listen: +error_create_poll: + unlink(the_config.client_unix_sock_path.value); + ret = close(client_sock); + if (ret) { + PERROR("close"); + } + + if (err) { + health_error(); + ERR("Health error occurred in %s", __func__); + } + + health_unregister(the_health_sessiond); + + DBG("Client thread dying"); + lttng_payload_reset(&cmd_ctx.reply_payload); + rcu_unregister_thread(); + return NULL; +} + +static +bool shutdown_client_thread(void *thread_data) +{ + struct lttng_pipe *client_quit_pipe = (lttng_pipe *) thread_data; + const int write_fd = lttng_pipe_get_writefd(client_quit_pipe); + + return notify_thread_pipe(write_fd) == 1; +} + +struct lttng_thread *launch_client_thread(void) +{ + bool thread_running; + struct lttng_pipe *client_quit_pipe; + struct lttng_thread *thread = NULL; + int client_sock_fd = -1; + + sem_init(&thread_state.ready, 0, 0); + client_quit_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!client_quit_pipe) { + goto error; + } + + client_sock_fd = create_client_sock(); + if (client_sock_fd < 0) { + goto error; + } + + thread_state.client_sock = client_sock_fd; + thread = lttng_thread_create("Client management", + thread_manage_clients, + shutdown_client_thread, + cleanup_client_thread, + client_quit_pipe); + if (!thread) { + goto error; + } + /* The client thread now owns the client sock fd and the quit pipe. */ + client_sock_fd = -1; + client_quit_pipe = NULL; + + /* + * This thread is part of the threads that need to be fully + * initialized before the session daemon is marked as "ready". + */ + thread_running = wait_thread_status(); + if (!thread_running) { + goto error; + } + return thread; +error: + if (client_sock_fd >= 0) { + if (close(client_sock_fd)) { + PERROR("Failed to close client socket"); + } + } + lttng_thread_put(thread); + cleanup_client_thread(client_quit_pipe); + return NULL; +} diff --git a/src/bin/lttng-sessiond/cmd.c b/src/bin/lttng-sessiond/cmd.c deleted file mode 100644 index 2c0fa462d..000000000 --- a/src/bin/lttng-sessiond/cmd.c +++ /dev/null @@ -1,5893 +0,0 @@ -/* - * Copyright (C) 2012 David Goulet - * Copyright (C) 2016 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "agent-thread.h" -#include "agent.h" -#include "buffer-registry.h" -#include "channel.h" -#include "cmd.h" -#include "consumer.h" -#include "event-notifier-error-accounting.h" -#include "event.h" -#include "health-sessiond.h" -#include "kernel-consumer.h" -#include "kernel.h" -#include "lttng-sessiond.h" -#include "lttng-syscall.h" -#include "notification-thread-commands.h" -#include "notification-thread.h" -#include "rotate.h" -#include "rotation-thread.h" -#include "session.h" -#include "timer.h" -#include "tracker.h" -#include "utils.h" - -/* Sleep for 100ms between each check for the shm path's deletion. */ -#define SESSION_DESTROY_SHM_PATH_CHECK_DELAY_US 100000 - -struct cmd_destroy_session_reply_context { - int reply_sock_fd; - bool implicit_rotation_on_destroy; - /* - * Indicates whether or not an error occurred while launching the - * destruction of a session. - */ - enum lttng_error_code destruction_status; -}; - -static enum lttng_error_code wait_on_path(void *path); - -/* - * Command completion handler that is used by the destroy command - * when a session that has a non-default shm_path is being destroyed. - * - * See comment in cmd_destroy_session() for the rationale. - */ -static struct destroy_completion_handler { - struct cmd_completion_handler handler; - char shm_path[member_sizeof(struct ltt_session, shm_path)]; -} destroy_completion_handler = { - .handler = { - .run = wait_on_path, - .data = destroy_completion_handler.shm_path - }, - .shm_path = { 0 }, -}; - -static struct cmd_completion_handler *current_completion_handler; - -/* - * Used to keep a unique index for each relayd socket created where this value - * is associated with streams on the consumer so it can match the right relayd - * to send to. It must be accessed with the relayd_net_seq_idx_lock - * held. - */ -static pthread_mutex_t relayd_net_seq_idx_lock = PTHREAD_MUTEX_INITIALIZER; -static uint64_t relayd_net_seq_idx; - -static int validate_ust_event_name(const char *); -static int cmd_enable_event_internal(struct ltt_session *session, - const struct lttng_domain *domain, - char *channel_name, struct lttng_event *event, - char *filter_expression, - struct lttng_bytecode *filter, - struct lttng_event_exclusion *exclusion, - int wpipe); - -/* - * Create a session path used by list_lttng_sessions for the case that the - * session consumer is on the network. - */ -static int build_network_session_path(char *dst, size_t size, - struct ltt_session *session) -{ - int ret, kdata_port, udata_port; - struct lttng_uri *kuri = NULL, *uuri = NULL, *uri = NULL; - char tmp_uurl[PATH_MAX], tmp_urls[PATH_MAX]; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(dst); - - memset(tmp_urls, 0, sizeof(tmp_urls)); - memset(tmp_uurl, 0, sizeof(tmp_uurl)); - - kdata_port = udata_port = DEFAULT_NETWORK_DATA_PORT; - - if (session->kernel_session && session->kernel_session->consumer) { - kuri = &session->kernel_session->consumer->dst.net.control; - kdata_port = session->kernel_session->consumer->dst.net.data.port; - } - - if (session->ust_session && session->ust_session->consumer) { - uuri = &session->ust_session->consumer->dst.net.control; - udata_port = session->ust_session->consumer->dst.net.data.port; - } - - if (uuri == NULL && kuri == NULL) { - uri = &session->consumer->dst.net.control; - kdata_port = session->consumer->dst.net.data.port; - } else if (kuri && uuri) { - ret = uri_compare(kuri, uuri); - if (ret) { - /* Not Equal */ - uri = kuri; - /* Build uuri URL string */ - ret = uri_to_str_url(uuri, tmp_uurl, sizeof(tmp_uurl)); - if (ret < 0) { - goto error; - } - } else { - uri = kuri; - } - } else if (kuri && uuri == NULL) { - uri = kuri; - } else if (uuri && kuri == NULL) { - uri = uuri; - } - - ret = uri_to_str_url(uri, tmp_urls, sizeof(tmp_urls)); - if (ret < 0) { - goto error; - } - - /* - * Do we have a UST url set. If yes, this means we have both kernel and UST - * to print. - */ - if (*tmp_uurl != '\0') { - ret = snprintf(dst, size, "[K]: %s [data: %d] -- [U]: %s [data: %d]", - tmp_urls, kdata_port, tmp_uurl, udata_port); - } else { - int dport; - if (kuri || (!kuri && !uuri)) { - dport = kdata_port; - } else { - /* No kernel URI, use the UST port. */ - dport = udata_port; - } - ret = snprintf(dst, size, "%s [data: %d]", tmp_urls, dport); - } - -error: - return ret; -} - -/* - * Get run-time attributes if the session has been started (discarded events, - * lost packets). - */ -static int get_kernel_runtime_stats(struct ltt_session *session, - struct ltt_kernel_channel *kchan, uint64_t *discarded_events, - uint64_t *lost_packets) -{ - int ret; - - if (!session->has_been_started) { - ret = 0; - *discarded_events = 0; - *lost_packets = 0; - goto end; - } - - ret = consumer_get_discarded_events(session->id, kchan->key, - session->kernel_session->consumer, - discarded_events); - if (ret < 0) { - goto end; - } - - ret = consumer_get_lost_packets(session->id, kchan->key, - session->kernel_session->consumer, - lost_packets); - if (ret < 0) { - goto end; - } - -end: - return ret; -} - -/* - * Get run-time attributes if the session has been started (discarded events, - * lost packets). - */ -static int get_ust_runtime_stats(struct ltt_session *session, - struct ltt_ust_channel *uchan, uint64_t *discarded_events, - uint64_t *lost_packets) -{ - int ret; - struct ltt_ust_session *usess; - - if (!discarded_events || !lost_packets) { - ret = -1; - goto end; - } - - usess = session->ust_session; - LTTNG_ASSERT(discarded_events); - LTTNG_ASSERT(lost_packets); - - if (!usess || !session->has_been_started) { - *discarded_events = 0; - *lost_packets = 0; - ret = 0; - goto end; - } - - if (usess->buffer_type == LTTNG_BUFFER_PER_UID) { - ret = ust_app_uid_get_channel_runtime_stats(usess->id, - &usess->buffer_reg_uid_list, - usess->consumer, uchan->id, - uchan->attr.overwrite, - discarded_events, - lost_packets); - } else if (usess->buffer_type == LTTNG_BUFFER_PER_PID) { - ret = ust_app_pid_get_channel_runtime_stats(usess, - uchan, usess->consumer, - uchan->attr.overwrite, - discarded_events, - lost_packets); - if (ret < 0) { - goto end; - } - *discarded_events += uchan->per_pid_closed_app_discarded; - *lost_packets += uchan->per_pid_closed_app_lost; - } else { - ERR("Unsupported buffer type"); - abort(); - ret = -1; - goto end; - } - -end: - return ret; -} - -/* - * Fill lttng_channel array of all channels. - */ -static ssize_t list_lttng_channels(enum lttng_domain_type domain, - struct ltt_session *session, struct lttng_channel *channels, - struct lttng_channel_extended *chan_exts) -{ - int i = 0, ret = 0; - struct ltt_kernel_channel *kchan; - - DBG("Listing channels for session %s", session->name); - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - /* Kernel channels */ - if (session->kernel_session != NULL) { - cds_list_for_each_entry(kchan, - &session->kernel_session->channel_list.head, list) { - uint64_t discarded_events, lost_packets; - struct lttng_channel_extended *extended; - - extended = (struct lttng_channel_extended *) - kchan->channel->attr.extended.ptr; - - ret = get_kernel_runtime_stats(session, kchan, - &discarded_events, &lost_packets); - if (ret < 0) { - goto end; - } - /* Copy lttng_channel struct to array */ - memcpy(&channels[i], kchan->channel, sizeof(struct lttng_channel)); - channels[i].enabled = kchan->enabled; - chan_exts[i].discarded_events = - discarded_events; - chan_exts[i].lost_packets = lost_packets; - chan_exts[i].monitor_timer_interval = - extended->monitor_timer_interval; - chan_exts[i].blocking_timeout = 0; - i++; - } - } - break; - case LTTNG_DOMAIN_UST: - { - struct lttng_ht_iter iter; - struct ltt_ust_channel *uchan; - - rcu_read_lock(); - cds_lfht_for_each_entry(session->ust_session->domain_global.channels->ht, - &iter.iter, uchan, node.node) { - uint64_t discarded_events = 0, lost_packets = 0; - - if (lttng_strncpy(channels[i].name, uchan->name, - LTTNG_SYMBOL_NAME_LEN)) { - break; - } - channels[i].attr.overwrite = uchan->attr.overwrite; - channels[i].attr.subbuf_size = uchan->attr.subbuf_size; - channels[i].attr.num_subbuf = uchan->attr.num_subbuf; - channels[i].attr.switch_timer_interval = - uchan->attr.switch_timer_interval; - channels[i].attr.read_timer_interval = - uchan->attr.read_timer_interval; - channels[i].enabled = uchan->enabled; - channels[i].attr.tracefile_size = uchan->tracefile_size; - channels[i].attr.tracefile_count = uchan->tracefile_count; - - /* - * Map enum lttng_ust_output to enum lttng_event_output. - */ - switch (uchan->attr.output) { - case LTTNG_UST_ABI_MMAP: - channels[i].attr.output = LTTNG_EVENT_MMAP; - break; - default: - /* - * LTTNG_UST_MMAP is the only supported UST - * output mode. - */ - abort(); - break; - } - - chan_exts[i].monitor_timer_interval = - uchan->monitor_timer_interval; - chan_exts[i].blocking_timeout = - uchan->attr.u.s.blocking_timeout; - - ret = get_ust_runtime_stats(session, uchan, - &discarded_events, &lost_packets); - if (ret < 0) { - break; - } - chan_exts[i].discarded_events = discarded_events; - chan_exts[i].lost_packets = lost_packets; - i++; - } - rcu_read_unlock(); - break; - } - default: - break; - } - -end: - if (ret < 0) { - return -LTTNG_ERR_FATAL; - } else { - return LTTNG_OK; - } -} - -static int append_extended_info(const char *filter_expression, - struct lttng_event_exclusion *exclusion, - struct lttng_userspace_probe_location *probe_location, - struct lttng_payload *payload) -{ - int ret = 0; - size_t filter_len = 0; - size_t nb_exclusions = 0; - size_t userspace_probe_location_len = 0; - struct lttcomm_event_extended_header extended_header = {}; - struct lttcomm_event_extended_header *p_extended_header; - const size_t original_payload_size = payload->buffer.size; - - ret = lttng_dynamic_buffer_append(&payload->buffer, &extended_header, - sizeof(extended_header)); - if (ret) { - goto end; - } - - if (filter_expression) { - filter_len = strlen(filter_expression) + 1; - ret = lttng_dynamic_buffer_append(&payload->buffer, - filter_expression, filter_len); - if (ret) { - goto end; - } - } - - if (exclusion) { - const size_t len = exclusion->count * LTTNG_SYMBOL_NAME_LEN; - - nb_exclusions = exclusion->count; - - ret = lttng_dynamic_buffer_append( - &payload->buffer, &exclusion->names, len); - if (ret) { - goto end; - } - } - - if (probe_location) { - const size_t size_before_probe = payload->buffer.size; - - ret = lttng_userspace_probe_location_serialize(probe_location, - payload); - if (ret < 0) { - ret = -1; - goto end; - } - - userspace_probe_location_len = - payload->buffer.size - size_before_probe; - } - - /* Set header fields */ - p_extended_header = (struct lttcomm_event_extended_header *) - (payload->buffer.data + original_payload_size); - - p_extended_header->filter_len = filter_len; - p_extended_header->nb_exclusions = nb_exclusions; - p_extended_header->userspace_probe_location_len = - userspace_probe_location_len; - - ret = 0; -end: - return ret; -} - -/* - * Create a list of agent domain events. - * - * Return number of events in list on success or else a negative value. - */ -static int list_lttng_agent_events(struct agent *agt, - struct lttng_payload *payload) -{ - int nb_events = 0, ret = 0; - const struct agent_event *agent_event; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(agt); - - DBG3("Listing agent events"); - - rcu_read_lock(); - cds_lfht_for_each_entry ( - agt->events->ht, &iter.iter, agent_event, node.node) { - struct lttng_event event = { - .enabled = AGENT_EVENT_IS_ENABLED(agent_event), - .loglevel = agent_event->loglevel_value, - .loglevel_type = agent_event->loglevel_type, - }; - - ret = lttng_strncpy(event.name, agent_event->name, sizeof(event.name)); - if (ret) { - /* Internal error, invalid name. */ - ERR("Invalid event name while listing agent events: '%s' exceeds the maximal allowed length of %zu bytes", - agent_event->name, sizeof(event.name)); - ret = -LTTNG_ERR_UNK; - goto end; - } - - ret = lttng_dynamic_buffer_append( - &payload->buffer, &event, sizeof(event)); - if (ret) { - ERR("Failed to append event to payload"); - ret = -LTTNG_ERR_NOMEM; - goto end; - } - - nb_events++; - } - - cds_lfht_for_each_entry ( - agt->events->ht, &iter.iter, agent_event, node.node) { - /* Append extended info. */ - ret = append_extended_info(agent_event->filter_expression, NULL, - NULL, payload); - if (ret) { - ERR("Failed to append extended event info to payload"); - ret = -LTTNG_ERR_NOMEM; - goto end; - } - } - - ret = nb_events; -end: - rcu_read_unlock(); - return ret; -} - -/* - * Create a list of ust global domain events. - */ -static int list_lttng_ust_global_events(char *channel_name, - struct ltt_ust_domain_global *ust_global, - struct lttng_payload *payload) -{ - int ret = 0; - unsigned int nb_events = 0; - struct lttng_ht_iter iter; - const struct lttng_ht_node_str *node; - const struct ltt_ust_channel *uchan; - const struct ltt_ust_event *uevent; - - DBG("Listing UST global events for channel %s", channel_name); - - rcu_read_lock(); - - lttng_ht_lookup(ust_global->channels, (void *) channel_name, &iter); - node = lttng_ht_iter_get_node_str(&iter); - if (node == NULL) { - ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; - goto end; - } - - uchan = caa_container_of(&node->node, struct ltt_ust_channel, node.node); - - DBG3("Listing UST global events"); - - cds_lfht_for_each_entry(uchan->events->ht, &iter.iter, uevent, node.node) { - struct lttng_event event = {}; - - if (uevent->internal) { - continue; - } - - ret = lttng_strncpy(event.name, uevent->attr.name, sizeof(event.name)); - if (ret) { - /* Internal error, invalid name. */ - ERR("Invalid event name while listing user space tracer events: '%s' exceeds the maximal allowed length of %zu bytes", - uevent->attr.name, sizeof(event.name)); - ret = -LTTNG_ERR_UNK; - goto end; - } - - event.enabled = uevent->enabled; - - switch (uevent->attr.instrumentation) { - case LTTNG_UST_ABI_TRACEPOINT: - event.type = LTTNG_EVENT_TRACEPOINT; - break; - case LTTNG_UST_ABI_PROBE: - event.type = LTTNG_EVENT_PROBE; - break; - case LTTNG_UST_ABI_FUNCTION: - event.type = LTTNG_EVENT_FUNCTION; - break; - } - - event.loglevel = uevent->attr.loglevel; - switch (uevent->attr.loglevel_type) { - case LTTNG_UST_ABI_LOGLEVEL_ALL: - event.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; - break; - case LTTNG_UST_ABI_LOGLEVEL_RANGE: - event.loglevel_type = LTTNG_EVENT_LOGLEVEL_RANGE; - break; - case LTTNG_UST_ABI_LOGLEVEL_SINGLE: - event.loglevel_type = LTTNG_EVENT_LOGLEVEL_SINGLE; - break; - } - - if (uevent->filter) { - event.filter = 1; - } - - if (uevent->exclusion) { - event.exclusion = 1; - } - - ret = lttng_dynamic_buffer_append(&payload->buffer, &event, sizeof(event)); - if (ret) { - ERR("Failed to append event to payload"); - ret = -LTTNG_ERR_NOMEM; - goto end; - } - - nb_events++; - } - - cds_lfht_for_each_entry(uchan->events->ht, &iter.iter, uevent, node.node) { - /* Append extended info. */ - ret = append_extended_info(uevent->filter_expression, - uevent->exclusion, NULL, payload); - if (ret) { - ERR("Failed to append extended event info to payload"); - ret = -LTTNG_ERR_FATAL; - goto end; - } - } - - ret = nb_events; -end: - rcu_read_unlock(); - return ret; -} - -/* - * Fill lttng_event array of all kernel events in the channel. - */ -static int list_lttng_kernel_events(char *channel_name, - struct ltt_kernel_session *kernel_session, - struct lttng_payload *payload) -{ - int ret; - unsigned int nb_event; - const struct ltt_kernel_event *kevent; - const struct ltt_kernel_channel *kchan; - - kchan = trace_kernel_get_channel_by_name(channel_name, kernel_session); - if (kchan == NULL) { - ret = LTTNG_ERR_KERN_CHAN_NOT_FOUND; - goto error; - } - - nb_event = kchan->event_count; - - DBG("Listing events for channel %s", kchan->channel->name); - - /* Kernel channels */ - cds_list_for_each_entry(kevent, &kchan->events_list.head , list) { - struct lttng_event event = {}; - - ret = lttng_strncpy(event.name, kevent->event->name, sizeof(event.name)); - if (ret) { - /* Internal error, invalid name. */ - ERR("Invalid event name while listing kernel events: '%s' exceeds the maximal allowed length of %zu bytes", - kevent->event->name, - sizeof(event.name)); - ret = -LTTNG_ERR_UNK; - goto end; - } - - event.enabled = kevent->enabled; - event.filter = (unsigned char) !!kevent->filter_expression; - - switch (kevent->event->instrumentation) { - case LTTNG_KERNEL_ABI_TRACEPOINT: - event.type = LTTNG_EVENT_TRACEPOINT; - break; - case LTTNG_KERNEL_ABI_KRETPROBE: - event.type = LTTNG_EVENT_FUNCTION; - memcpy(&event.attr.probe, &kevent->event->u.kprobe, - sizeof(struct lttng_kernel_abi_kprobe)); - break; - case LTTNG_KERNEL_ABI_KPROBE: - event.type = LTTNG_EVENT_PROBE; - memcpy(&event.attr.probe, &kevent->event->u.kprobe, - sizeof(struct lttng_kernel_abi_kprobe)); - break; - case LTTNG_KERNEL_ABI_UPROBE: - event.type = LTTNG_EVENT_USERSPACE_PROBE; - break; - case LTTNG_KERNEL_ABI_FUNCTION: - event.type = LTTNG_EVENT_FUNCTION; - memcpy(&event.attr.ftrace, &kevent->event->u.ftrace, - sizeof(struct lttng_kernel_abi_function)); - break; - case LTTNG_KERNEL_ABI_NOOP: - event.type = LTTNG_EVENT_NOOP; - break; - case LTTNG_KERNEL_ABI_SYSCALL: - event.type = LTTNG_EVENT_SYSCALL; - break; - case LTTNG_KERNEL_ABI_ALL: - /* fall-through. */ - default: - abort(); - break; - } - - ret = lttng_dynamic_buffer_append( - &payload->buffer, &event, sizeof(event)); - if (ret) { - ERR("Failed to append event to payload"); - ret = -LTTNG_ERR_NOMEM; - goto end; - } - } - - cds_list_for_each_entry(kevent, &kchan->events_list.head , list) { - /* Append extended info. */ - ret = append_extended_info(kevent->filter_expression, NULL, - kevent->userspace_probe_location, payload); - if (ret) { - DBG("Error appending extended info message"); - ret = -LTTNG_ERR_FATAL; - goto error; - } - } - -end: - return nb_event; -error: - return ret; -} - -/* - * Add URI so the consumer output object. Set the correct path depending on the - * domain adding the default trace directory. - */ -static enum lttng_error_code add_uri_to_consumer( - const struct ltt_session *session, - struct consumer_output *consumer, - struct lttng_uri *uri, enum lttng_domain_type domain) -{ - int ret; - enum lttng_error_code ret_code = LTTNG_OK; - - LTTNG_ASSERT(uri); - - if (consumer == NULL) { - DBG("No consumer detected. Don't add URI. Stopping."); - ret_code = LTTNG_ERR_NO_CONSUMER; - goto error; - } - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - ret = lttng_strncpy(consumer->domain_subdir, - DEFAULT_KERNEL_TRACE_DIR, - sizeof(consumer->domain_subdir)); - break; - case LTTNG_DOMAIN_UST: - ret = lttng_strncpy(consumer->domain_subdir, - DEFAULT_UST_TRACE_DIR, - sizeof(consumer->domain_subdir)); - break; - default: - /* - * This case is possible is we try to add the URI to the global - * tracing session consumer object which in this case there is - * no subdir. - */ - memset(consumer->domain_subdir, 0, - sizeof(consumer->domain_subdir)); - ret = 0; - } - if (ret) { - ERR("Failed to initialize consumer output domain subdirectory"); - ret_code = LTTNG_ERR_FATAL; - goto error; - } - - switch (uri->dtype) { - case LTTNG_DST_IPV4: - case LTTNG_DST_IPV6: - DBG2("Setting network URI to consumer"); - - if (consumer->type == CONSUMER_DST_NET) { - if ((uri->stype == LTTNG_STREAM_CONTROL && - consumer->dst.net.control_isset) || - (uri->stype == LTTNG_STREAM_DATA && - consumer->dst.net.data_isset)) { - ret_code = LTTNG_ERR_URL_EXIST; - goto error; - } - } else { - memset(&consumer->dst, 0, sizeof(consumer->dst)); - } - - /* Set URI into consumer output object */ - ret = consumer_set_network_uri(session, consumer, uri); - if (ret < 0) { - ret_code = -ret; - goto error; - } else if (ret == 1) { - /* - * URI was the same in the consumer so we do not append the subdir - * again so to not duplicate output dir. - */ - ret_code = LTTNG_OK; - goto error; - } - break; - case LTTNG_DST_PATH: - if (*uri->dst.path != '/' || strstr(uri->dst.path, "../")) { - ret_code = LTTNG_ERR_INVALID; - goto error; - } - DBG2("Setting trace directory path from URI to %s", - uri->dst.path); - memset(&consumer->dst, 0, sizeof(consumer->dst)); - - ret = lttng_strncpy(consumer->dst.session_root_path, - uri->dst.path, - sizeof(consumer->dst.session_root_path)); - if (ret) { - ret_code = LTTNG_ERR_FATAL; - goto error; - } - consumer->type = CONSUMER_DST_LOCAL; - break; - } - - ret_code = LTTNG_OK; -error: - return ret_code; -} - -/* - * Init tracing by creating trace directory and sending fds kernel consumer. - */ -static int init_kernel_tracing(struct ltt_kernel_session *session) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct consumer_socket *socket; - - LTTNG_ASSERT(session); - - rcu_read_lock(); - - if (session->consumer_fds_sent == 0 && session->consumer != NULL) { - cds_lfht_for_each_entry(session->consumer->socks->ht, &iter.iter, - socket, node.node) { - pthread_mutex_lock(socket->lock); - ret = kernel_consumer_send_session(socket, session); - pthread_mutex_unlock(socket->lock); - if (ret < 0) { - ret = LTTNG_ERR_KERN_CONSUMER_FAIL; - goto error; - } - } - } - -error: - rcu_read_unlock(); - return ret; -} - -/* - * Create a socket to the relayd using the URI. - * - * On success, the relayd_sock pointer is set to the created socket. - * Else, it remains untouched and an LTTng error code is returned. - */ -static enum lttng_error_code create_connect_relayd(struct lttng_uri *uri, - struct lttcomm_relayd_sock **relayd_sock, - struct consumer_output *consumer) -{ - int ret; - enum lttng_error_code status = LTTNG_OK; - struct lttcomm_relayd_sock *rsock; - - rsock = lttcomm_alloc_relayd_sock(uri, RELAYD_VERSION_COMM_MAJOR, - RELAYD_VERSION_COMM_MINOR); - if (!rsock) { - status = LTTNG_ERR_FATAL; - goto error; - } - - /* - * Connect to relayd so we can proceed with a session creation. This call - * can possibly block for an arbitrary amount of time to set the health - * state to be in poll execution. - */ - health_poll_entry(); - ret = relayd_connect(rsock); - health_poll_exit(); - if (ret < 0) { - ERR("Unable to reach lttng-relayd"); - status = LTTNG_ERR_RELAYD_CONNECT_FAIL; - goto free_sock; - } - - /* Create socket for control stream. */ - if (uri->stype == LTTNG_STREAM_CONTROL) { - uint64_t result_flags; - - DBG3("Creating relayd stream socket from URI"); - - /* Check relayd version */ - ret = relayd_version_check(rsock); - if (ret == LTTNG_ERR_RELAYD_VERSION_FAIL) { - status = LTTNG_ERR_RELAYD_VERSION_FAIL; - goto close_sock; - } else if (ret < 0) { - ERR("Unable to reach lttng-relayd"); - status = LTTNG_ERR_RELAYD_CONNECT_FAIL; - goto close_sock; - } - consumer->relay_major_version = rsock->major; - consumer->relay_minor_version = rsock->minor; - ret = relayd_get_configuration(rsock, 0, - &result_flags); - if (ret < 0) { - ERR("Unable to get relayd configuration"); - status = LTTNG_ERR_RELAYD_CONNECT_FAIL; - goto close_sock; - } - if (result_flags & LTTCOMM_RELAYD_CONFIGURATION_FLAG_CLEAR_ALLOWED) { - consumer->relay_allows_clear = true; - } - } else if (uri->stype == LTTNG_STREAM_DATA) { - DBG3("Creating relayd data socket from URI"); - } else { - /* Command is not valid */ - ERR("Relayd invalid stream type: %d", uri->stype); - status = LTTNG_ERR_INVALID; - goto close_sock; - } - - *relayd_sock = rsock; - - return status; - -close_sock: - /* The returned value is not useful since we are on an error path. */ - (void) relayd_close(rsock); -free_sock: - free(rsock); -error: - return status; -} - -/* - * Connect to the relayd using URI and send the socket to the right consumer. - * - * The consumer socket lock must be held by the caller. - * - * Returns LTTNG_OK on success or an LTTng error code on failure. - */ -static enum lttng_error_code send_consumer_relayd_socket( - unsigned int session_id, - struct lttng_uri *relayd_uri, - struct consumer_output *consumer, - struct consumer_socket *consumer_sock, - const char *session_name, const char *hostname, - const char *base_path, int session_live_timer, - const uint64_t *current_chunk_id, - time_t session_creation_time, - bool session_name_contains_creation_time) -{ - int ret; - struct lttcomm_relayd_sock *rsock = NULL; - enum lttng_error_code status; - - /* Connect to relayd and make version check if uri is the control. */ - status = create_connect_relayd(relayd_uri, &rsock, consumer); - if (status != LTTNG_OK) { - goto relayd_comm_error; - } - LTTNG_ASSERT(rsock); - - /* Set the network sequence index if not set. */ - if (consumer->net_seq_index == (uint64_t) -1ULL) { - pthread_mutex_lock(&relayd_net_seq_idx_lock); - /* - * Increment net_seq_idx because we are about to transfer the - * new relayd socket to the consumer. - * Assign unique key so the consumer can match streams. - */ - consumer->net_seq_index = ++relayd_net_seq_idx; - pthread_mutex_unlock(&relayd_net_seq_idx_lock); - } - - /* Send relayd socket to consumer. */ - ret = consumer_send_relayd_socket(consumer_sock, rsock, consumer, - relayd_uri->stype, session_id, - session_name, hostname, base_path, - session_live_timer, current_chunk_id, - session_creation_time, session_name_contains_creation_time); - if (ret < 0) { - status = LTTNG_ERR_ENABLE_CONSUMER_FAIL; - goto close_sock; - } - - /* Flag that the corresponding socket was sent. */ - if (relayd_uri->stype == LTTNG_STREAM_CONTROL) { - consumer_sock->control_sock_sent = 1; - } else if (relayd_uri->stype == LTTNG_STREAM_DATA) { - consumer_sock->data_sock_sent = 1; - } - - /* - * Close socket which was dup on the consumer side. The session daemon does - * NOT keep track of the relayd socket(s) once transfer to the consumer. - */ - -close_sock: - if (status != LTTNG_OK) { - /* - * The consumer output for this session should not be used anymore - * since the relayd connection failed thus making any tracing or/and - * streaming not usable. - */ - consumer->enabled = 0; - } - (void) relayd_close(rsock); - free(rsock); - -relayd_comm_error: - return status; -} - -/* - * Send both relayd sockets to a specific consumer and domain. This is a - * helper function to facilitate sending the information to the consumer for a - * session. - * - * The consumer socket lock must be held by the caller. - * - * Returns LTTNG_OK, or an LTTng error code on failure. - */ -static enum lttng_error_code send_consumer_relayd_sockets( - enum lttng_domain_type domain, - unsigned int session_id, struct consumer_output *consumer, - struct consumer_socket *sock, const char *session_name, - const char *hostname, const char *base_path, int session_live_timer, - const uint64_t *current_chunk_id, time_t session_creation_time, - bool session_name_contains_creation_time) -{ - enum lttng_error_code status = LTTNG_OK; - - LTTNG_ASSERT(consumer); - LTTNG_ASSERT(sock); - - /* Sending control relayd socket. */ - if (!sock->control_sock_sent) { - status = send_consumer_relayd_socket(session_id, - &consumer->dst.net.control, consumer, sock, - session_name, hostname, base_path, session_live_timer, - current_chunk_id, session_creation_time, - session_name_contains_creation_time); - if (status != LTTNG_OK) { - goto error; - } - } - - /* Sending data relayd socket. */ - if (!sock->data_sock_sent) { - status = send_consumer_relayd_socket(session_id, - &consumer->dst.net.data, consumer, sock, - session_name, hostname, base_path, session_live_timer, - current_chunk_id, session_creation_time, - session_name_contains_creation_time); - if (status != LTTNG_OK) { - goto error; - } - } - -error: - return status; -} - -/* - * Setup relayd connections for a tracing session. First creates the socket to - * the relayd and send them to the right domain consumer. Consumer type MUST be - * network. - */ -int cmd_setup_relayd(struct ltt_session *session) -{ - int ret = LTTNG_OK; - struct ltt_ust_session *usess; - struct ltt_kernel_session *ksess; - struct consumer_socket *socket; - struct lttng_ht_iter iter; - LTTNG_OPTIONAL(uint64_t) current_chunk_id = {}; - - LTTNG_ASSERT(session); - - usess = session->ust_session; - ksess = session->kernel_session; - - DBG("Setting relayd for session %s", session->name); - - rcu_read_lock(); - if (session->current_trace_chunk) { - enum lttng_trace_chunk_status status = lttng_trace_chunk_get_id( - session->current_trace_chunk, ¤t_chunk_id.value); - - if (status == LTTNG_TRACE_CHUNK_STATUS_OK) { - current_chunk_id.is_set = true; - } else { - ERR("Failed to get current trace chunk id"); - ret = LTTNG_ERR_UNK; - goto error; - } - } - - if (usess && usess->consumer && usess->consumer->type == CONSUMER_DST_NET - && usess->consumer->enabled) { - /* For each consumer socket, send relayd sockets */ - cds_lfht_for_each_entry(usess->consumer->socks->ht, &iter.iter, - socket, node.node) { - pthread_mutex_lock(socket->lock); - ret = send_consumer_relayd_sockets(LTTNG_DOMAIN_UST, session->id, - usess->consumer, socket, - session->name, session->hostname, - session->base_path, - session->live_timer, - current_chunk_id.is_set ? ¤t_chunk_id.value : NULL, - session->creation_time, - session->name_contains_creation_time); - pthread_mutex_unlock(socket->lock); - if (ret != LTTNG_OK) { - goto error; - } - /* Session is now ready for network streaming. */ - session->net_handle = 1; - } - session->consumer->relay_major_version = - usess->consumer->relay_major_version; - session->consumer->relay_minor_version = - usess->consumer->relay_minor_version; - session->consumer->relay_allows_clear = - usess->consumer->relay_allows_clear; - } - - if (ksess && ksess->consumer && ksess->consumer->type == CONSUMER_DST_NET - && ksess->consumer->enabled) { - cds_lfht_for_each_entry(ksess->consumer->socks->ht, &iter.iter, - socket, node.node) { - pthread_mutex_lock(socket->lock); - ret = send_consumer_relayd_sockets(LTTNG_DOMAIN_KERNEL, session->id, - ksess->consumer, socket, - session->name, session->hostname, - session->base_path, - session->live_timer, - current_chunk_id.is_set ? ¤t_chunk_id.value : NULL, - session->creation_time, - session->name_contains_creation_time); - pthread_mutex_unlock(socket->lock); - if (ret != LTTNG_OK) { - goto error; - } - /* Session is now ready for network streaming. */ - session->net_handle = 1; - } - session->consumer->relay_major_version = - ksess->consumer->relay_major_version; - session->consumer->relay_minor_version = - ksess->consumer->relay_minor_version; - session->consumer->relay_allows_clear = - ksess->consumer->relay_allows_clear; - } - -error: - rcu_read_unlock(); - return ret; -} - -/* - * Start a kernel session by opening all necessary streams. - */ -int start_kernel_session(struct ltt_kernel_session *ksess) -{ - int ret; - struct ltt_kernel_channel *kchan; - - /* Open kernel metadata */ - if (ksess->metadata == NULL && ksess->output_traces) { - ret = kernel_open_metadata(ksess); - if (ret < 0) { - ret = LTTNG_ERR_KERN_META_FAIL; - goto error; - } - } - - /* Open kernel metadata stream */ - if (ksess->metadata && ksess->metadata_stream_fd < 0) { - ret = kernel_open_metadata_stream(ksess); - if (ret < 0) { - ERR("Kernel create metadata stream failed"); - ret = LTTNG_ERR_KERN_STREAM_FAIL; - goto error; - } - } - - /* For each channel */ - cds_list_for_each_entry(kchan, &ksess->channel_list.head, list) { - if (kchan->stream_count == 0) { - ret = kernel_open_channel_stream(kchan); - if (ret < 0) { - ret = LTTNG_ERR_KERN_STREAM_FAIL; - goto error; - } - /* Update the stream global counter */ - ksess->stream_count_global += ret; - } - } - - /* Setup kernel consumer socket and send fds to it */ - ret = init_kernel_tracing(ksess); - if (ret != 0) { - ret = LTTNG_ERR_KERN_START_FAIL; - goto error; - } - - /* This start the kernel tracing */ - ret = kernel_start_session(ksess); - if (ret < 0) { - ret = LTTNG_ERR_KERN_START_FAIL; - goto error; - } - - /* Quiescent wait after starting trace */ - kernel_wait_quiescent(); - - ksess->active = 1; - - ret = LTTNG_OK; - -error: - return ret; -} - -int stop_kernel_session(struct ltt_kernel_session *ksess) -{ - struct ltt_kernel_channel *kchan; - bool error_occurred = false; - int ret; - - if (!ksess || !ksess->active) { - return LTTNG_OK; - } - DBG("Stopping kernel tracing"); - - ret = kernel_stop_session(ksess); - if (ret < 0) { - ret = LTTNG_ERR_KERN_STOP_FAIL; - goto error; - } - - kernel_wait_quiescent(); - - /* Flush metadata after stopping (if exists) */ - if (ksess->metadata_stream_fd >= 0) { - ret = kernel_metadata_flush_buffer(ksess->metadata_stream_fd); - if (ret < 0) { - ERR("Kernel metadata flush failed"); - error_occurred = true; - } - } - - /* Flush all buffers after stopping */ - cds_list_for_each_entry(kchan, &ksess->channel_list.head, list) { - ret = kernel_flush_buffer(kchan); - if (ret < 0) { - ERR("Kernel flush buffer error"); - error_occurred = true; - } - } - - ksess->active = 0; - if (error_occurred) { - ret = LTTNG_ERR_UNK; - } else { - ret = LTTNG_OK; - } -error: - return ret; -} - -/* - * Command LTTNG_DISABLE_CHANNEL processed by the client thread. - */ -int cmd_disable_channel(struct ltt_session *session, - enum lttng_domain_type domain, char *channel_name) -{ - int ret; - struct ltt_ust_session *usess; - - usess = session->ust_session; - - rcu_read_lock(); - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - { - ret = channel_kernel_disable(session->kernel_session, - channel_name); - if (ret != LTTNG_OK) { - goto error; - } - - kernel_wait_quiescent(); - break; - } - case LTTNG_DOMAIN_UST: - { - struct ltt_ust_channel *uchan; - struct lttng_ht *chan_ht; - - chan_ht = usess->domain_global.channels; - - uchan = trace_ust_find_channel_by_name(chan_ht, channel_name); - if (uchan == NULL) { - ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; - goto error; - } - - ret = channel_ust_disable(usess, uchan); - if (ret != LTTNG_OK) { - goto error; - } - break; - } - default: - ret = LTTNG_ERR_UNKNOWN_DOMAIN; - goto error; - } - - ret = LTTNG_OK; - -error: - rcu_read_unlock(); - return ret; -} - -/* - * Command LTTNG_ENABLE_CHANNEL processed by the client thread. - * - * The wpipe arguments is used as a notifier for the kernel thread. - */ -int cmd_enable_channel(struct ltt_session *session, - const struct lttng_domain *domain, const struct lttng_channel *_attr, int wpipe) -{ - int ret; - struct ltt_ust_session *usess = session->ust_session; - struct lttng_ht *chan_ht; - size_t len; - struct lttng_channel attr; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(_attr); - LTTNG_ASSERT(domain); - - attr = *_attr; - len = lttng_strnlen(attr.name, sizeof(attr.name)); - - /* Validate channel name */ - if (attr.name[0] == '.' || - memchr(attr.name, '/', len) != NULL) { - ret = LTTNG_ERR_INVALID_CHANNEL_NAME; - goto end; - } - - DBG("Enabling channel %s for session %s", attr.name, session->name); - - rcu_read_lock(); - - /* - * If the session is a live session, remove the switch timer, the - * live timer does the same thing but sends also synchronisation - * beacons for inactive streams. - */ - if (session->live_timer > 0) { - attr.attr.live_timer_interval = session->live_timer; - attr.attr.switch_timer_interval = 0; - } - - /* Check for feature support */ - switch (domain->type) { - case LTTNG_DOMAIN_KERNEL: - { - if (kernel_supports_ring_buffer_snapshot_sample_positions() != 1) { - /* Sampling position of buffer is not supported */ - WARN("Kernel tracer does not support buffer monitoring. " - "Setting the monitor interval timer to 0 " - "(disabled) for channel '%s' of session '%s'", - attr.name, session->name); - lttng_channel_set_monitor_timer_interval(&attr, 0); - } - break; - } - case LTTNG_DOMAIN_UST: - break; - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_PYTHON: - if (!agent_tracing_is_enabled()) { - DBG("Attempted to enable a channel in an agent domain but the agent thread is not running"); - ret = LTTNG_ERR_AGENT_TRACING_DISABLED; - goto error; - } - break; - default: - ret = LTTNG_ERR_UNKNOWN_DOMAIN; - goto error; - } - - switch (domain->type) { - case LTTNG_DOMAIN_KERNEL: - { - struct ltt_kernel_channel *kchan; - - kchan = trace_kernel_get_channel_by_name(attr.name, - session->kernel_session); - if (kchan == NULL) { - /* - * Don't try to create a channel if the session has been started at - * some point in time before. The tracer does not allow it. - */ - if (session->has_been_started) { - ret = LTTNG_ERR_TRACE_ALREADY_STARTED; - goto error; - } - - if (session->snapshot.nb_output > 0 || - session->snapshot_mode) { - /* Enforce mmap output for snapshot sessions. */ - attr.attr.output = LTTNG_EVENT_MMAP; - } - ret = channel_kernel_create(session->kernel_session, &attr, wpipe); - if (attr.name[0] != '\0') { - session->kernel_session->has_non_default_channel = 1; - } - } else { - ret = channel_kernel_enable(session->kernel_session, kchan); - } - - if (ret != LTTNG_OK) { - goto error; - } - - kernel_wait_quiescent(); - break; - } - case LTTNG_DOMAIN_UST: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_PYTHON: - { - struct ltt_ust_channel *uchan; - - /* - * FIXME - * - * Current agent implementation limitations force us to allow - * only one channel at once in "agent" subdomains. Each - * subdomain has a default channel name which must be strictly - * adhered to. - */ - if (domain->type == LTTNG_DOMAIN_JUL) { - if (strncmp(attr.name, DEFAULT_JUL_CHANNEL_NAME, - LTTNG_SYMBOL_NAME_LEN)) { - ret = LTTNG_ERR_INVALID_CHANNEL_NAME; - goto error; - } - } else if (domain->type == LTTNG_DOMAIN_LOG4J) { - if (strncmp(attr.name, DEFAULT_LOG4J_CHANNEL_NAME, - LTTNG_SYMBOL_NAME_LEN)) { - ret = LTTNG_ERR_INVALID_CHANNEL_NAME; - goto error; - } - } else if (domain->type == LTTNG_DOMAIN_PYTHON) { - if (strncmp(attr.name, DEFAULT_PYTHON_CHANNEL_NAME, - LTTNG_SYMBOL_NAME_LEN)) { - ret = LTTNG_ERR_INVALID_CHANNEL_NAME; - goto error; - } - } - - chan_ht = usess->domain_global.channels; - - uchan = trace_ust_find_channel_by_name(chan_ht, attr.name); - if (uchan == NULL) { - /* - * Don't try to create a channel if the session has been started at - * some point in time before. The tracer does not allow it. - */ - if (session->has_been_started) { - ret = LTTNG_ERR_TRACE_ALREADY_STARTED; - goto error; - } - - ret = channel_ust_create(usess, &attr, domain->buf_type); - if (attr.name[0] != '\0') { - usess->has_non_default_channel = 1; - } - } else { - ret = channel_ust_enable(usess, uchan); - } - break; - } - default: - ret = LTTNG_ERR_UNKNOWN_DOMAIN; - goto error; - } - - if (ret == LTTNG_OK && attr.attr.output != LTTNG_EVENT_MMAP) { - session->has_non_mmap_channel = true; - } -error: - rcu_read_unlock(); -end: - return ret; -} - -enum lttng_error_code cmd_process_attr_tracker_get_tracking_policy( - struct ltt_session *session, - enum lttng_domain_type domain, - enum lttng_process_attr process_attr, - enum lttng_tracking_policy *policy) -{ - enum lttng_error_code ret_code = LTTNG_OK; - const struct process_attr_tracker *tracker; - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - if (!session->kernel_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - tracker = kernel_get_process_attr_tracker( - session->kernel_session, process_attr); - break; - case LTTNG_DOMAIN_UST: - if (!session->ust_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - tracker = trace_ust_get_process_attr_tracker( - session->ust_session, process_attr); - break; - default: - ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; - goto end; - } - if (tracker) { - *policy = process_attr_tracker_get_tracking_policy(tracker); - } else { - ret_code = LTTNG_ERR_INVALID; - } -end: - return ret_code; -} - -enum lttng_error_code cmd_process_attr_tracker_set_tracking_policy( - struct ltt_session *session, - enum lttng_domain_type domain, - enum lttng_process_attr process_attr, - enum lttng_tracking_policy policy) -{ - enum lttng_error_code ret_code = LTTNG_OK; - - switch (policy) { - case LTTNG_TRACKING_POLICY_INCLUDE_SET: - case LTTNG_TRACKING_POLICY_EXCLUDE_ALL: - case LTTNG_TRACKING_POLICY_INCLUDE_ALL: - break; - default: - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - if (!session->kernel_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - ret_code = kernel_process_attr_tracker_set_tracking_policy( - session->kernel_session, process_attr, policy); - break; - case LTTNG_DOMAIN_UST: - if (!session->ust_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - ret_code = trace_ust_process_attr_tracker_set_tracking_policy( - session->ust_session, process_attr, policy); - break; - default: - ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; - break; - } -end: - return ret_code; -} - -enum lttng_error_code cmd_process_attr_tracker_inclusion_set_add_value( - struct ltt_session *session, - enum lttng_domain_type domain, - enum lttng_process_attr process_attr, - const struct process_attr_value *value) -{ - enum lttng_error_code ret_code = LTTNG_OK; - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - if (!session->kernel_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - ret_code = kernel_process_attr_tracker_inclusion_set_add_value( - session->kernel_session, process_attr, value); - break; - case LTTNG_DOMAIN_UST: - if (!session->ust_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - ret_code = trace_ust_process_attr_tracker_inclusion_set_add_value( - session->ust_session, process_attr, value); - break; - default: - ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; - break; - } -end: - return ret_code; -} - -enum lttng_error_code cmd_process_attr_tracker_inclusion_set_remove_value( - struct ltt_session *session, - enum lttng_domain_type domain, - enum lttng_process_attr process_attr, - const struct process_attr_value *value) -{ - enum lttng_error_code ret_code = LTTNG_OK; - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - if (!session->kernel_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - ret_code = kernel_process_attr_tracker_inclusion_set_remove_value( - session->kernel_session, process_attr, value); - break; - case LTTNG_DOMAIN_UST: - if (!session->ust_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - ret_code = trace_ust_process_attr_tracker_inclusion_set_remove_value( - session->ust_session, process_attr, value); - break; - default: - ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; - break; - } -end: - return ret_code; -} - -enum lttng_error_code cmd_process_attr_tracker_get_inclusion_set( - struct ltt_session *session, - enum lttng_domain_type domain, - enum lttng_process_attr process_attr, - struct lttng_process_attr_values **values) -{ - enum lttng_error_code ret_code = LTTNG_OK; - const struct process_attr_tracker *tracker; - enum process_attr_tracker_status status; - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - if (!session->kernel_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - tracker = kernel_get_process_attr_tracker( - session->kernel_session, process_attr); - break; - case LTTNG_DOMAIN_UST: - if (!session->ust_session) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - tracker = trace_ust_get_process_attr_tracker( - session->ust_session, process_attr); - break; - default: - ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; - goto end; - } - - if (!tracker) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - status = process_attr_tracker_get_inclusion_set(tracker, values); - switch (status) { - case PROCESS_ATTR_TRACKER_STATUS_OK: - ret_code = LTTNG_OK; - break; - case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: - ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; - break; - case PROCESS_ATTR_TRACKER_STATUS_ERROR: - ret_code = LTTNG_ERR_NOMEM; - break; - default: - ret_code = LTTNG_ERR_UNK; - break; - } - -end: - return ret_code; -} - -/* - * Command LTTNG_DISABLE_EVENT processed by the client thread. - */ -int cmd_disable_event(struct ltt_session *session, - enum lttng_domain_type domain, const char *channel_name, - const struct lttng_event *event) -{ - int ret; - const char *event_name; - - DBG("Disable event command for event \'%s\'", event->name); - - event_name = event->name; - - /* Error out on unhandled search criteria */ - if (event->loglevel_type || event->loglevel != -1 || event->enabled - || event->pid || event->filter || event->exclusion) { - ret = LTTNG_ERR_UNK; - goto error; - } - - rcu_read_lock(); - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - { - struct ltt_kernel_channel *kchan; - struct ltt_kernel_session *ksess; - - ksess = session->kernel_session; - - /* - * If a non-default channel has been created in the - * session, explicitely require that -c chan_name needs - * to be provided. - */ - if (ksess->has_non_default_channel && channel_name[0] == '\0') { - ret = LTTNG_ERR_NEED_CHANNEL_NAME; - goto error_unlock; - } - - kchan = trace_kernel_get_channel_by_name(channel_name, ksess); - if (kchan == NULL) { - ret = LTTNG_ERR_KERN_CHAN_NOT_FOUND; - goto error_unlock; - } - - switch (event->type) { - case LTTNG_EVENT_ALL: - case LTTNG_EVENT_TRACEPOINT: - case LTTNG_EVENT_SYSCALL: - case LTTNG_EVENT_PROBE: - case LTTNG_EVENT_FUNCTION: - case LTTNG_EVENT_FUNCTION_ENTRY:/* fall-through */ - if (event_name[0] == '\0') { - ret = event_kernel_disable_event(kchan, - NULL, event->type); - } else { - ret = event_kernel_disable_event(kchan, - event_name, event->type); - } - if (ret != LTTNG_OK) { - goto error_unlock; - } - break; - default: - ret = LTTNG_ERR_UNK; - goto error_unlock; - } - - kernel_wait_quiescent(); - break; - } - case LTTNG_DOMAIN_UST: - { - struct ltt_ust_channel *uchan; - struct ltt_ust_session *usess; - - usess = session->ust_session; - - if (validate_ust_event_name(event_name)) { - ret = LTTNG_ERR_INVALID_EVENT_NAME; - goto error_unlock; - } - - /* - * If a non-default channel has been created in the - * session, explicitly require that -c chan_name needs - * to be provided. - */ - if (usess->has_non_default_channel && channel_name[0] == '\0') { - ret = LTTNG_ERR_NEED_CHANNEL_NAME; - goto error_unlock; - } - - uchan = trace_ust_find_channel_by_name(usess->domain_global.channels, - channel_name); - if (uchan == NULL) { - ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; - goto error_unlock; - } - - switch (event->type) { - case LTTNG_EVENT_ALL: - /* - * An empty event name means that everything - * should be disabled. - */ - if (event->name[0] == '\0') { - ret = event_ust_disable_all_tracepoints(usess, uchan); - } else { - ret = event_ust_disable_tracepoint(usess, uchan, - event_name); - } - if (ret != LTTNG_OK) { - goto error_unlock; - } - break; - default: - ret = LTTNG_ERR_UNK; - goto error_unlock; - } - - DBG3("Disable UST event %s in channel %s completed", event_name, - channel_name); - break; - } - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_PYTHON: - { - struct agent *agt; - struct ltt_ust_session *usess = session->ust_session; - - LTTNG_ASSERT(usess); - - switch (event->type) { - case LTTNG_EVENT_ALL: - break; - default: - ret = LTTNG_ERR_UNK; - goto error_unlock; - } - - agt = trace_ust_find_agent(usess, domain); - if (!agt) { - ret = -LTTNG_ERR_UST_EVENT_NOT_FOUND; - goto error_unlock; - } - /* - * An empty event name means that everything - * should be disabled. - */ - if (event->name[0] == '\0') { - ret = event_agent_disable_all(usess, agt); - } else { - ret = event_agent_disable(usess, agt, event_name); - } - if (ret != LTTNG_OK) { - goto error_unlock; - } - - break; - } - default: - ret = LTTNG_ERR_UND; - goto error_unlock; - } - - ret = LTTNG_OK; - -error_unlock: - rcu_read_unlock(); -error: - return ret; -} - -/* - * Command LTTNG_ADD_CONTEXT processed by the client thread. - */ -int cmd_add_context(struct ltt_session *session, enum lttng_domain_type domain, - char *channel_name, const struct lttng_event_context *ctx, int kwpipe) -{ - int ret, chan_kern_created = 0, chan_ust_created = 0; - char *app_ctx_provider_name = NULL, *app_ctx_name = NULL; - - /* - * Don't try to add a context if the session has been started at - * some point in time before. The tracer does not allow it and would - * result in a corrupted trace. - */ - if (session->has_been_started) { - ret = LTTNG_ERR_TRACE_ALREADY_STARTED; - goto end; - } - - if (ctx->ctx == LTTNG_EVENT_CONTEXT_APP_CONTEXT) { - app_ctx_provider_name = ctx->u.app_ctx.provider_name; - app_ctx_name = ctx->u.app_ctx.ctx_name; - } - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - LTTNG_ASSERT(session->kernel_session); - - if (session->kernel_session->channel_count == 0) { - /* Create default channel */ - ret = channel_kernel_create(session->kernel_session, NULL, kwpipe); - if (ret != LTTNG_OK) { - goto error; - } - chan_kern_created = 1; - } - /* Add kernel context to kernel tracer */ - ret = context_kernel_add(session->kernel_session, ctx, channel_name); - if (ret != LTTNG_OK) { - goto error; - } - break; - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - { - /* - * Validate channel name. - * If no channel name is given and the domain is JUL or LOG4J, - * set it to the appropriate domain-specific channel name. If - * a name is provided but does not match the expexted channel - * name, return an error. - */ - if (domain == LTTNG_DOMAIN_JUL && *channel_name && - strcmp(channel_name, - DEFAULT_JUL_CHANNEL_NAME)) { - ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; - goto error; - } else if (domain == LTTNG_DOMAIN_LOG4J && *channel_name && - strcmp(channel_name, - DEFAULT_LOG4J_CHANNEL_NAME)) { - ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; - goto error; - } - /* break is _not_ missing here. */ - } - case LTTNG_DOMAIN_UST: - { - struct ltt_ust_session *usess = session->ust_session; - unsigned int chan_count; - - LTTNG_ASSERT(usess); - - chan_count = lttng_ht_get_count(usess->domain_global.channels); - if (chan_count == 0) { - struct lttng_channel *attr; - /* Create default channel */ - attr = channel_new_default_attr(domain, usess->buffer_type); - if (attr == NULL) { - ret = LTTNG_ERR_FATAL; - goto error; - } - - ret = channel_ust_create(usess, attr, usess->buffer_type); - if (ret != LTTNG_OK) { - free(attr); - goto error; - } - channel_attr_destroy(attr); - chan_ust_created = 1; - } - - ret = context_ust_add(usess, domain, ctx, channel_name); - free(app_ctx_provider_name); - free(app_ctx_name); - app_ctx_name = NULL; - app_ctx_provider_name = NULL; - if (ret != LTTNG_OK) { - goto error; - } - break; - } - default: - ret = LTTNG_ERR_UND; - goto error; - } - - ret = LTTNG_OK; - goto end; - -error: - if (chan_kern_created) { - struct ltt_kernel_channel *kchan = - trace_kernel_get_channel_by_name(DEFAULT_CHANNEL_NAME, - session->kernel_session); - /* Created previously, this should NOT fail. */ - LTTNG_ASSERT(kchan); - kernel_destroy_channel(kchan); - } - - if (chan_ust_created) { - struct ltt_ust_channel *uchan = - trace_ust_find_channel_by_name( - session->ust_session->domain_global.channels, - DEFAULT_CHANNEL_NAME); - /* Created previously, this should NOT fail. */ - LTTNG_ASSERT(uchan); - /* Remove from the channel list of the session. */ - trace_ust_delete_channel(session->ust_session->domain_global.channels, - uchan); - trace_ust_destroy_channel(uchan); - } -end: - free(app_ctx_provider_name); - free(app_ctx_name); - return ret; -} - -static inline bool name_starts_with(const char *name, const char *prefix) -{ - const size_t max_cmp_len = min(strlen(prefix), LTTNG_SYMBOL_NAME_LEN); - - return !strncmp(name, prefix, max_cmp_len); -} - -/* Perform userspace-specific event name validation */ -static int validate_ust_event_name(const char *name) -{ - int ret = 0; - - if (!name) { - ret = -1; - goto end; - } - - /* - * Check name against all internal UST event component namespaces used - * by the agents. - */ - if (name_starts_with(name, DEFAULT_JUL_EVENT_COMPONENT) || - name_starts_with(name, DEFAULT_LOG4J_EVENT_COMPONENT) || - name_starts_with(name, DEFAULT_PYTHON_EVENT_COMPONENT)) { - ret = -1; - } - -end: - return ret; -} - -/* - * Internal version of cmd_enable_event() with a supplemental - * "internal_event" flag which is used to enable internal events which should - * be hidden from clients. Such events are used in the agent implementation to - * enable the events through which all "agent" events are funeled. - */ -static int _cmd_enable_event(struct ltt_session *session, - const struct lttng_domain *domain, - char *channel_name, struct lttng_event *event, - char *filter_expression, - struct lttng_bytecode *filter, - struct lttng_event_exclusion *exclusion, - int wpipe, bool internal_event) -{ - int ret = 0, channel_created = 0; - struct lttng_channel *attr = NULL; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(event); - LTTNG_ASSERT(channel_name); - - /* If we have a filter, we must have its filter expression */ - LTTNG_ASSERT(!(!!filter_expression ^ !!filter)); - - /* Normalize event name as a globbing pattern */ - strutils_normalize_star_glob_pattern(event->name); - - /* Normalize exclusion names as globbing patterns */ - if (exclusion) { - size_t i; - - for (i = 0; i < exclusion->count; i++) { - char *name = LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, i); - - strutils_normalize_star_glob_pattern(name); - } - } - - DBG("Enable event command for event \'%s\'", event->name); - - rcu_read_lock(); - - switch (domain->type) { - case LTTNG_DOMAIN_KERNEL: - { - struct ltt_kernel_channel *kchan; - - /* - * If a non-default channel has been created in the - * session, explicitely require that -c chan_name needs - * to be provided. - */ - if (session->kernel_session->has_non_default_channel - && channel_name[0] == '\0') { - ret = LTTNG_ERR_NEED_CHANNEL_NAME; - goto error; - } - - kchan = trace_kernel_get_channel_by_name(channel_name, - session->kernel_session); - if (kchan == NULL) { - attr = channel_new_default_attr(LTTNG_DOMAIN_KERNEL, - LTTNG_BUFFER_GLOBAL); - if (attr == NULL) { - ret = LTTNG_ERR_FATAL; - goto error; - } - if (lttng_strncpy(attr->name, channel_name, - sizeof(attr->name))) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - ret = cmd_enable_channel(session, domain, attr, wpipe); - if (ret != LTTNG_OK) { - goto error; - } - channel_created = 1; - } - - /* Get the newly created kernel channel pointer */ - kchan = trace_kernel_get_channel_by_name(channel_name, - session->kernel_session); - if (kchan == NULL) { - /* This sould not happen... */ - ret = LTTNG_ERR_FATAL; - goto error; - } - - switch (event->type) { - case LTTNG_EVENT_ALL: - { - char *filter_expression_a = NULL; - struct lttng_bytecode *filter_a = NULL; - - /* - * We need to duplicate filter_expression and filter, - * because ownership is passed to first enable - * event. - */ - if (filter_expression) { - filter_expression_a = strdup(filter_expression); - if (!filter_expression_a) { - ret = LTTNG_ERR_FATAL; - goto error; - } - } - if (filter) { - filter_a = zmalloc(sizeof(*filter_a) + filter->len); - if (!filter_a) { - free(filter_expression_a); - ret = LTTNG_ERR_FATAL; - goto error; - } - memcpy(filter_a, filter, sizeof(*filter_a) + filter->len); - } - event->type = LTTNG_EVENT_TRACEPOINT; /* Hack */ - ret = event_kernel_enable_event(kchan, event, - filter_expression, filter); - /* We have passed ownership */ - filter_expression = NULL; - filter = NULL; - if (ret != LTTNG_OK) { - if (channel_created) { - /* Let's not leak a useless channel. */ - kernel_destroy_channel(kchan); - } - free(filter_expression_a); - free(filter_a); - goto error; - } - event->type = LTTNG_EVENT_SYSCALL; /* Hack */ - ret = event_kernel_enable_event(kchan, event, - filter_expression_a, filter_a); - /* We have passed ownership */ - filter_expression_a = NULL; - filter_a = NULL; - if (ret != LTTNG_OK) { - goto error; - } - break; - } - case LTTNG_EVENT_PROBE: - case LTTNG_EVENT_USERSPACE_PROBE: - case LTTNG_EVENT_FUNCTION: - case LTTNG_EVENT_FUNCTION_ENTRY: - case LTTNG_EVENT_TRACEPOINT: - ret = event_kernel_enable_event(kchan, event, - filter_expression, filter); - /* We have passed ownership */ - filter_expression = NULL; - filter = NULL; - if (ret != LTTNG_OK) { - if (channel_created) { - /* Let's not leak a useless channel. */ - kernel_destroy_channel(kchan); - } - goto error; - } - break; - case LTTNG_EVENT_SYSCALL: - ret = event_kernel_enable_event(kchan, event, - filter_expression, filter); - /* We have passed ownership */ - filter_expression = NULL; - filter = NULL; - if (ret != LTTNG_OK) { - goto error; - } - break; - default: - ret = LTTNG_ERR_UNK; - goto error; - } - - kernel_wait_quiescent(); - break; - } - case LTTNG_DOMAIN_UST: - { - struct ltt_ust_channel *uchan; - struct ltt_ust_session *usess = session->ust_session; - - LTTNG_ASSERT(usess); - - /* - * If a non-default channel has been created in the - * session, explicitely require that -c chan_name needs - * to be provided. - */ - if (usess->has_non_default_channel && channel_name[0] == '\0') { - ret = LTTNG_ERR_NEED_CHANNEL_NAME; - goto error; - } - - /* Get channel from global UST domain */ - uchan = trace_ust_find_channel_by_name(usess->domain_global.channels, - channel_name); - if (uchan == NULL) { - /* Create default channel */ - attr = channel_new_default_attr(LTTNG_DOMAIN_UST, - usess->buffer_type); - if (attr == NULL) { - ret = LTTNG_ERR_FATAL; - goto error; - } - if (lttng_strncpy(attr->name, channel_name, - sizeof(attr->name))) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - ret = cmd_enable_channel(session, domain, attr, wpipe); - if (ret != LTTNG_OK) { - goto error; - } - - /* Get the newly created channel reference back */ - uchan = trace_ust_find_channel_by_name( - usess->domain_global.channels, channel_name); - LTTNG_ASSERT(uchan); - } - - if (uchan->domain != LTTNG_DOMAIN_UST && !internal_event) { - /* - * Don't allow users to add UST events to channels which - * are assigned to a userspace subdomain (JUL, Log4J, - * Python, etc.). - */ - ret = LTTNG_ERR_INVALID_CHANNEL_DOMAIN; - goto error; - } - - if (!internal_event) { - /* - * Ensure the event name is not reserved for internal - * use. - */ - ret = validate_ust_event_name(event->name); - if (ret) { - WARN("Userspace event name %s failed validation.", - event->name); - ret = LTTNG_ERR_INVALID_EVENT_NAME; - goto error; - } - } - - /* At this point, the session and channel exist on the tracer */ - ret = event_ust_enable_tracepoint(usess, uchan, event, - filter_expression, filter, exclusion, - internal_event); - /* We have passed ownership */ - filter_expression = NULL; - filter = NULL; - exclusion = NULL; - if (ret == LTTNG_ERR_UST_EVENT_ENABLED) { - goto already_enabled; - } else if (ret != LTTNG_OK) { - goto error; - } - break; - } - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_PYTHON: - { - const char *default_event_name, *default_chan_name; - struct agent *agt; - struct lttng_event uevent; - struct lttng_domain tmp_dom; - struct ltt_ust_session *usess = session->ust_session; - - LTTNG_ASSERT(usess); - - if (!agent_tracing_is_enabled()) { - DBG("Attempted to enable an event in an agent domain but the agent thread is not running"); - ret = LTTNG_ERR_AGENT_TRACING_DISABLED; - goto error; - } - - agt = trace_ust_find_agent(usess, domain->type); - if (!agt) { - agt = agent_create(domain->type); - if (!agt) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - agent_add(agt, usess->agents); - } - - /* Create the default tracepoint. */ - memset(&uevent, 0, sizeof(uevent)); - uevent.type = LTTNG_EVENT_TRACEPOINT; - uevent.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; - default_event_name = event_get_default_agent_ust_name( - domain->type); - if (!default_event_name) { - ret = LTTNG_ERR_FATAL; - goto error; - } - strncpy(uevent.name, default_event_name, sizeof(uevent.name)); - uevent.name[sizeof(uevent.name) - 1] = '\0'; - - /* - * The domain type is changed because we are about to enable the - * default channel and event for the JUL domain that are hardcoded. - * This happens in the UST domain. - */ - memcpy(&tmp_dom, domain, sizeof(tmp_dom)); - tmp_dom.type = LTTNG_DOMAIN_UST; - - switch (domain->type) { - case LTTNG_DOMAIN_LOG4J: - default_chan_name = DEFAULT_LOG4J_CHANNEL_NAME; - break; - case LTTNG_DOMAIN_JUL: - default_chan_name = DEFAULT_JUL_CHANNEL_NAME; - break; - case LTTNG_DOMAIN_PYTHON: - default_chan_name = DEFAULT_PYTHON_CHANNEL_NAME; - break; - default: - /* The switch/case we are in makes this impossible */ - abort(); - } - - { - char *filter_expression_copy = NULL; - struct lttng_bytecode *filter_copy = NULL; - - if (filter) { - const size_t filter_size = sizeof( - struct lttng_bytecode) - + filter->len; - - filter_copy = zmalloc(filter_size); - if (!filter_copy) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - memcpy(filter_copy, filter, filter_size); - - filter_expression_copy = - strdup(filter_expression); - if (!filter_expression) { - ret = LTTNG_ERR_NOMEM; - } - - if (!filter_expression_copy || !filter_copy) { - free(filter_expression_copy); - free(filter_copy); - goto error; - } - } - - ret = cmd_enable_event_internal(session, &tmp_dom, - (char *) default_chan_name, - &uevent, filter_expression_copy, - filter_copy, NULL, wpipe); - } - - if (ret == LTTNG_ERR_UST_EVENT_ENABLED) { - goto already_enabled; - } else if (ret != LTTNG_OK) { - goto error; - } - - /* The wild card * means that everything should be enabled. */ - if (strncmp(event->name, "*", 1) == 0 && strlen(event->name) == 1) { - ret = event_agent_enable_all(usess, agt, event, filter, - filter_expression); - } else { - ret = event_agent_enable(usess, agt, event, filter, - filter_expression); - } - filter = NULL; - filter_expression = NULL; - if (ret != LTTNG_OK) { - goto error; - } - - break; - } - default: - ret = LTTNG_ERR_UND; - goto error; - } - - ret = LTTNG_OK; - -already_enabled: -error: - free(filter_expression); - free(filter); - free(exclusion); - channel_attr_destroy(attr); - rcu_read_unlock(); - return ret; -} - -/* - * Command LTTNG_ENABLE_EVENT processed by the client thread. - * We own filter, exclusion, and filter_expression. - */ -int cmd_enable_event(struct ltt_session *session, - const struct lttng_domain *domain, - char *channel_name, struct lttng_event *event, - char *filter_expression, - struct lttng_bytecode *filter, - struct lttng_event_exclusion *exclusion, - int wpipe) -{ - return _cmd_enable_event(session, domain, channel_name, event, - filter_expression, filter, exclusion, wpipe, false); -} - -/* - * Enable an event which is internal to LTTng. An internal should - * never be made visible to clients and are immune to checks such as - * reserved names. - */ -static int cmd_enable_event_internal(struct ltt_session *session, - const struct lttng_domain *domain, - char *channel_name, struct lttng_event *event, - char *filter_expression, - struct lttng_bytecode *filter, - struct lttng_event_exclusion *exclusion, - int wpipe) -{ - return _cmd_enable_event(session, domain, channel_name, event, - filter_expression, filter, exclusion, wpipe, true); -} - -/* - * Command LTTNG_LIST_TRACEPOINTS processed by the client thread. - */ -ssize_t cmd_list_tracepoints(enum lttng_domain_type domain, - struct lttng_event **events) -{ - int ret; - ssize_t nb_events = 0; - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - nb_events = kernel_list_events(events); - if (nb_events < 0) { - ret = LTTNG_ERR_KERN_LIST_FAIL; - goto error; - } - break; - case LTTNG_DOMAIN_UST: - nb_events = ust_app_list_events(events); - if (nb_events < 0) { - ret = LTTNG_ERR_UST_LIST_FAIL; - goto error; - } - break; - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_PYTHON: - nb_events = agent_list_events(events, domain); - if (nb_events < 0) { - ret = LTTNG_ERR_UST_LIST_FAIL; - goto error; - } - break; - default: - ret = LTTNG_ERR_UND; - goto error; - } - - return nb_events; - -error: - /* Return negative value to differentiate return code */ - return -ret; -} - -/* - * Command LTTNG_LIST_TRACEPOINT_FIELDS processed by the client thread. - */ -ssize_t cmd_list_tracepoint_fields(enum lttng_domain_type domain, - struct lttng_event_field **fields) -{ - int ret; - ssize_t nb_fields = 0; - - switch (domain) { - case LTTNG_DOMAIN_UST: - nb_fields = ust_app_list_event_fields(fields); - if (nb_fields < 0) { - ret = LTTNG_ERR_UST_LIST_FAIL; - goto error; - } - break; - case LTTNG_DOMAIN_KERNEL: - default: /* fall-through */ - ret = LTTNG_ERR_UND; - goto error; - } - - return nb_fields; - -error: - /* Return negative value to differentiate return code */ - return -ret; -} - -ssize_t cmd_list_syscalls(struct lttng_event **events) -{ - return syscall_table_list(events); -} - -/* - * Command LTTNG_START_TRACE processed by the client thread. - * - * Called with session mutex held. - */ -int cmd_start_trace(struct ltt_session *session) -{ - enum lttng_error_code ret; - unsigned long nb_chan = 0; - struct ltt_kernel_session *ksession; - struct ltt_ust_session *usess; - const bool session_rotated_after_last_stop = - session->rotated_after_last_stop; - const bool session_cleared_after_last_stop = - session->cleared_after_last_stop; - - LTTNG_ASSERT(session); - - /* Ease our life a bit ;) */ - ksession = session->kernel_session; - usess = session->ust_session; - - /* Is the session already started? */ - if (session->active) { - ret = LTTNG_ERR_TRACE_ALREADY_STARTED; - /* Perform nothing */ - goto end; - } - - if (session->rotation_state == LTTNG_ROTATION_STATE_ONGOING && - !session->current_trace_chunk) { - /* - * A rotation was launched while the session was stopped and - * it has not been completed yet. It is not possible to start - * the session since starting the session here would require a - * rotation from "NULL" to a new trace chunk. That rotation - * would overlap with the ongoing rotation, which is not - * supported. - */ - WARN("Refusing to start session \"%s\" as a rotation launched after the last \"stop\" is still ongoing", - session->name); - ret = LTTNG_ERR_ROTATION_PENDING; - goto error; - } - - /* - * Starting a session without channel is useless since after that it's not - * possible to enable channel thus inform the client. - */ - if (usess && usess->domain_global.channels) { - nb_chan += lttng_ht_get_count(usess->domain_global.channels); - } - if (ksession) { - nb_chan += ksession->channel_count; - } - if (!nb_chan) { - ret = LTTNG_ERR_NO_CHANNEL; - goto error; - } - - session->active = 1; - session->rotated_after_last_stop = false; - session->cleared_after_last_stop = false; - if (session->output_traces && !session->current_trace_chunk) { - if (!session->has_been_started) { - struct lttng_trace_chunk *trace_chunk; - - DBG("Creating initial trace chunk of session \"%s\"", - session->name); - trace_chunk = session_create_new_trace_chunk( - session, NULL, NULL, NULL); - if (!trace_chunk) { - ret = LTTNG_ERR_CREATE_DIR_FAIL; - goto error; - } - LTTNG_ASSERT(!session->current_trace_chunk); - ret = session_set_trace_chunk(session, trace_chunk, - NULL); - lttng_trace_chunk_put(trace_chunk); - if (ret) { - ret = LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; - goto error; - } - } else { - DBG("Rotating session \"%s\" from its current \"NULL\" trace chunk to a new chunk", - session->name); - /* - * Rotate existing streams into the new chunk. - * This is a "quiet" rotation has no client has - * explicitly requested this operation. - * - * There is also no need to wait for the rotation - * to complete as it will happen immediately. No data - * was produced as the session was stopped, so the - * rotation should happen on reception of the command. - */ - ret = cmd_rotate_session(session, NULL, true, - LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION); - if (ret != LTTNG_OK) { - goto error; - } - } - } - - /* Kernel tracing */ - if (ksession != NULL) { - DBG("Start kernel tracing session %s", session->name); - ret = start_kernel_session(ksession); - if (ret != LTTNG_OK) { - goto error; - } - } - - /* Flag session that trace should start automatically */ - if (usess) { - int int_ret = ust_app_start_trace_all(usess); - - if (int_ret < 0) { - ret = LTTNG_ERR_UST_START_FAIL; - goto error; - } - } - - /* - * Open a packet in every stream of the session to ensure that viewers - * can correctly identify the boundaries of the periods during which - * tracing was active for this session. - */ - ret = session_open_packets(session); - if (ret != LTTNG_OK) { - goto error; - } - - /* - * Clear the flag that indicates that a rotation was done while the - * session was stopped. - */ - session->rotated_after_last_stop = false; - - if (session->rotate_timer_period) { - int int_ret = timer_session_rotation_schedule_timer_start( - session, session->rotate_timer_period); - - if (int_ret < 0) { - ERR("Failed to enable rotate timer"); - ret = LTTNG_ERR_UNK; - goto error; - } - } - - ret = LTTNG_OK; - -error: - if (ret == LTTNG_OK) { - /* Flag this after a successful start. */ - session->has_been_started |= 1; - } else { - session->active = 0; - /* Restore initial state on error. */ - session->rotated_after_last_stop = - session_rotated_after_last_stop; - session->cleared_after_last_stop = - session_cleared_after_last_stop; - } -end: - return ret; -} - -/* - * Command LTTNG_STOP_TRACE processed by the client thread. - */ -int cmd_stop_trace(struct ltt_session *session) -{ - int ret; - struct ltt_kernel_session *ksession; - struct ltt_ust_session *usess; - - LTTNG_ASSERT(session); - - DBG("Begin stop session \"%s\" (id %" PRIu64 ")", session->name, session->id); - /* Short cut */ - ksession = session->kernel_session; - usess = session->ust_session; - - /* Session is not active. Skip everythong and inform the client. */ - if (!session->active) { - ret = LTTNG_ERR_TRACE_ALREADY_STOPPED; - goto error; - } - - ret = stop_kernel_session(ksession); - if (ret != LTTNG_OK) { - goto error; - } - - if (usess && usess->active) { - ret = ust_app_stop_trace_all(usess); - if (ret < 0) { - ret = LTTNG_ERR_UST_STOP_FAIL; - goto error; - } - } - - DBG("Completed stop session \"%s\" (id %" PRIu64 ")", session->name, - session->id); - /* Flag inactive after a successful stop. */ - session->active = 0; - ret = LTTNG_OK; - -error: - return ret; -} - -/* - * Set the base_path of the session only if subdir of a control uris is set. - * Return LTTNG_OK on success, otherwise LTTNG_ERR_*. - */ -static int set_session_base_path_from_uris(struct ltt_session *session, - size_t nb_uri, - struct lttng_uri *uris) -{ - int ret; - size_t i; - - for (i = 0; i < nb_uri; i++) { - if (uris[i].stype != LTTNG_STREAM_CONTROL || - uris[i].subdir[0] == '\0') { - /* Not interested in these URIs */ - continue; - } - - if (session->base_path != NULL) { - free(session->base_path); - session->base_path = NULL; - } - - /* Set session base_path */ - session->base_path = strdup(uris[i].subdir); - if (!session->base_path) { - PERROR("Failed to copy base path \"%s\" to session \"%s\"", - uris[i].subdir, session->name); - ret = LTTNG_ERR_NOMEM; - goto error; - } - DBG2("Setting base path \"%s\" for session \"%s\"", - session->base_path, session->name); - } - ret = LTTNG_OK; -error: - return ret; -} - -/* - * Command LTTNG_SET_CONSUMER_URI processed by the client thread. - */ -int cmd_set_consumer_uri(struct ltt_session *session, size_t nb_uri, - struct lttng_uri *uris) -{ - int ret, i; - struct ltt_kernel_session *ksess = session->kernel_session; - struct ltt_ust_session *usess = session->ust_session; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(uris); - LTTNG_ASSERT(nb_uri > 0); - - /* Can't set consumer URI if the session is active. */ - if (session->active) { - ret = LTTNG_ERR_TRACE_ALREADY_STARTED; - goto error; - } - - /* - * Set the session base path if any. This is done inside - * cmd_set_consumer_uri to preserve backward compatibility of the - * previous session creation api vs the session descriptor api. - */ - ret = set_session_base_path_from_uris(session, nb_uri, uris); - if (ret != LTTNG_OK) { - goto error; - } - - /* Set the "global" consumer URIs */ - for (i = 0; i < nb_uri; i++) { - ret = add_uri_to_consumer(session, session->consumer, &uris[i], - LTTNG_DOMAIN_NONE); - if (ret != LTTNG_OK) { - goto error; - } - } - - /* Set UST session URIs */ - if (session->ust_session) { - for (i = 0; i < nb_uri; i++) { - ret = add_uri_to_consumer(session, - session->ust_session->consumer, - &uris[i], LTTNG_DOMAIN_UST); - if (ret != LTTNG_OK) { - goto error; - } - } - } - - /* Set kernel session URIs */ - if (session->kernel_session) { - for (i = 0; i < nb_uri; i++) { - ret = add_uri_to_consumer(session, - session->kernel_session->consumer, - &uris[i], LTTNG_DOMAIN_KERNEL); - if (ret != LTTNG_OK) { - goto error; - } - } - } - - /* - * Make sure to set the session in output mode after we set URI since a - * session can be created without URL (thus flagged in no output mode). - */ - session->output_traces = 1; - if (ksess) { - ksess->output_traces = 1; - } - - if (usess) { - usess->output_traces = 1; - } - - /* All good! */ - ret = LTTNG_OK; - -error: - return ret; -} - -static -enum lttng_error_code set_session_output_from_descriptor( - struct ltt_session *session, - const struct lttng_session_descriptor *descriptor) -{ - int ret; - enum lttng_error_code ret_code = LTTNG_OK; - enum lttng_session_descriptor_type session_type = - lttng_session_descriptor_get_type(descriptor); - enum lttng_session_descriptor_output_type output_type = - lttng_session_descriptor_get_output_type(descriptor); - struct lttng_uri uris[2] = {}; - size_t uri_count = 0; - - switch (output_type) { - case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE: - goto end; - case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL: - lttng_session_descriptor_get_local_output_uri(descriptor, - &uris[0]); - uri_count = 1; - break; - case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK: - lttng_session_descriptor_get_network_output_uris(descriptor, - &uris[0], &uris[1]); - uri_count = 2; - break; - default: - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - switch (session_type) { - case LTTNG_SESSION_DESCRIPTOR_TYPE_SNAPSHOT: - { - struct snapshot_output *new_output = NULL; - - new_output = snapshot_output_alloc(); - if (!new_output) { - ret_code = LTTNG_ERR_NOMEM; - goto end; - } - - ret = snapshot_output_init_with_uri(session, - DEFAULT_SNAPSHOT_MAX_SIZE, - NULL, uris, uri_count, session->consumer, - new_output, &session->snapshot); - if (ret < 0) { - ret_code = (ret == -ENOMEM) ? - LTTNG_ERR_NOMEM : LTTNG_ERR_INVALID; - snapshot_output_destroy(new_output); - goto end; - } - snapshot_add_output(&session->snapshot, new_output); - break; - } - case LTTNG_SESSION_DESCRIPTOR_TYPE_REGULAR: - case LTTNG_SESSION_DESCRIPTOR_TYPE_LIVE: - { - ret_code = cmd_set_consumer_uri(session, uri_count, uris); - break; - } - default: - ret_code = LTTNG_ERR_INVALID; - goto end; - } -end: - return ret_code; -} - -static -enum lttng_error_code cmd_create_session_from_descriptor( - struct lttng_session_descriptor *descriptor, - const lttng_sock_cred *creds, - const char *home_path) -{ - int ret; - enum lttng_error_code ret_code; - const char *session_name; - struct ltt_session *new_session = NULL; - enum lttng_session_descriptor_status descriptor_status; - - session_lock_list(); - if (home_path) { - if (*home_path != '/') { - ERR("Home path provided by client is not absolute"); - ret_code = LTTNG_ERR_INVALID; - goto end; - } - } - - descriptor_status = lttng_session_descriptor_get_session_name( - descriptor, &session_name); - switch (descriptor_status) { - case LTTNG_SESSION_DESCRIPTOR_STATUS_OK: - break; - case LTTNG_SESSION_DESCRIPTOR_STATUS_UNSET: - session_name = NULL; - break; - default: - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - ret_code = session_create(session_name, creds->uid, creds->gid, - &new_session); - if (ret_code != LTTNG_OK) { - goto end; - } - - if (!session_name) { - ret = lttng_session_descriptor_set_session_name(descriptor, - new_session->name); - if (ret) { - ret_code = LTTNG_ERR_SESSION_FAIL; - goto end; - } - } - - if (!lttng_session_descriptor_is_output_destination_initialized( - descriptor)) { - /* - * Only include the session's creation time in the output - * destination if the name of the session itself was - * not auto-generated. - */ - ret_code = lttng_session_descriptor_set_default_output( - descriptor, - session_name ? &new_session->creation_time : NULL, - home_path); - if (ret_code != LTTNG_OK) { - goto end; - } - } else { - new_session->has_user_specified_directory = - lttng_session_descriptor_has_output_directory( - descriptor); - } - - switch (lttng_session_descriptor_get_type(descriptor)) { - case LTTNG_SESSION_DESCRIPTOR_TYPE_SNAPSHOT: - new_session->snapshot_mode = 1; - break; - case LTTNG_SESSION_DESCRIPTOR_TYPE_LIVE: - new_session->live_timer = - lttng_session_descriptor_live_get_timer_interval( - descriptor); - break; - default: - break; - } - - ret_code = set_session_output_from_descriptor(new_session, descriptor); - if (ret_code != LTTNG_OK) { - goto end; - } - new_session->consumer->enabled = 1; - ret_code = LTTNG_OK; -end: - /* Release reference provided by the session_create function. */ - session_put(new_session); - if (ret_code != LTTNG_OK && new_session) { - /* Release the global reference on error. */ - session_destroy(new_session); - } - session_unlock_list(); - return ret_code; -} - -enum lttng_error_code cmd_create_session(struct command_ctx *cmd_ctx, int sock, - struct lttng_session_descriptor **return_descriptor) -{ - int ret; - size_t payload_size; - struct lttng_dynamic_buffer payload; - struct lttng_buffer_view home_dir_view; - struct lttng_buffer_view session_descriptor_view; - struct lttng_session_descriptor *session_descriptor = NULL; - enum lttng_error_code ret_code; - - lttng_dynamic_buffer_init(&payload); - if (cmd_ctx->lsm.u.create_session.home_dir_size >= - LTTNG_PATH_MAX) { - ret_code = LTTNG_ERR_INVALID; - goto error; - } - if (cmd_ctx->lsm.u.create_session.session_descriptor_size > - LTTNG_SESSION_DESCRIPTOR_MAX_LEN) { - ret_code = LTTNG_ERR_INVALID; - goto error; - } - - payload_size = cmd_ctx->lsm.u.create_session.home_dir_size + - cmd_ctx->lsm.u.create_session.session_descriptor_size; - ret = lttng_dynamic_buffer_set_size(&payload, payload_size); - if (ret) { - ret_code = LTTNG_ERR_NOMEM; - goto error; - } - - ret = lttcomm_recv_unix_sock(sock, payload.data, payload.size); - if (ret <= 0) { - ERR("Reception of session descriptor failed, aborting."); - ret_code = LTTNG_ERR_SESSION_FAIL; - goto error; - } - - home_dir_view = lttng_buffer_view_from_dynamic_buffer( - &payload, - 0, - cmd_ctx->lsm.u.create_session.home_dir_size); - if (cmd_ctx->lsm.u.create_session.home_dir_size > 0 && - !lttng_buffer_view_is_valid(&home_dir_view)) { - ERR("Invalid payload in \"create session\" command: buffer too short to contain home directory"); - ret_code = LTTNG_ERR_INVALID_PROTOCOL; - goto error; - } - - session_descriptor_view = lttng_buffer_view_from_dynamic_buffer( - &payload, - cmd_ctx->lsm.u.create_session.home_dir_size, - cmd_ctx->lsm.u.create_session.session_descriptor_size); - if (!lttng_buffer_view_is_valid(&session_descriptor_view)) { - ERR("Invalid payload in \"create session\" command: buffer too short to contain session descriptor"); - ret_code = LTTNG_ERR_INVALID_PROTOCOL; - goto error; - } - - ret = lttng_session_descriptor_create_from_buffer( - &session_descriptor_view, &session_descriptor); - if (ret < 0) { - ERR("Failed to create session descriptor from payload of \"create session\" command"); - ret_code = LTTNG_ERR_INVALID; - goto error; - } - - /* - * Sets the descriptor's auto-generated properties (name, output) if - * needed. - */ - ret_code = cmd_create_session_from_descriptor(session_descriptor, - &cmd_ctx->creds, - home_dir_view.size ? home_dir_view.data : NULL); - if (ret_code != LTTNG_OK) { - goto error; - } - - ret_code = LTTNG_OK; - *return_descriptor = session_descriptor; - session_descriptor = NULL; -error: - lttng_dynamic_buffer_reset(&payload); - lttng_session_descriptor_destroy(session_descriptor); - return ret_code; -} - -static -void cmd_destroy_session_reply(const struct ltt_session *session, - void *_reply_context) -{ - int ret; - ssize_t comm_ret; - const struct cmd_destroy_session_reply_context *reply_context = - _reply_context; - struct lttng_dynamic_buffer payload; - struct lttcomm_session_destroy_command_header cmd_header; - struct lttng_trace_archive_location *location = NULL; - struct lttcomm_lttng_msg llm = { - .cmd_type = LTTNG_DESTROY_SESSION, - .ret_code = reply_context->destruction_status, - .pid = UINT32_MAX, - .cmd_header_size = - sizeof(struct lttcomm_session_destroy_command_header), - .data_size = 0, - }; - size_t payload_size_before_location; - - lttng_dynamic_buffer_init(&payload); - - ret = lttng_dynamic_buffer_append(&payload, &llm, sizeof(llm)); - if (ret) { - ERR("Failed to append session destruction message"); - goto error; - } - - cmd_header.rotation_state = - (int32_t) (reply_context->implicit_rotation_on_destroy ? - session->rotation_state : - LTTNG_ROTATION_STATE_NO_ROTATION); - ret = lttng_dynamic_buffer_append(&payload, &cmd_header, - sizeof(cmd_header)); - if (ret) { - ERR("Failed to append session destruction command header"); - goto error; - } - - if (!reply_context->implicit_rotation_on_destroy) { - DBG("No implicit rotation performed during the destruction of session \"%s\", sending reply", - session->name); - goto send_reply; - } - if (session->rotation_state != LTTNG_ROTATION_STATE_COMPLETED) { - DBG("Rotation state of session \"%s\" is not \"completed\", sending session destruction reply", - session->name); - goto send_reply; - } - - location = session_get_trace_archive_location(session); - if (!location) { - ERR("Failed to get the location of the trace archive produced during the destruction of session \"%s\"", - session->name); - goto error; - } - - payload_size_before_location = payload.size; - comm_ret = lttng_trace_archive_location_serialize(location, - &payload); - lttng_trace_archive_location_put(location); - if (comm_ret < 0) { - ERR("Failed to serialize the location of the trace archive produced during the destruction of session \"%s\"", - session->name); - goto error; - } - /* Update the message to indicate the location's length. */ - ((struct lttcomm_lttng_msg *) payload.data)->data_size = - payload.size - payload_size_before_location; -send_reply: - comm_ret = lttcomm_send_unix_sock(reply_context->reply_sock_fd, - payload.data, payload.size); - if (comm_ret != (ssize_t) payload.size) { - ERR("Failed to send result of the destruction of session \"%s\" to client", - session->name); - } -error: - ret = close(reply_context->reply_sock_fd); - if (ret) { - PERROR("Failed to close client socket in deferred session destroy reply"); - } - lttng_dynamic_buffer_reset(&payload); - free(_reply_context); -} - -/* - * Command LTTNG_DESTROY_SESSION processed by the client thread. - * - * Called with session lock held. - */ -int cmd_destroy_session(struct ltt_session *session, - struct notification_thread_handle *notification_thread_handle, - int *sock_fd) -{ - int ret; - enum lttng_error_code destruction_last_error = LTTNG_OK; - struct cmd_destroy_session_reply_context *reply_context = NULL; - - if (sock_fd) { - reply_context = zmalloc(sizeof(*reply_context)); - if (!reply_context) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - reply_context->reply_sock_fd = *sock_fd; - } - - /* Safety net */ - LTTNG_ASSERT(session); - - DBG("Begin destroy session %s (id %" PRIu64 ")", session->name, - session->id); - if (session->active) { - DBG("Session \"%s\" is active, attempting to stop it before destroying it", - session->name); - ret = cmd_stop_trace(session); - if (ret != LTTNG_OK && ret != LTTNG_ERR_TRACE_ALREADY_STOPPED) { - /* Carry on with the destruction of the session. */ - ERR("Failed to stop session \"%s\" as part of its destruction: %s", - session->name, lttng_strerror(-ret)); - destruction_last_error = ret; - } - } - - if (session->rotation_schedule_timer_enabled) { - if (timer_session_rotation_schedule_timer_stop( - session)) { - ERR("Failed to stop the \"rotation schedule\" timer of session %s", - session->name); - destruction_last_error = LTTNG_ERR_TIMER_STOP_ERROR; - } - } - - if (session->rotate_size) { - unsubscribe_session_consumed_size_rotation(session, notification_thread_handle); - session->rotate_size = 0; - } - - if (session->rotated && session->current_trace_chunk && session->output_traces) { - /* - * Perform a last rotation on destruction if rotations have - * occurred during the session's lifetime. - */ - ret = cmd_rotate_session(session, NULL, false, - LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); - if (ret != LTTNG_OK) { - ERR("Failed to perform an implicit rotation as part of the destruction of session \"%s\": %s", - session->name, lttng_strerror(-ret)); - destruction_last_error = -ret; - } - if (reply_context) { - reply_context->implicit_rotation_on_destroy = true; - } - } else if (session->has_been_started && session->current_trace_chunk) { - /* - * The user has not triggered a session rotation. However, to - * ensure all data has been consumed, the session is rotated - * to a 'null' trace chunk before it is destroyed. - * - * This is a "quiet" rotation meaning that no notification is - * emitted and no renaming of the current trace chunk takes - * place. - */ - ret = cmd_rotate_session(session, NULL, true, - LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION); - /* - * Rotation operations may not be supported by the kernel - * tracer. Hence, do not consider this implicit rotation as - * a session destruction error. The library has already stopped - * the session and waited for pending data; there is nothing - * left to do but complete the destruction of the session. - */ - if (ret != LTTNG_OK && - ret != -LTTNG_ERR_ROTATION_NOT_AVAILABLE_KERNEL) { - ERR("Failed to perform a quiet rotation as part of the destruction of session \"%s\": %s", - session->name, lttng_strerror(ret)); - destruction_last_error = -ret; - } - } - - if (session->shm_path[0]) { - /* - * When a session is created with an explicit shm_path, - * the consumer daemon will create its shared memory files - * at that location and will *not* unlink them. This is normal - * as the intention of that feature is to make it possible - * to retrieve the content of those files should a crash occur. - * - * To ensure the content of those files can be used, the - * sessiond daemon will replicate the content of the metadata - * cache in a metadata file. - * - * On clean-up, it is expected that the consumer daemon will - * unlink the shared memory files and that the session daemon - * will unlink the metadata file. Then, the session's directory - * in the shm path can be removed. - * - * Unfortunately, a flaw in the design of the sessiond's and - * consumerd's tear down of channels makes it impossible to - * determine when the sessiond _and_ the consumerd have both - * destroyed their representation of a channel. For one, the - * unlinking, close, and rmdir happen in deferred 'call_rcu' - * callbacks in both daemons. - * - * However, it is also impossible for the sessiond to know when - * the consumer daemon is done destroying its channel(s) since - * it occurs as a reaction to the closing of the channel's file - * descriptor. There is no resulting communication initiated - * from the consumerd to the sessiond to confirm that the - * operation is completed (and was successful). - * - * Until this is all fixed, the session daemon checks for the - * removal of the session's shm path which makes it possible - * to safely advertise a session as having been destroyed. - * - * Prior to this fix, it was not possible to reliably save - * a session making use of the --shm-path option, destroy it, - * and load it again. This is because the creation of the - * session would fail upon seeing the session's shm path - * already in existence. - * - * Note that none of the error paths in the check for the - * directory's existence return an error. This is normal - * as there isn't much that can be done. The session will - * be destroyed properly, except that we can't offer the - * guarantee that the same session can be re-created. - */ - current_completion_handler = &destroy_completion_handler.handler; - ret = lttng_strncpy(destroy_completion_handler.shm_path, - session->shm_path, - sizeof(destroy_completion_handler.shm_path)); - LTTNG_ASSERT(!ret); - } - - /* - * The session is destroyed. However, note that the command context - * still holds a reference to the session, thus delaying its destruction - * _at least_ up to the point when that reference is released. - */ - session_destroy(session); - if (reply_context) { - reply_context->destruction_status = destruction_last_error; - ret = session_add_destroy_notifier(session, - cmd_destroy_session_reply, - (void *) reply_context); - if (ret) { - ret = LTTNG_ERR_FATAL; - goto end; - } else { - *sock_fd = -1; - } - } - ret = LTTNG_OK; -end: - return ret; -} - -/* - * Command LTTNG_REGISTER_CONSUMER processed by the client thread. - */ -int cmd_register_consumer(struct ltt_session *session, - enum lttng_domain_type domain, const char *sock_path, - struct consumer_data *cdata) -{ - int ret, sock; - struct consumer_socket *socket = NULL; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(cdata); - LTTNG_ASSERT(sock_path); - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - { - struct ltt_kernel_session *ksess = session->kernel_session; - - LTTNG_ASSERT(ksess); - - /* Can't register a consumer if there is already one */ - if (ksess->consumer_fds_sent != 0) { - ret = LTTNG_ERR_KERN_CONSUMER_FAIL; - goto error; - } - - sock = lttcomm_connect_unix_sock(sock_path); - if (sock < 0) { - ret = LTTNG_ERR_CONNECT_FAIL; - goto error; - } - cdata->cmd_sock = sock; - - socket = consumer_allocate_socket(&cdata->cmd_sock); - if (socket == NULL) { - ret = close(sock); - if (ret < 0) { - PERROR("close register consumer"); - } - cdata->cmd_sock = -1; - ret = LTTNG_ERR_FATAL; - goto error; - } - - socket->lock = zmalloc(sizeof(pthread_mutex_t)); - if (socket->lock == NULL) { - PERROR("zmalloc pthread mutex"); - ret = LTTNG_ERR_FATAL; - goto error; - } - pthread_mutex_init(socket->lock, NULL); - socket->registered = 1; - - rcu_read_lock(); - consumer_add_socket(socket, ksess->consumer); - rcu_read_unlock(); - - pthread_mutex_lock(&cdata->pid_mutex); - cdata->pid = -1; - pthread_mutex_unlock(&cdata->pid_mutex); - - break; - } - default: - /* TODO: Userspace tracing */ - ret = LTTNG_ERR_UND; - goto error; - } - - return LTTNG_OK; - -error: - if (socket) { - consumer_destroy_socket(socket); - } - return ret; -} - -/* - * Command LTTNG_LIST_DOMAINS processed by the client thread. - */ -ssize_t cmd_list_domains(struct ltt_session *session, - struct lttng_domain **domains) -{ - int ret, index = 0; - ssize_t nb_dom = 0; - struct agent *agt; - struct lttng_ht_iter iter; - - if (session->kernel_session != NULL) { - DBG3("Listing domains found kernel domain"); - nb_dom++; - } - - if (session->ust_session != NULL) { - DBG3("Listing domains found UST global domain"); - nb_dom++; - - rcu_read_lock(); - cds_lfht_for_each_entry(session->ust_session->agents->ht, &iter.iter, - agt, node.node) { - if (agt->being_used) { - nb_dom++; - } - } - rcu_read_unlock(); - } - - if (!nb_dom) { - goto end; - } - - *domains = zmalloc(nb_dom * sizeof(struct lttng_domain)); - if (*domains == NULL) { - ret = LTTNG_ERR_FATAL; - goto error; - } - - if (session->kernel_session != NULL) { - (*domains)[index].type = LTTNG_DOMAIN_KERNEL; - - /* Kernel session buffer type is always GLOBAL */ - (*domains)[index].buf_type = LTTNG_BUFFER_GLOBAL; - - index++; - } - - if (session->ust_session != NULL) { - (*domains)[index].type = LTTNG_DOMAIN_UST; - (*domains)[index].buf_type = session->ust_session->buffer_type; - index++; - - rcu_read_lock(); - cds_lfht_for_each_entry(session->ust_session->agents->ht, &iter.iter, - agt, node.node) { - if (agt->being_used) { - (*domains)[index].type = agt->domain; - (*domains)[index].buf_type = session->ust_session->buffer_type; - index++; - } - } - rcu_read_unlock(); - } -end: - return nb_dom; - -error: - /* Return negative value to differentiate return code */ - return -ret; -} - - -/* - * Command LTTNG_LIST_CHANNELS processed by the client thread. - */ -ssize_t cmd_list_channels(enum lttng_domain_type domain, - struct ltt_session *session, struct lttng_channel **channels) -{ - ssize_t nb_chan = 0, payload_size = 0, ret; - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - if (session->kernel_session != NULL) { - nb_chan = session->kernel_session->channel_count; - } - DBG3("Number of kernel channels %zd", nb_chan); - if (nb_chan <= 0) { - ret = -LTTNG_ERR_KERN_CHAN_NOT_FOUND; - goto end; - } - break; - case LTTNG_DOMAIN_UST: - if (session->ust_session != NULL) { - rcu_read_lock(); - nb_chan = lttng_ht_get_count( - session->ust_session->domain_global.channels); - rcu_read_unlock(); - } - DBG3("Number of UST global channels %zd", nb_chan); - if (nb_chan < 0) { - ret = -LTTNG_ERR_UST_CHAN_NOT_FOUND; - goto end; - } - break; - default: - ret = -LTTNG_ERR_UND; - goto end; - } - - if (nb_chan > 0) { - const size_t channel_size = sizeof(struct lttng_channel) + - sizeof(struct lttng_channel_extended); - struct lttng_channel_extended *channel_exts; - - payload_size = nb_chan * channel_size; - *channels = zmalloc(payload_size); - if (*channels == NULL) { - ret = -LTTNG_ERR_FATAL; - goto end; - } - - channel_exts = ((void *) *channels) + - (nb_chan * sizeof(struct lttng_channel)); - ret = list_lttng_channels(domain, session, *channels, channel_exts); - if (ret != LTTNG_OK) { - free(*channels); - *channels = NULL; - goto end; - } - } else { - *channels = NULL; - } - - ret = payload_size; -end: - return ret; -} - -/* - * Command LTTNG_LIST_EVENTS processed by the client thread. - */ -ssize_t cmd_list_events(enum lttng_domain_type domain, - struct ltt_session *session, char *channel_name, - struct lttng_payload *payload) -{ - int ret = 0; - ssize_t nb_events = 0; - struct lttcomm_event_command_header cmd_header = {}; - const size_t cmd_header_offset = payload->buffer.size; - - ret = lttng_dynamic_buffer_append( - &payload->buffer, &cmd_header, sizeof(cmd_header)); - if (ret) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - if (session->kernel_session != NULL) { - nb_events = list_lttng_kernel_events(channel_name, - session->kernel_session, payload); - } - break; - case LTTNG_DOMAIN_UST: - { - if (session->ust_session != NULL) { - nb_events = list_lttng_ust_global_events(channel_name, - &session->ust_session->domain_global, - payload); - } - break; - } - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_PYTHON: - if (session->ust_session) { - struct lttng_ht_iter iter; - struct agent *agt; - - rcu_read_lock(); - cds_lfht_for_each_entry(session->ust_session->agents->ht, - &iter.iter, agt, node.node) { - if (agt->domain == domain) { - nb_events = list_lttng_agent_events( - agt, payload); - break; - } - } - rcu_read_unlock(); - } - break; - default: - ret = LTTNG_ERR_UND; - goto error; - } - - ((struct lttcomm_event_command_header *) (payload->buffer.data + - cmd_header_offset))->nb_events = (uint32_t) nb_events; - - return nb_events; - -error: - /* Return negative value to differentiate return code */ - return -ret; -} - -/* - * Using the session list, filled a lttng_session array to send back to the - * client for session listing. - * - * The session list lock MUST be acquired before calling this function. Use - * session_lock_list() and session_unlock_list(). - */ -void cmd_list_lttng_sessions(struct lttng_session *sessions, - size_t session_count, uid_t uid, gid_t gid) -{ - int ret; - unsigned int i = 0; - struct ltt_session *session; - struct ltt_session_list *list = session_get_list(); - struct lttng_session_extended *extended = - (typeof(extended)) (&sessions[session_count]); - - DBG("Getting all available session for UID %d GID %d", - uid, gid); - /* - * Iterate over session list and append data after the control struct in - * the buffer. - */ - cds_list_for_each_entry(session, &list->head, list) { - if (!session_get(session)) { - continue; - } - /* - * Only list the sessions the user can control. - */ - if (!session_access_ok(session, uid) || - session->destroyed) { - session_put(session); - continue; - } - - struct ltt_kernel_session *ksess = session->kernel_session; - struct ltt_ust_session *usess = session->ust_session; - - if (session->consumer->type == CONSUMER_DST_NET || - (ksess && ksess->consumer->type == CONSUMER_DST_NET) || - (usess && usess->consumer->type == CONSUMER_DST_NET)) { - ret = build_network_session_path(sessions[i].path, - sizeof(sessions[i].path), session); - } else { - ret = snprintf(sessions[i].path, sizeof(sessions[i].path), "%s", - session->consumer->dst.session_root_path); - } - if (ret < 0) { - PERROR("snprintf session path"); - session_put(session); - continue; - } - - strncpy(sessions[i].name, session->name, NAME_MAX); - sessions[i].name[NAME_MAX - 1] = '\0'; - sessions[i].enabled = session->active; - sessions[i].snapshot_mode = session->snapshot_mode; - sessions[i].live_timer_interval = session->live_timer; - extended[i].creation_time.value = (uint64_t) session->creation_time; - extended[i].creation_time.is_set = 1; - i++; - session_put(session); - } -} - -/* - * Command LTTNG_DATA_PENDING returning 0 if the data is NOT pending meaning - * ready for trace analysis (or any kind of reader) or else 1 for pending data. - */ -int cmd_data_pending(struct ltt_session *session) -{ - int ret; - struct ltt_kernel_session *ksess = session->kernel_session; - struct ltt_ust_session *usess = session->ust_session; - - LTTNG_ASSERT(session); - - DBG("Data pending for session %s", session->name); - - /* Session MUST be stopped to ask for data availability. */ - if (session->active) { - ret = LTTNG_ERR_SESSION_STARTED; - goto error; - } else { - /* - * If stopped, just make sure we've started before else the above call - * will always send that there is data pending. - * - * The consumer assumes that when the data pending command is received, - * the trace has been started before or else no output data is written - * by the streams which is a condition for data pending. So, this is - * *VERY* important that we don't ask the consumer before a start - * trace. - */ - if (!session->has_been_started) { - ret = 0; - goto error; - } - } - - /* A rotation is still pending, we have to wait. */ - if (session->rotation_state == LTTNG_ROTATION_STATE_ONGOING) { - DBG("Rotate still pending for session %s", session->name); - ret = 1; - goto error; - } - - if (ksess && ksess->consumer) { - ret = consumer_is_data_pending(ksess->id, ksess->consumer); - if (ret == 1) { - /* Data is still being extracted for the kernel. */ - goto error; - } - } - - if (usess && usess->consumer) { - ret = consumer_is_data_pending(usess->id, usess->consumer); - if (ret == 1) { - /* Data is still being extracted for the kernel. */ - goto error; - } - } - - /* Data is ready to be read by a viewer */ - ret = 0; - -error: - return ret; -} - -/* - * Command LTTNG_SNAPSHOT_ADD_OUTPUT from the lttng ctl library. - * - * Return LTTNG_OK on success or else a LTTNG_ERR code. - */ -int cmd_snapshot_add_output(struct ltt_session *session, - const struct lttng_snapshot_output *output, uint32_t *id) -{ - int ret; - struct snapshot_output *new_output; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(output); - - DBG("Cmd snapshot add output for session %s", session->name); - - /* - * Can't create an output if the session is not set in no-output mode. - */ - if (session->output_traces) { - ret = LTTNG_ERR_NOT_SNAPSHOT_SESSION; - goto error; - } - - if (session->has_non_mmap_channel) { - ret = LTTNG_ERR_SNAPSHOT_UNSUPPORTED; - goto error; - } - - /* Only one output is allowed until we have the "tee" feature. */ - if (session->snapshot.nb_output == 1) { - ret = LTTNG_ERR_SNAPSHOT_OUTPUT_EXIST; - goto error; - } - - new_output = snapshot_output_alloc(); - if (!new_output) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - - ret = snapshot_output_init(session, output->max_size, output->name, - output->ctrl_url, output->data_url, session->consumer, new_output, - &session->snapshot); - if (ret < 0) { - if (ret == -ENOMEM) { - ret = LTTNG_ERR_NOMEM; - } else { - ret = LTTNG_ERR_INVALID; - } - goto free_error; - } - - rcu_read_lock(); - snapshot_add_output(&session->snapshot, new_output); - if (id) { - *id = new_output->id; - } - rcu_read_unlock(); - - return LTTNG_OK; - -free_error: - snapshot_output_destroy(new_output); -error: - return ret; -} - -/* - * Command LTTNG_SNAPSHOT_DEL_OUTPUT from lib lttng ctl. - * - * Return LTTNG_OK on success or else a LTTNG_ERR code. - */ -int cmd_snapshot_del_output(struct ltt_session *session, - const struct lttng_snapshot_output *output) -{ - int ret; - struct snapshot_output *sout = NULL; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(output); - - rcu_read_lock(); - - /* - * Permission denied to create an output if the session is not - * set in no output mode. - */ - if (session->output_traces) { - ret = LTTNG_ERR_NOT_SNAPSHOT_SESSION; - goto error; - } - - if (output->id) { - DBG("Cmd snapshot del output id %" PRIu32 " for session %s", output->id, - session->name); - sout = snapshot_find_output_by_id(output->id, &session->snapshot); - } else if (*output->name != '\0') { - DBG("Cmd snapshot del output name %s for session %s", output->name, - session->name); - sout = snapshot_find_output_by_name(output->name, &session->snapshot); - } - if (!sout) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - snapshot_delete_output(&session->snapshot, sout); - snapshot_output_destroy(sout); - ret = LTTNG_OK; - -error: - rcu_read_unlock(); - return ret; -} - -/* - * Command LTTNG_SNAPSHOT_LIST_OUTPUT from lib lttng ctl. - * - * If no output is available, outputs is untouched and 0 is returned. - * - * Return the size of the newly allocated outputs or a negative LTTNG_ERR code. - */ -ssize_t cmd_snapshot_list_outputs(struct ltt_session *session, - struct lttng_snapshot_output **outputs) -{ - int ret, idx = 0; - struct lttng_snapshot_output *list = NULL; - struct lttng_ht_iter iter; - struct snapshot_output *output; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(outputs); - - DBG("Cmd snapshot list outputs for session %s", session->name); - - /* - * Permission denied to create an output if the session is not - * set in no output mode. - */ - if (session->output_traces) { - ret = -LTTNG_ERR_NOT_SNAPSHOT_SESSION; - goto end; - } - - if (session->snapshot.nb_output == 0) { - ret = 0; - goto end; - } - - list = zmalloc(session->snapshot.nb_output * sizeof(*list)); - if (!list) { - ret = -LTTNG_ERR_NOMEM; - goto end; - } - - /* Copy list from session to the new list object. */ - rcu_read_lock(); - cds_lfht_for_each_entry(session->snapshot.output_ht->ht, &iter.iter, - output, node.node) { - LTTNG_ASSERT(output->consumer); - list[idx].id = output->id; - list[idx].max_size = output->max_size; - if (lttng_strncpy(list[idx].name, output->name, - sizeof(list[idx].name))) { - ret = -LTTNG_ERR_INVALID; - goto error; - } - if (output->consumer->type == CONSUMER_DST_LOCAL) { - if (lttng_strncpy(list[idx].ctrl_url, - output->consumer->dst.session_root_path, - sizeof(list[idx].ctrl_url))) { - ret = -LTTNG_ERR_INVALID; - goto error; - } - } else { - /* Control URI. */ - ret = uri_to_str_url(&output->consumer->dst.net.control, - list[idx].ctrl_url, sizeof(list[idx].ctrl_url)); - if (ret < 0) { - ret = -LTTNG_ERR_NOMEM; - goto error; - } - - /* Data URI. */ - ret = uri_to_str_url(&output->consumer->dst.net.data, - list[idx].data_url, sizeof(list[idx].data_url)); - if (ret < 0) { - ret = -LTTNG_ERR_NOMEM; - goto error; - } - } - idx++; - } - - *outputs = list; - list = NULL; - ret = session->snapshot.nb_output; -error: - rcu_read_unlock(); - free(list); -end: - return ret; -} - -/* - * Check if we can regenerate the metadata for this session. - * Only kernel, UST per-uid and non-live sessions are supported. - * - * Return 0 if the metadata can be generated, a LTTNG_ERR code otherwise. - */ -static -int check_regenerate_metadata_support(struct ltt_session *session) -{ - int ret; - - LTTNG_ASSERT(session); - - if (session->live_timer != 0) { - ret = LTTNG_ERR_LIVE_SESSION; - goto end; - } - if (!session->active) { - ret = LTTNG_ERR_SESSION_NOT_STARTED; - goto end; - } - if (session->ust_session) { - switch (session->ust_session->buffer_type) { - case LTTNG_BUFFER_PER_UID: - break; - case LTTNG_BUFFER_PER_PID: - ret = LTTNG_ERR_PER_PID_SESSION; - goto end; - default: - abort(); - ret = LTTNG_ERR_UNK; - goto end; - } - } - if (session->consumer->type == CONSUMER_DST_NET && - session->consumer->relay_minor_version < 8) { - ret = LTTNG_ERR_RELAYD_VERSION_FAIL; - goto end; - } - ret = 0; - -end: - return ret; -} - -static -int clear_metadata_file(int fd) -{ - int ret; - off_t lseek_ret; - - lseek_ret = lseek(fd, 0, SEEK_SET); - if (lseek_ret < 0) { - PERROR("lseek"); - ret = -1; - goto end; - } - - ret = ftruncate(fd, 0); - if (ret < 0) { - PERROR("ftruncate"); - goto end; - } - -end: - return ret; -} - -static -int ust_regenerate_metadata(struct ltt_ust_session *usess) -{ - int ret = 0; - struct buffer_reg_uid *uid_reg = NULL; - struct buffer_reg_session *session_reg = NULL; - - rcu_read_lock(); - cds_list_for_each_entry(uid_reg, &usess->buffer_reg_uid_list, lnode) { - struct ust_registry_session *registry; - struct ust_registry_channel *chan; - struct lttng_ht_iter iter_chan; - - session_reg = uid_reg->registry; - registry = session_reg->reg.ust; - - pthread_mutex_lock(®istry->lock); - registry->metadata_len_sent = 0; - memset(registry->metadata, 0, registry->metadata_alloc_len); - registry->metadata_len = 0; - registry->metadata_version++; - if (registry->metadata_fd > 0) { - /* Clear the metadata file's content. */ - ret = clear_metadata_file(registry->metadata_fd); - if (ret) { - pthread_mutex_unlock(®istry->lock); - goto end; - } - } - - ret = ust_metadata_session_statedump(registry, NULL, - registry->major, registry->minor); - if (ret) { - pthread_mutex_unlock(®istry->lock); - ERR("Failed to generate session metadata (err = %d)", - ret); - goto end; - } - cds_lfht_for_each_entry(registry->channels->ht, &iter_chan.iter, - chan, node.node) { - struct ust_registry_event *event; - struct lttng_ht_iter iter_event; - - ret = ust_metadata_channel_statedump(registry, chan); - if (ret) { - pthread_mutex_unlock(®istry->lock); - ERR("Failed to generate channel metadata " - "(err = %d)", ret); - goto end; - } - cds_lfht_for_each_entry(chan->ht->ht, &iter_event.iter, - event, node.node) { - ret = ust_metadata_event_statedump(registry, - chan, event); - if (ret) { - pthread_mutex_unlock(®istry->lock); - ERR("Failed to generate event metadata " - "(err = %d)", ret); - goto end; - } - } - } - pthread_mutex_unlock(®istry->lock); - } - -end: - rcu_read_unlock(); - return ret; -} - -/* - * Command LTTNG_REGENERATE_METADATA from the lttng-ctl library. - * - * Ask the consumer to truncate the existing metadata file(s) and - * then regenerate the metadata. Live and per-pid sessions are not - * supported and return an error. - * - * Return LTTNG_OK on success or else a LTTNG_ERR code. - */ -int cmd_regenerate_metadata(struct ltt_session *session) -{ - int ret; - - LTTNG_ASSERT(session); - - ret = check_regenerate_metadata_support(session); - if (ret) { - goto end; - } - - if (session->kernel_session) { - ret = kernctl_session_regenerate_metadata( - session->kernel_session->fd); - if (ret < 0) { - ERR("Failed to regenerate the kernel metadata"); - goto end; - } - } - - if (session->ust_session) { - ret = ust_regenerate_metadata(session->ust_session); - if (ret < 0) { - ERR("Failed to regenerate the UST metadata"); - goto end; - } - } - DBG("Cmd metadata regenerate for session %s", session->name); - ret = LTTNG_OK; - -end: - return ret; -} - -/* - * Command LTTNG_REGENERATE_STATEDUMP from the lttng-ctl library. - * - * Ask the tracer to regenerate a new statedump. - * - * Return LTTNG_OK on success or else a LTTNG_ERR code. - */ -int cmd_regenerate_statedump(struct ltt_session *session) -{ - int ret; - - LTTNG_ASSERT(session); - - if (!session->active) { - ret = LTTNG_ERR_SESSION_NOT_STARTED; - goto end; - } - - if (session->kernel_session) { - ret = kernctl_session_regenerate_statedump( - session->kernel_session->fd); - /* - * Currently, the statedump in kernel can only fail if out - * of memory. - */ - if (ret < 0) { - if (ret == -ENOMEM) { - ret = LTTNG_ERR_REGEN_STATEDUMP_NOMEM; - } else { - ret = LTTNG_ERR_REGEN_STATEDUMP_FAIL; - } - ERR("Failed to regenerate the kernel statedump"); - goto end; - } - } - - if (session->ust_session) { - ret = ust_app_regenerate_statedump_all(session->ust_session); - /* - * Currently, the statedump in UST always returns 0. - */ - if (ret < 0) { - ret = LTTNG_ERR_REGEN_STATEDUMP_FAIL; - ERR("Failed to regenerate the UST statedump"); - goto end; - } - } - DBG("Cmd regenerate statedump for session %s", session->name); - ret = LTTNG_OK; - -end: - return ret; -} - -static -enum lttng_error_code synchronize_tracer_notifier_register( - struct notification_thread_handle *notification_thread, - struct lttng_trigger *trigger, const struct lttng_credentials *cmd_creds) -{ - enum lttng_error_code ret_code; - const struct lttng_condition *condition = - lttng_trigger_get_const_condition(trigger); - const char *trigger_name; - uid_t trigger_owner; - enum lttng_trigger_status trigger_status; - const enum lttng_domain_type trigger_domain = - lttng_trigger_get_underlying_domain_type_restriction( - trigger); - - trigger_status = lttng_trigger_get_owner_uid(trigger, &trigger_owner); - LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); - - LTTNG_ASSERT(condition); - LTTNG_ASSERT(lttng_condition_get_type(condition) == - LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); - - trigger_status = lttng_trigger_get_name(trigger, &trigger_name); - trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? - trigger_name : "(anonymous)"; - - session_lock_list(); - switch (trigger_domain) { - case LTTNG_DOMAIN_KERNEL: - { - ret_code = kernel_register_event_notifier(trigger, cmd_creds); - if (ret_code != LTTNG_OK) { - enum lttng_error_code notif_thread_unregister_ret; - - notif_thread_unregister_ret = - notification_thread_command_unregister_trigger( - notification_thread, trigger); - - if (notif_thread_unregister_ret != LTTNG_OK) { - /* Return the original error code. */ - ERR("Failed to unregister trigger from notification thread during error recovery: trigger name = '%s', trigger owner uid = %d, error code = %d", - trigger_name, - (int) trigger_owner, - ret_code); - } - } - break; - } - case LTTNG_DOMAIN_UST: - ust_app_global_update_all_event_notifier_rules(); - break; - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_PYTHON: - { - /* Agent domains. */ - struct agent *agt = agent_find_by_event_notifier_domain( - trigger_domain); - - if (!agt) { - agt = agent_create(trigger_domain); - if (!agt) { - ret_code = LTTNG_ERR_NOMEM; - goto end_unlock_session_list; - } - - agent_add(agt, the_trigger_agents_ht_by_domain); - } - - ret_code = trigger_agent_enable(trigger, agt); - if (ret_code != LTTNG_OK) { - goto end_unlock_session_list; - } - - break; - } - case LTTNG_DOMAIN_NONE: - default: - abort(); - } - - ret_code = LTTNG_OK; -end_unlock_session_list: - session_unlock_list(); - return ret_code; -} - -enum lttng_error_code cmd_register_trigger(const struct lttng_credentials *cmd_creds, - struct lttng_trigger *trigger, - bool is_trigger_anonymous, - struct notification_thread_handle *notification_thread, - struct lttng_trigger **return_trigger) -{ - enum lttng_error_code ret_code; - const char *trigger_name; - uid_t trigger_owner; - enum lttng_trigger_status trigger_status; - - trigger_status = lttng_trigger_get_name(trigger, &trigger_name); - trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? - trigger_name : "(anonymous)"; - - trigger_status = lttng_trigger_get_owner_uid( - trigger, &trigger_owner); - LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); - - DBG("Running register trigger command: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", - trigger_name, (int) trigger_owner, - (int) lttng_credentials_get_uid(cmd_creds)); - - /* - * Validate the trigger credentials against the command credentials. - * Only the root user can register a trigger with non-matching - * credentials. - */ - if (!lttng_credentials_is_equal_uid( - lttng_trigger_get_credentials(trigger), - cmd_creds)) { - if (lttng_credentials_get_uid(cmd_creds) != 0) { - ERR("Trigger credentials do not match the command credentials: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", - trigger_name, (int) trigger_owner, - (int) lttng_credentials_get_uid(cmd_creds)); - ret_code = LTTNG_ERR_INVALID_TRIGGER; - goto end; - } - } - - /* - * The bytecode generation also serves as a validation step for the - * bytecode expressions. - */ - ret_code = lttng_trigger_generate_bytecode(trigger, cmd_creds); - if (ret_code != LTTNG_OK) { - ERR("Failed to generate bytecode of trigger: trigger name = '%s', trigger owner uid = %d, error code = %d", - trigger_name, (int) trigger_owner, ret_code); - goto end; - } - - /* - * A reference to the trigger is acquired by the notification thread. - * It is safe to return the same trigger to the caller since it the - * other user holds a reference. - * - * The trigger is modified during the execution of the - * "register trigger" command. However, by the time the command returns, - * it is safe to use without any locking as its properties are - * immutable. - */ - ret_code = notification_thread_command_register_trigger( - notification_thread, trigger, is_trigger_anonymous); - if (ret_code != LTTNG_OK) { - DBG("Failed to register trigger to notification thread: trigger name = '%s', trigger owner uid = %d, error code = %d", - trigger_name, (int) trigger_owner, ret_code); - goto end; - } - - trigger_status = lttng_trigger_get_name(trigger, &trigger_name); - trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? - trigger_name : "(anonymous)"; - - /* - * Synchronize tracers if the trigger adds an event notifier. - */ - if (lttng_trigger_needs_tracer_notifier(trigger)) { - ret_code = synchronize_tracer_notifier_register(notification_thread, - trigger, cmd_creds); - if (ret_code != LTTNG_OK) { - ERR("Error registering tracer notifier: %s", - lttng_strerror(-ret_code)); - goto end; - } - } - - /* - * Return an updated trigger to the client. - * - * Since a modified version of the same trigger is returned, acquire a - * reference to the trigger so the caller doesn't have to care if those - * are distinct instances or not. - */ - if (ret_code == LTTNG_OK) { - lttng_trigger_get(trigger); - *return_trigger = trigger; - /* Ownership of trigger was transferred to caller. */ - trigger = NULL; - } -end: - return ret_code; -} - -static -enum lttng_error_code synchronize_tracer_notifier_unregister( - const struct lttng_trigger *trigger) -{ - enum lttng_error_code ret_code; - const struct lttng_condition *condition = - lttng_trigger_get_const_condition(trigger); - const enum lttng_domain_type trigger_domain = - lttng_trigger_get_underlying_domain_type_restriction( - trigger); - - LTTNG_ASSERT(condition); - LTTNG_ASSERT(lttng_condition_get_type(condition) == - LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); - - session_lock_list(); - switch (trigger_domain) { - case LTTNG_DOMAIN_KERNEL: - ret_code = kernel_unregister_event_notifier(trigger); - if (ret_code != LTTNG_OK) { - goto end_unlock_session_list; - } - - break; - case LTTNG_DOMAIN_UST: - ust_app_global_update_all_event_notifier_rules(); - break; - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_PYTHON: - { - /* Agent domains. */ - struct agent *agt = agent_find_by_event_notifier_domain( - trigger_domain); - - /* - * This trigger was never registered in the first place. Calling - * this function under those circumstances is an internal error. - */ - LTTNG_ASSERT(agt); - ret_code = trigger_agent_disable(trigger, agt); - if (ret_code != LTTNG_OK) { - goto end_unlock_session_list; - } - - break; - } - case LTTNG_DOMAIN_NONE: - default: - abort(); - } - - ret_code = LTTNG_OK; - -end_unlock_session_list: - session_unlock_list(); - return ret_code; -} - -enum lttng_error_code cmd_unregister_trigger(const struct lttng_credentials *cmd_creds, - const struct lttng_trigger *trigger, - struct notification_thread_handle *notification_thread) -{ - enum lttng_error_code ret_code; - const char *trigger_name; - uid_t trigger_owner; - enum lttng_trigger_status trigger_status; - struct lttng_trigger *sessiond_trigger = NULL; - - trigger_status = lttng_trigger_get_name(trigger, &trigger_name); - trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? trigger_name : "(anonymous)"; - trigger_status = lttng_trigger_get_owner_uid(trigger, &trigger_owner); - LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); - - DBG("Running unregister trigger command: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", - trigger_name, (int) trigger_owner, - (int) lttng_credentials_get_uid(cmd_creds)); - - /* - * Validate the trigger credentials against the command credentials. - * Only the root user can unregister a trigger with non-matching - * credentials. - */ - if (!lttng_credentials_is_equal_uid( - lttng_trigger_get_credentials(trigger), - cmd_creds)) { - if (lttng_credentials_get_uid(cmd_creds) != 0) { - ERR("Trigger credentials do not match the command credentials: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", - trigger_name, (int) trigger_owner, - (int) lttng_credentials_get_uid(cmd_creds)); - ret_code = LTTNG_ERR_INVALID_TRIGGER; - goto end; - } - } - - /* Fetch the sessiond side trigger object. */ - ret_code = notification_thread_command_get_trigger( - notification_thread, trigger, &sessiond_trigger); - if (ret_code != LTTNG_OK) { - DBG("Failed to get trigger from notification thread during unregister: trigger name = '%s', trigger owner uid = %d, error code = %d", - trigger_name, (int) trigger_owner, ret_code); - goto end; - } - - LTTNG_ASSERT(sessiond_trigger); - - /* - * From this point on, no matter what, consider the trigger - * unregistered. - * - * We set the unregistered state of the sessiond side trigger object in - * the client thread since we want to minimize the possibility of the - * notification thread being stalled due to a long execution of an - * action that required the trigger lock. - */ - lttng_trigger_set_as_unregistered(sessiond_trigger); - - ret_code = notification_thread_command_unregister_trigger(notification_thread, - trigger); - if (ret_code != LTTNG_OK) { - DBG("Failed to unregister trigger from notification thread: trigger name = '%s', trigger owner uid = %d, error code = %d", - trigger_name, (int) trigger_owner, ret_code); - goto end; - } - - /* - * Synchronize tracers if the trigger removes an event notifier. - * Do this even if the trigger unregistration failed to at least stop - * the tracers from producing notifications associated with this - * event notifier. - */ - if (lttng_trigger_needs_tracer_notifier(trigger)) { - ret_code = synchronize_tracer_notifier_unregister(trigger); - if (ret_code != LTTNG_OK) { - ERR("Error unregistering trigger to tracer."); - goto end; - } - - } - -end: - lttng_trigger_put(sessiond_trigger); - return ret_code; -} - -enum lttng_error_code cmd_list_triggers(struct command_ctx *cmd_ctx, - struct notification_thread_handle *notification_thread, - struct lttng_triggers **return_triggers) -{ - int ret; - enum lttng_error_code ret_code; - struct lttng_triggers *triggers = NULL; - - /* Get the set of triggers from the notification thread. */ - ret_code = notification_thread_command_list_triggers( - notification_thread, cmd_ctx->creds.uid, &triggers); - if (ret_code != LTTNG_OK) { - goto end; - } - - ret = lttng_triggers_remove_hidden_triggers(triggers); - if (ret) { - ret_code = LTTNG_ERR_UNK; - goto end; - } - - *return_triggers = triggers; - triggers = NULL; - ret_code = LTTNG_OK; -end: - lttng_triggers_destroy(triggers); - return ret_code; -} - -enum lttng_error_code cmd_execute_error_query(const struct lttng_credentials *cmd_creds, - const struct lttng_error_query *query, - struct lttng_error_query_results **_results, - struct notification_thread_handle *notification_thread) -{ - enum lttng_error_code ret_code; - const struct lttng_trigger *query_target_trigger; - const struct lttng_action *query_target_action = NULL; - struct lttng_trigger *matching_trigger = NULL; - const char *trigger_name; - uid_t trigger_owner; - enum lttng_trigger_status trigger_status; - struct lttng_error_query_results *results = NULL; - - switch (lttng_error_query_get_target_type(query)) { - case LTTNG_ERROR_QUERY_TARGET_TYPE_TRIGGER: - query_target_trigger = lttng_error_query_trigger_borrow_target(query); - break; - case LTTNG_ERROR_QUERY_TARGET_TYPE_CONDITION: - query_target_trigger = - lttng_error_query_condition_borrow_target(query); - break; - case LTTNG_ERROR_QUERY_TARGET_TYPE_ACTION: - query_target_trigger = lttng_error_query_action_borrow_trigger_target( - query); - break; - default: - abort(); - } - - LTTNG_ASSERT(query_target_trigger); - - ret_code = notification_thread_command_get_trigger(notification_thread, - query_target_trigger, &matching_trigger); - if (ret_code != LTTNG_OK) { - goto end; - } - - /* No longer needed. */ - query_target_trigger = NULL; - - if (lttng_error_query_get_target_type(query) == - LTTNG_ERROR_QUERY_TARGET_TYPE_ACTION) { - /* Get the sessiond-side version of the target action. */ - query_target_action = - lttng_error_query_action_borrow_action_target( - query, matching_trigger); - } - - trigger_status = lttng_trigger_get_name(matching_trigger, &trigger_name); - trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? - trigger_name : "(anonymous)"; - trigger_status = lttng_trigger_get_owner_uid(matching_trigger, - &trigger_owner); - LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); - - results = lttng_error_query_results_create(); - if (!results) { - ret_code = LTTNG_ERR_NOMEM; - goto end; - } - - DBG("Running \"execute error query\" command: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", - trigger_name, (int) trigger_owner, - (int) lttng_credentials_get_uid(cmd_creds)); - - /* - * Validate the trigger credentials against the command credentials. - * Only the root user can target a trigger with non-matching - * credentials. - */ - if (!lttng_credentials_is_equal_uid( - lttng_trigger_get_credentials(matching_trigger), - cmd_creds)) { - if (lttng_credentials_get_uid(cmd_creds) != 0) { - ERR("Trigger credentials do not match the command credentials: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", - trigger_name, (int) trigger_owner, - (int) lttng_credentials_get_uid(cmd_creds)); - ret_code = LTTNG_ERR_INVALID_TRIGGER; - goto end; - } - } - - switch (lttng_error_query_get_target_type(query)) { - case LTTNG_ERROR_QUERY_TARGET_TYPE_TRIGGER: - trigger_status = lttng_trigger_add_error_results( - matching_trigger, results); - - switch (trigger_status) { - case LTTNG_TRIGGER_STATUS_OK: - break; - default: - ret_code = LTTNG_ERR_UNK; - goto end; - } - - break; - case LTTNG_ERROR_QUERY_TARGET_TYPE_CONDITION: - { - trigger_status = lttng_trigger_condition_add_error_results( - matching_trigger, results); - - switch (trigger_status) { - case LTTNG_TRIGGER_STATUS_OK: - break; - default: - ret_code = LTTNG_ERR_UNK; - goto end; - } - - break; - } - case LTTNG_ERROR_QUERY_TARGET_TYPE_ACTION: - { - const enum lttng_action_status action_status = - lttng_action_add_error_query_results( - query_target_action, results); - - switch (action_status) { - case LTTNG_ACTION_STATUS_OK: - break; - default: - ret_code = LTTNG_ERR_UNK; - goto end; - } - - break; - } - default: - abort(); - break; - } - - *_results = results; - results = NULL; - ret_code = LTTNG_OK; -end: - lttng_trigger_put(matching_trigger); - lttng_error_query_results_destroy(results); - return ret_code; -} - -/* - * Send relayd sockets from snapshot output to consumer. Ignore request if the - * snapshot output is *not* set with a remote destination. - * - * Return LTTNG_OK on success or a LTTNG_ERR code. - */ -static enum lttng_error_code set_relayd_for_snapshot( - struct consumer_output *output, - const struct ltt_session *session) -{ - enum lttng_error_code status = LTTNG_OK; - struct lttng_ht_iter iter; - struct consumer_socket *socket; - LTTNG_OPTIONAL(uint64_t) current_chunk_id = {}; - const char *base_path; - - LTTNG_ASSERT(output); - LTTNG_ASSERT(session); - - DBG2("Set relayd object from snapshot output"); - - if (session->current_trace_chunk) { - enum lttng_trace_chunk_status chunk_status = - lttng_trace_chunk_get_id( - session->current_trace_chunk, - ¤t_chunk_id.value); - - if (chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK) { - current_chunk_id.is_set = true; - } else { - ERR("Failed to get current trace chunk id"); - status = LTTNG_ERR_UNK; - goto error; - } - } - - /* Ignore if snapshot consumer output is not network. */ - if (output->type != CONSUMER_DST_NET) { - goto error; - } - - /* - * The snapshot record URI base path overrides the session - * base path. - */ - if (output->dst.net.control.subdir[0] != '\0') { - base_path = output->dst.net.control.subdir; - } else { - base_path = session->base_path; - } - - /* - * For each consumer socket, create and send the relayd object of the - * snapshot output. - */ - rcu_read_lock(); - cds_lfht_for_each_entry(output->socks->ht, &iter.iter, - socket, node.node) { - pthread_mutex_lock(socket->lock); - status = send_consumer_relayd_sockets(0, session->id, - output, socket, - session->name, session->hostname, - base_path, - session->live_timer, - current_chunk_id.is_set ? ¤t_chunk_id.value : NULL, - session->creation_time, - session->name_contains_creation_time); - pthread_mutex_unlock(socket->lock); - if (status != LTTNG_OK) { - rcu_read_unlock(); - goto error; - } - } - rcu_read_unlock(); - -error: - return status; -} - -/* - * Record a kernel snapshot. - * - * Return LTTNG_OK on success or a LTTNG_ERR code. - */ -static enum lttng_error_code record_kernel_snapshot( - struct ltt_kernel_session *ksess, - const struct consumer_output *output, - const struct ltt_session *session, - int wait, uint64_t nb_packets_per_stream) -{ - enum lttng_error_code status; - - LTTNG_ASSERT(ksess); - LTTNG_ASSERT(output); - LTTNG_ASSERT(session); - - status = kernel_snapshot_record( - ksess, output, wait, nb_packets_per_stream); - return status; -} - -/* - * Record a UST snapshot. - * - * Returns LTTNG_OK on success or a LTTNG_ERR error code. - */ -static enum lttng_error_code record_ust_snapshot(struct ltt_ust_session *usess, - const struct consumer_output *output, - const struct ltt_session *session, - int wait, uint64_t nb_packets_per_stream) -{ - enum lttng_error_code status; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(output); - LTTNG_ASSERT(session); - - status = ust_app_snapshot_record( - usess, output, wait, nb_packets_per_stream); - return status; -} - -static -uint64_t get_session_size_one_more_packet_per_stream( - const struct ltt_session *session, uint64_t cur_nr_packets) -{ - uint64_t tot_size = 0; - - if (session->kernel_session) { - struct ltt_kernel_channel *chan; - const struct ltt_kernel_session *ksess = - session->kernel_session; - - cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { - if (cur_nr_packets >= chan->channel->attr.num_subbuf) { - /* - * Don't take channel into account if we - * already grab all its packets. - */ - continue; - } - tot_size += chan->channel->attr.subbuf_size - * chan->stream_count; - } - } - - if (session->ust_session) { - const struct ltt_ust_session *usess = session->ust_session; - - tot_size += ust_app_get_size_one_more_packet_per_stream(usess, - cur_nr_packets); - } - - return tot_size; -} - -/* - * Calculate the number of packets we can grab from each stream that - * fits within the overall snapshot max size. - * - * Returns -1 on error, 0 means infinite number of packets, else > 0 is - * the number of packets per stream. - * - * TODO: this approach is not perfect: we consider the worse case - * (packet filling the sub-buffers) as an upper bound, but we could do - * better if we do this calculation while we actually grab the packet - * content: we would know how much padding we don't actually store into - * the file. - * - * This algorithm is currently bounded by the number of packets per - * stream. - * - * Since we call this algorithm before actually grabbing the data, it's - * an approximation: for instance, applications could appear/disappear - * in between this call and actually grabbing data. - */ -static -int64_t get_session_nb_packets_per_stream(const struct ltt_session *session, - uint64_t max_size) -{ - int64_t size_left; - uint64_t cur_nb_packets = 0; - - if (!max_size) { - return 0; /* Infinite */ - } - - size_left = max_size; - for (;;) { - uint64_t one_more_packet_tot_size; - - one_more_packet_tot_size = get_session_size_one_more_packet_per_stream( - session, cur_nb_packets); - if (!one_more_packet_tot_size) { - /* We are already grabbing all packets. */ - break; - } - size_left -= one_more_packet_tot_size; - if (size_left < 0) { - break; - } - cur_nb_packets++; - } - if (!cur_nb_packets && size_left != max_size) { - /* Not enough room to grab one packet of each stream, error. */ - return -1; - } - return cur_nb_packets; -} - -static -enum lttng_error_code snapshot_record(struct ltt_session *session, - const struct snapshot_output *snapshot_output, int wait) -{ - int64_t nb_packets_per_stream; - char snapshot_chunk_name[LTTNG_NAME_MAX]; - int ret; - enum lttng_error_code ret_code = LTTNG_OK; - struct lttng_trace_chunk *snapshot_trace_chunk; - struct consumer_output *original_ust_consumer_output = NULL; - struct consumer_output *original_kernel_consumer_output = NULL; - struct consumer_output *snapshot_ust_consumer_output = NULL; - struct consumer_output *snapshot_kernel_consumer_output = NULL; - - ret = snprintf(snapshot_chunk_name, sizeof(snapshot_chunk_name), - "%s-%s-%" PRIu64, - snapshot_output->name, - snapshot_output->datetime, - snapshot_output->nb_snapshot); - if (ret < 0 || ret >= sizeof(snapshot_chunk_name)) { - ERR("Failed to format snapshot name"); - ret_code = LTTNG_ERR_INVALID; - goto error; - } - DBG("Recording snapshot \"%s\" for session \"%s\" with chunk name \"%s\"", - snapshot_output->name, session->name, - snapshot_chunk_name); - if (!session->kernel_session && !session->ust_session) { - ERR("Failed to record snapshot as no channels exist"); - ret_code = LTTNG_ERR_NO_CHANNEL; - goto error; - } - - if (session->kernel_session) { - original_kernel_consumer_output = - session->kernel_session->consumer; - snapshot_kernel_consumer_output = - consumer_copy_output(snapshot_output->consumer); - strcpy(snapshot_kernel_consumer_output->chunk_path, - snapshot_chunk_name); - - /* Copy the original domain subdir. */ - strcpy(snapshot_kernel_consumer_output->domain_subdir, - original_kernel_consumer_output->domain_subdir); - - ret = consumer_copy_sockets(snapshot_kernel_consumer_output, - original_kernel_consumer_output); - if (ret < 0) { - ERR("Failed to copy consumer sockets from snapshot output configuration"); - ret_code = LTTNG_ERR_NOMEM; - goto error; - } - ret_code = set_relayd_for_snapshot( - snapshot_kernel_consumer_output, session); - if (ret_code != LTTNG_OK) { - ERR("Failed to setup relay daemon for kernel tracer snapshot"); - goto error; - } - session->kernel_session->consumer = - snapshot_kernel_consumer_output; - } - if (session->ust_session) { - original_ust_consumer_output = session->ust_session->consumer; - snapshot_ust_consumer_output = - consumer_copy_output(snapshot_output->consumer); - strcpy(snapshot_ust_consumer_output->chunk_path, - snapshot_chunk_name); - - /* Copy the original domain subdir. */ - strcpy(snapshot_ust_consumer_output->domain_subdir, - original_ust_consumer_output->domain_subdir); - - ret = consumer_copy_sockets(snapshot_ust_consumer_output, - original_ust_consumer_output); - if (ret < 0) { - ERR("Failed to copy consumer sockets from snapshot output configuration"); - ret_code = LTTNG_ERR_NOMEM; - goto error; - } - ret_code = set_relayd_for_snapshot( - snapshot_ust_consumer_output, session); - if (ret_code != LTTNG_OK) { - ERR("Failed to setup relay daemon for userspace tracer snapshot"); - goto error; - } - session->ust_session->consumer = - snapshot_ust_consumer_output; - } - - snapshot_trace_chunk = session_create_new_trace_chunk(session, - snapshot_kernel_consumer_output ?: - snapshot_ust_consumer_output, - consumer_output_get_base_path( - snapshot_output->consumer), - snapshot_chunk_name); - if (!snapshot_trace_chunk) { - ERR("Failed to create temporary trace chunk to record a snapshot of session \"%s\"", - session->name); - ret_code = LTTNG_ERR_CREATE_DIR_FAIL; - goto error; - } - LTTNG_ASSERT(!session->current_trace_chunk); - ret = session_set_trace_chunk(session, snapshot_trace_chunk, NULL); - lttng_trace_chunk_put(snapshot_trace_chunk); - snapshot_trace_chunk = NULL; - if (ret) { - ERR("Failed to set temporary trace chunk to record a snapshot of session \"%s\"", - session->name); - ret_code = LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; - goto error; - } - - nb_packets_per_stream = get_session_nb_packets_per_stream(session, - snapshot_output->max_size); - if (nb_packets_per_stream < 0) { - ret_code = LTTNG_ERR_MAX_SIZE_INVALID; - goto error_close_trace_chunk; - } - - if (session->kernel_session) { - ret_code = record_kernel_snapshot(session->kernel_session, - snapshot_kernel_consumer_output, session, - wait, nb_packets_per_stream); - if (ret_code != LTTNG_OK) { - goto error_close_trace_chunk; - } - } - - if (session->ust_session) { - ret_code = record_ust_snapshot(session->ust_session, - snapshot_ust_consumer_output, session, - wait, nb_packets_per_stream); - if (ret_code != LTTNG_OK) { - goto error_close_trace_chunk; - } - } - -error_close_trace_chunk: - if (session_set_trace_chunk(session, NULL, &snapshot_trace_chunk)) { - ERR("Failed to release the current trace chunk of session \"%s\"", - session->name); - ret_code = LTTNG_ERR_UNK; - } - - if (session_close_trace_chunk(session, snapshot_trace_chunk, - LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION, NULL)) { - /* - * Don't goto end; make sure the chunk is closed for the session - * to allow future snapshots. - */ - ERR("Failed to close snapshot trace chunk of session \"%s\"", - session->name); - ret_code = LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; - } -error: - if (original_ust_consumer_output) { - session->ust_session->consumer = original_ust_consumer_output; - } - if (original_kernel_consumer_output) { - session->kernel_session->consumer = - original_kernel_consumer_output; - } - consumer_output_put(snapshot_ust_consumer_output); - consumer_output_put(snapshot_kernel_consumer_output); - return ret_code; -} - -/* - * Command LTTNG_SNAPSHOT_RECORD from lib lttng ctl. - * - * The wait parameter is ignored so this call always wait for the snapshot to - * complete before returning. - * - * Return LTTNG_OK on success or else a LTTNG_ERR code. - */ -int cmd_snapshot_record(struct ltt_session *session, - const struct lttng_snapshot_output *output, int wait) -{ - enum lttng_error_code cmd_ret = LTTNG_OK; - int ret; - unsigned int snapshot_success = 0; - char datetime[16]; - struct snapshot_output *tmp_output = NULL; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(output); - - DBG("Cmd snapshot record for session %s", session->name); - - /* Get the datetime for the snapshot output directory. */ - ret = utils_get_current_time_str("%Y%m%d-%H%M%S", datetime, - sizeof(datetime)); - if (!ret) { - cmd_ret = LTTNG_ERR_INVALID; - goto error; - } - - /* - * Permission denied to create an output if the session is not - * set in no output mode. - */ - if (session->output_traces) { - cmd_ret = LTTNG_ERR_NOT_SNAPSHOT_SESSION; - goto error; - } - - /* The session needs to be started at least once. */ - if (!session->has_been_started) { - cmd_ret = LTTNG_ERR_START_SESSION_ONCE; - goto error; - } - - /* Use temporary output for the session. */ - if (*output->ctrl_url != '\0') { - tmp_output = snapshot_output_alloc(); - if (!tmp_output) { - cmd_ret = LTTNG_ERR_NOMEM; - goto error; - } - - ret = snapshot_output_init(session, output->max_size, - output->name, - output->ctrl_url, output->data_url, - session->consumer, - tmp_output, NULL); - if (ret < 0) { - if (ret == -ENOMEM) { - cmd_ret = LTTNG_ERR_NOMEM; - } else { - cmd_ret = LTTNG_ERR_INVALID; - } - goto error; - } - /* Use the global session count for the temporary snapshot. */ - tmp_output->nb_snapshot = session->snapshot.nb_snapshot; - - /* Use the global datetime */ - memcpy(tmp_output->datetime, datetime, sizeof(datetime)); - cmd_ret = snapshot_record(session, tmp_output, wait); - if (cmd_ret != LTTNG_OK) { - goto error; - } - snapshot_success = 1; - } else { - struct snapshot_output *sout; - struct lttng_ht_iter iter; - - rcu_read_lock(); - cds_lfht_for_each_entry(session->snapshot.output_ht->ht, - &iter.iter, sout, node.node) { - struct snapshot_output output_copy; - - /* - * Make a local copy of the output and override output - * parameters with those provided as part of the - * command. - */ - memcpy(&output_copy, sout, sizeof(output_copy)); - - if (output->max_size != (uint64_t) -1ULL) { - output_copy.max_size = output->max_size; - } - - output_copy.nb_snapshot = session->snapshot.nb_snapshot; - memcpy(output_copy.datetime, datetime, - sizeof(datetime)); - - /* Use temporary name. */ - if (*output->name != '\0') { - if (lttng_strncpy(output_copy.name, - output->name, - sizeof(output_copy.name))) { - cmd_ret = LTTNG_ERR_INVALID; - rcu_read_unlock(); - goto error; - } - } - - cmd_ret = snapshot_record(session, &output_copy, wait); - if (cmd_ret != LTTNG_OK) { - rcu_read_unlock(); - goto error; - } - snapshot_success = 1; - } - rcu_read_unlock(); - } - - if (snapshot_success) { - session->snapshot.nb_snapshot++; - } else { - cmd_ret = LTTNG_ERR_SNAPSHOT_FAIL; - } - -error: - if (tmp_output) { - snapshot_output_destroy(tmp_output); - } - return cmd_ret; -} - -/* - * Command LTTNG_SET_SESSION_SHM_PATH processed by the client thread. - */ -int cmd_set_session_shm_path(struct ltt_session *session, - const char *shm_path) -{ - /* Safety net */ - LTTNG_ASSERT(session); - - /* - * Can only set shm path before session is started. - */ - if (session->has_been_started) { - return LTTNG_ERR_SESSION_STARTED; - } - - strncpy(session->shm_path, shm_path, - sizeof(session->shm_path)); - session->shm_path[sizeof(session->shm_path) - 1] = '\0'; - - return LTTNG_OK; -} - -/* - * Command LTTNG_ROTATE_SESSION from the lttng-ctl library. - * - * Ask the consumer to rotate the session output directory. - * The session lock must be held. - * - * Returns LTTNG_OK on success or else a negative LTTng error code. - */ -int cmd_rotate_session(struct ltt_session *session, - struct lttng_rotate_session_return *rotate_return, - bool quiet_rotation, - enum lttng_trace_chunk_command_type command) -{ - int ret; - uint64_t ongoing_rotation_chunk_id; - enum lttng_error_code cmd_ret = LTTNG_OK; - struct lttng_trace_chunk *chunk_being_archived = NULL; - struct lttng_trace_chunk *new_trace_chunk = NULL; - enum lttng_trace_chunk_status chunk_status; - bool failed_to_rotate = false; - enum lttng_error_code rotation_fail_code = LTTNG_OK; - - LTTNG_ASSERT(session); - - if (!session->has_been_started) { - cmd_ret = LTTNG_ERR_START_SESSION_ONCE; - goto end; - } - - /* - * Explicit rotation is not supported for live sessions. - * However, live sessions can perform a quiet rotation on - * destroy. - * Rotation is not supported for snapshot traces (no output). - */ - if ((!quiet_rotation && session->live_timer) || - !session->output_traces) { - cmd_ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE; - goto end; - } - - /* Unsupported feature in lttng-relayd before 2.11. */ - if (!quiet_rotation && session->consumer->type == CONSUMER_DST_NET && - (session->consumer->relay_major_version == 2 && - session->consumer->relay_minor_version < 11)) { - cmd_ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE_RELAY; - goto end; - } - - /* Unsupported feature in lttng-modules before 2.8 (lack of sequence number). */ - if (session->kernel_session && !kernel_supports_ring_buffer_packet_sequence_number()) { - cmd_ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE_KERNEL; - goto end; - } - - if (session->rotation_state == LTTNG_ROTATION_STATE_ONGOING) { - DBG("Refusing to launch a rotation; a rotation is already in progress for session %s", - session->name); - cmd_ret = LTTNG_ERR_ROTATION_PENDING; - goto end; - } - - /* - * After a stop, we only allow one rotation to occur, the other ones are - * useless until a new start. - */ - if (session->rotated_after_last_stop) { - DBG("Session \"%s\" was already rotated after stop, refusing rotation", - session->name); - cmd_ret = LTTNG_ERR_ROTATION_MULTIPLE_AFTER_STOP; - goto end; - } - - /* - * After a stop followed by a clear, disallow following rotations a they would - * generate empty chunks. - */ - if (session->cleared_after_last_stop) { - DBG("Session \"%s\" was already cleared after stop, refusing rotation", - session->name); - cmd_ret = LTTNG_ERR_ROTATION_AFTER_STOP_CLEAR; - goto end; - } - - if (session->active) { - new_trace_chunk = session_create_new_trace_chunk(session, NULL, - NULL, NULL); - if (!new_trace_chunk) { - cmd_ret = LTTNG_ERR_CREATE_DIR_FAIL; - goto error; - } - } - - /* - * The current trace chunk becomes the chunk being archived. - * - * After this point, "chunk_being_archived" must absolutely - * be closed on the consumer(s), otherwise it will never be - * cleaned-up, which will result in a leak. - */ - ret = session_set_trace_chunk(session, new_trace_chunk, - &chunk_being_archived); - if (ret) { - cmd_ret = LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; - goto error; - } - - if (session->kernel_session) { - cmd_ret = kernel_rotate_session(session); - if (cmd_ret != LTTNG_OK) { - failed_to_rotate = true; - rotation_fail_code = cmd_ret; - } - } - if (session->ust_session) { - cmd_ret = ust_app_rotate_session(session); - if (cmd_ret != LTTNG_OK) { - failed_to_rotate = true; - rotation_fail_code = cmd_ret; - } - } - - if (!session->active) { - session->rotated_after_last_stop = true; - } - - if (!chunk_being_archived) { - DBG("Rotating session \"%s\" from a \"NULL\" trace chunk to a new trace chunk, skipping completion check", - session->name); - if (failed_to_rotate) { - cmd_ret = rotation_fail_code; - goto error; - } - cmd_ret = LTTNG_OK; - goto end; - } - - session->rotation_state = LTTNG_ROTATION_STATE_ONGOING; - chunk_status = lttng_trace_chunk_get_id(chunk_being_archived, - &ongoing_rotation_chunk_id); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - - ret = session_close_trace_chunk(session, chunk_being_archived, - command, session->last_chunk_path); - if (ret) { - cmd_ret = LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; - goto error; - } - - if (failed_to_rotate) { - cmd_ret = rotation_fail_code; - goto error; - } - - session->quiet_rotation = quiet_rotation; - ret = timer_session_rotation_pending_check_start(session, - DEFAULT_ROTATE_PENDING_TIMER); - if (ret) { - cmd_ret = LTTNG_ERR_UNK; - goto error; - } - - if (rotate_return) { - rotate_return->rotation_id = ongoing_rotation_chunk_id; - } - - session->chunk_being_archived = chunk_being_archived; - chunk_being_archived = NULL; - if (!quiet_rotation) { - ret = notification_thread_command_session_rotation_ongoing( - the_notification_thread_handle, session->name, - session->uid, session->gid, - ongoing_rotation_chunk_id); - if (ret != LTTNG_OK) { - ERR("Failed to notify notification thread that a session rotation is ongoing for session %s", - session->name); - cmd_ret = ret; - } - } - - DBG("Cmd rotate session %s, archive_id %" PRIu64 " sent", - session->name, ongoing_rotation_chunk_id); -end: - lttng_trace_chunk_put(new_trace_chunk); - lttng_trace_chunk_put(chunk_being_archived); - ret = (cmd_ret == LTTNG_OK) ? cmd_ret : -((int) cmd_ret); - return ret; -error: - if (session_reset_rotation_state(session, - LTTNG_ROTATION_STATE_ERROR)) { - ERR("Failed to reset rotation state of session \"%s\"", - session->name); - } - goto end; -} - -/* - * Command LTTNG_ROTATION_GET_INFO from the lttng-ctl library. - * - * Check if the session has finished its rotation. - * - * Return LTTNG_OK on success or else an LTTNG_ERR code. - */ -int cmd_rotate_get_info(struct ltt_session *session, - struct lttng_rotation_get_info_return *info_return, - uint64_t rotation_id) -{ - enum lttng_error_code cmd_ret = LTTNG_OK; - enum lttng_rotation_state rotation_state; - - DBG("Cmd rotate_get_info session %s, rotation id %" PRIu64, session->name, - session->most_recent_chunk_id.value); - - if (session->chunk_being_archived) { - enum lttng_trace_chunk_status chunk_status; - uint64_t chunk_id; - - chunk_status = lttng_trace_chunk_get_id( - session->chunk_being_archived, - &chunk_id); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - - rotation_state = rotation_id == chunk_id ? - LTTNG_ROTATION_STATE_ONGOING : - LTTNG_ROTATION_STATE_EXPIRED; - } else { - if (session->last_archived_chunk_id.is_set && - rotation_id != session->last_archived_chunk_id.value) { - rotation_state = LTTNG_ROTATION_STATE_EXPIRED; - } else { - rotation_state = session->rotation_state; - } - } - - switch (rotation_state) { - case LTTNG_ROTATION_STATE_NO_ROTATION: - DBG("Reporting that no rotation has occurred within the lifetime of session \"%s\"", - session->name); - goto end; - case LTTNG_ROTATION_STATE_EXPIRED: - DBG("Reporting that the rotation state of rotation id %" PRIu64 " of session \"%s\" has expired", - rotation_id, session->name); - break; - case LTTNG_ROTATION_STATE_ONGOING: - DBG("Reporting that rotation id %" PRIu64 " of session \"%s\" is still pending", - rotation_id, session->name); - break; - case LTTNG_ROTATION_STATE_COMPLETED: - { - int fmt_ret; - char *chunk_path; - char *current_tracing_path_reply; - size_t current_tracing_path_reply_len; - - DBG("Reporting that rotation id %" PRIu64 " of session \"%s\" is completed", - rotation_id, session->name); - - switch (session_get_consumer_destination_type(session)) { - case CONSUMER_DST_LOCAL: - current_tracing_path_reply = - info_return->location.local.absolute_path; - current_tracing_path_reply_len = - sizeof(info_return->location.local.absolute_path); - info_return->location_type = - (int8_t) LTTNG_TRACE_ARCHIVE_LOCATION_TYPE_LOCAL; - fmt_ret = asprintf(&chunk_path, - "%s/" DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY "/%s", - session_get_base_path(session), - session->last_archived_chunk_name); - if (fmt_ret == -1) { - PERROR("Failed to format the path of the last archived trace chunk"); - info_return->status = LTTNG_ROTATION_STATUS_ERROR; - cmd_ret = LTTNG_ERR_UNK; - goto end; - } - break; - case CONSUMER_DST_NET: - { - uint16_t ctrl_port, data_port; - - current_tracing_path_reply = - info_return->location.relay.relative_path; - current_tracing_path_reply_len = - sizeof(info_return->location.relay.relative_path); - /* Currently the only supported relay protocol. */ - info_return->location.relay.protocol = - (int8_t) LTTNG_TRACE_ARCHIVE_LOCATION_RELAY_PROTOCOL_TYPE_TCP; - - fmt_ret = lttng_strncpy(info_return->location.relay.host, - session_get_net_consumer_hostname(session), - sizeof(info_return->location.relay.host)); - if (fmt_ret) { - ERR("Failed to copy host name to rotate_get_info reply"); - info_return->status = LTTNG_ROTATION_STATUS_ERROR; - cmd_ret = LTTNG_ERR_SET_URL; - goto end; - } - - session_get_net_consumer_ports(session, &ctrl_port, &data_port); - info_return->location.relay.ports.control = ctrl_port; - info_return->location.relay.ports.data = data_port; - info_return->location_type = - (int8_t) LTTNG_TRACE_ARCHIVE_LOCATION_TYPE_RELAY; - chunk_path = strdup(session->last_chunk_path); - if (!chunk_path) { - ERR("Failed to allocate the path of the last archived trace chunk"); - info_return->status = LTTNG_ROTATION_STATUS_ERROR; - cmd_ret = LTTNG_ERR_UNK; - goto end; - } - break; - } - default: - abort(); - } - - fmt_ret = lttng_strncpy(current_tracing_path_reply, - chunk_path, current_tracing_path_reply_len); - free(chunk_path); - if (fmt_ret) { - ERR("Failed to copy path of the last archived trace chunk to rotate_get_info reply"); - info_return->status = LTTNG_ROTATION_STATUS_ERROR; - cmd_ret = LTTNG_ERR_UNK; - goto end; - } - - break; - } - case LTTNG_ROTATION_STATE_ERROR: - DBG("Reporting that an error occurred during rotation %" PRIu64 " of session \"%s\"", - rotation_id, session->name); - break; - default: - abort(); - } - - cmd_ret = LTTNG_OK; -end: - info_return->status = (int32_t) rotation_state; - return cmd_ret; -} - -/* - * Command LTTNG_ROTATION_SET_SCHEDULE from the lttng-ctl library. - * - * Configure the automatic rotation parameters. - * 'activate' to true means activate the rotation schedule type with 'new_value'. - * 'activate' to false means deactivate the rotation schedule and validate that - * 'new_value' has the same value as the currently active value. - * - * Return LTTNG_OK on success or else a positive LTTNG_ERR code. - */ -int cmd_rotation_set_schedule(struct ltt_session *session, - bool activate, enum lttng_rotation_schedule_type schedule_type, - uint64_t new_value, - struct notification_thread_handle *notification_thread_handle) -{ - int ret; - uint64_t *parameter_value; - - LTTNG_ASSERT(session); - - DBG("Cmd rotate set schedule session %s", session->name); - - if (session->live_timer || !session->output_traces) { - DBG("Failing ROTATION_SET_SCHEDULE command as the rotation feature is not available for this session"); - ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE; - goto end; - } - - switch (schedule_type) { - case LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD: - parameter_value = &session->rotate_size; - break; - case LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC: - parameter_value = &session->rotate_timer_period; - if (new_value >= UINT_MAX) { - DBG("Failing ROTATION_SET_SCHEDULE command as the value requested for a periodic rotation schedule is invalid: %" PRIu64 " > %u (UINT_MAX)", - new_value, UINT_MAX); - ret = LTTNG_ERR_INVALID; - goto end; - } - break; - default: - WARN("Failing ROTATION_SET_SCHEDULE command on unknown schedule type"); - ret = LTTNG_ERR_INVALID; - goto end; - } - - /* Improper use of the API. */ - if (new_value == -1ULL) { - WARN("Failing ROTATION_SET_SCHEDULE command as the value requested is -1"); - ret = LTTNG_ERR_INVALID; - goto end; - } - - /* - * As indicated in struct ltt_session's comments, a value of == 0 means - * this schedule rotation type is not in use. - * - * Reject the command if we were asked to activate a schedule that was - * already active. - */ - if (activate && *parameter_value != 0) { - DBG("Failing ROTATION_SET_SCHEDULE (activate) command as the schedule is already active"); - ret = LTTNG_ERR_ROTATION_SCHEDULE_SET; - goto end; - } - - /* - * Reject the command if we were asked to deactivate a schedule that was - * not active. - */ - if (!activate && *parameter_value == 0) { - DBG("Failing ROTATION_SET_SCHEDULE (deactivate) command as the schedule is already inactive"); - ret = LTTNG_ERR_ROTATION_SCHEDULE_NOT_SET; - goto end; - } - - /* - * Reject the command if we were asked to deactivate a schedule that - * doesn't exist. - */ - if (!activate && *parameter_value != new_value) { - DBG("Failing ROTATION_SET_SCHEDULE (deactivate) command as an inexistant schedule was provided"); - ret = LTTNG_ERR_ROTATION_SCHEDULE_NOT_SET; - goto end; - } - - *parameter_value = activate ? new_value : 0; - - switch (schedule_type) { - case LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC: - if (activate && session->active) { - /* - * Only start the timer if the session is active, - * otherwise it will be started when the session starts. - */ - ret = timer_session_rotation_schedule_timer_start( - session, new_value); - if (ret) { - ERR("Failed to enable session rotation timer in ROTATION_SET_SCHEDULE command"); - ret = LTTNG_ERR_UNK; - goto end; - } - } else { - ret = timer_session_rotation_schedule_timer_stop( - session); - if (ret) { - ERR("Failed to disable session rotation timer in ROTATION_SET_SCHEDULE command"); - ret = LTTNG_ERR_UNK; - goto end; - } - } - break; - case LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD: - if (activate) { - ret = subscribe_session_consumed_size_rotation(session, - new_value, notification_thread_handle); - if (ret) { - ERR("Failed to enable consumed-size notification in ROTATION_SET_SCHEDULE command"); - ret = LTTNG_ERR_UNK; - goto end; - } - } else { - ret = unsubscribe_session_consumed_size_rotation(session, - notification_thread_handle); - if (ret) { - ERR("Failed to disable consumed-size notification in ROTATION_SET_SCHEDULE command"); - ret = LTTNG_ERR_UNK; - goto end; - } - - } - break; - default: - /* Would have been caught before. */ - abort(); - } - - ret = LTTNG_OK; - - goto end; - -end: - return ret; -} - -/* Wait for a given path to be removed before continuing. */ -static enum lttng_error_code wait_on_path(void *path_data) -{ - const char *shm_path = path_data; - - DBG("Waiting for the shm path at %s to be removed before completing session destruction", - shm_path); - while (true) { - int ret; - struct stat st; - - ret = stat(shm_path, &st); - if (ret) { - if (errno != ENOENT) { - PERROR("stat() returned an error while checking for the existence of the shm path"); - } else { - DBG("shm path no longer exists, completing the destruction of session"); - } - break; - } else { - if (!S_ISDIR(st.st_mode)) { - ERR("The type of shm path %s returned by stat() is not a directory; aborting the wait for shm path removal", - shm_path); - break; - } - } - usleep(SESSION_DESTROY_SHM_PATH_CHECK_DELAY_US); - } - return LTTNG_OK; -} - -/* - * Returns a pointer to a handler to run on completion of a command. - * Returns NULL if no handler has to be run for the last command executed. - */ -const struct cmd_completion_handler *cmd_pop_completion_handler(void) -{ - struct cmd_completion_handler *handler = current_completion_handler; - - current_completion_handler = NULL; - return handler; -} - -/* - * Init command subsystem. - */ -void cmd_init(void) -{ - /* - * Set network sequence index to 1 for streams to match a relayd - * socket on the consumer side. - */ - pthread_mutex_lock(&relayd_net_seq_idx_lock); - relayd_net_seq_idx = 1; - pthread_mutex_unlock(&relayd_net_seq_idx_lock); - - DBG("Command subsystem initialized"); -} diff --git a/src/bin/lttng-sessiond/cmd.cpp b/src/bin/lttng-sessiond/cmd.cpp new file mode 100644 index 000000000..4ddff45b4 --- /dev/null +++ b/src/bin/lttng-sessiond/cmd.cpp @@ -0,0 +1,5894 @@ +/* + * Copyright (C) 2012 David Goulet + * Copyright (C) 2016 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent-thread.h" +#include "agent.h" +#include "buffer-registry.h" +#include "channel.h" +#include "cmd.h" +#include "consumer.h" +#include "event-notifier-error-accounting.h" +#include "event.h" +#include "health-sessiond.h" +#include "kernel-consumer.h" +#include "kernel.h" +#include "lttng-sessiond.h" +#include "lttng-syscall.h" +#include "notification-thread-commands.h" +#include "notification-thread.h" +#include "rotate.h" +#include "rotation-thread.h" +#include "session.h" +#include "timer.h" +#include "tracker.h" +#include "utils.h" + +/* Sleep for 100ms between each check for the shm path's deletion. */ +#define SESSION_DESTROY_SHM_PATH_CHECK_DELAY_US 100000 + +struct cmd_destroy_session_reply_context { + int reply_sock_fd; + bool implicit_rotation_on_destroy; + /* + * Indicates whether or not an error occurred while launching the + * destruction of a session. + */ + enum lttng_error_code destruction_status; +}; + +static enum lttng_error_code wait_on_path(void *path); + +/* + * Command completion handler that is used by the destroy command + * when a session that has a non-default shm_path is being destroyed. + * + * See comment in cmd_destroy_session() for the rationale. + */ +static struct destroy_completion_handler { + struct cmd_completion_handler handler; + char shm_path[member_sizeof(struct ltt_session, shm_path)]; +} destroy_completion_handler = { + .handler = { + .run = wait_on_path, + .data = destroy_completion_handler.shm_path + }, + .shm_path = { 0 }, +}; + +static struct cmd_completion_handler *current_completion_handler; + +/* + * Used to keep a unique index for each relayd socket created where this value + * is associated with streams on the consumer so it can match the right relayd + * to send to. It must be accessed with the relayd_net_seq_idx_lock + * held. + */ +static pthread_mutex_t relayd_net_seq_idx_lock = PTHREAD_MUTEX_INITIALIZER; +static uint64_t relayd_net_seq_idx; + +static int validate_ust_event_name(const char *); +static int cmd_enable_event_internal(struct ltt_session *session, + const struct lttng_domain *domain, + char *channel_name, struct lttng_event *event, + char *filter_expression, + struct lttng_bytecode *filter, + struct lttng_event_exclusion *exclusion, + int wpipe); + +/* + * Create a session path used by list_lttng_sessions for the case that the + * session consumer is on the network. + */ +static int build_network_session_path(char *dst, size_t size, + struct ltt_session *session) +{ + int ret, kdata_port, udata_port; + struct lttng_uri *kuri = NULL, *uuri = NULL, *uri = NULL; + char tmp_uurl[PATH_MAX], tmp_urls[PATH_MAX]; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(dst); + + memset(tmp_urls, 0, sizeof(tmp_urls)); + memset(tmp_uurl, 0, sizeof(tmp_uurl)); + + kdata_port = udata_port = DEFAULT_NETWORK_DATA_PORT; + + if (session->kernel_session && session->kernel_session->consumer) { + kuri = &session->kernel_session->consumer->dst.net.control; + kdata_port = session->kernel_session->consumer->dst.net.data.port; + } + + if (session->ust_session && session->ust_session->consumer) { + uuri = &session->ust_session->consumer->dst.net.control; + udata_port = session->ust_session->consumer->dst.net.data.port; + } + + if (uuri == NULL && kuri == NULL) { + uri = &session->consumer->dst.net.control; + kdata_port = session->consumer->dst.net.data.port; + } else if (kuri && uuri) { + ret = uri_compare(kuri, uuri); + if (ret) { + /* Not Equal */ + uri = kuri; + /* Build uuri URL string */ + ret = uri_to_str_url(uuri, tmp_uurl, sizeof(tmp_uurl)); + if (ret < 0) { + goto error; + } + } else { + uri = kuri; + } + } else if (kuri && uuri == NULL) { + uri = kuri; + } else if (uuri && kuri == NULL) { + uri = uuri; + } + + ret = uri_to_str_url(uri, tmp_urls, sizeof(tmp_urls)); + if (ret < 0) { + goto error; + } + + /* + * Do we have a UST url set. If yes, this means we have both kernel and UST + * to print. + */ + if (*tmp_uurl != '\0') { + ret = snprintf(dst, size, "[K]: %s [data: %d] -- [U]: %s [data: %d]", + tmp_urls, kdata_port, tmp_uurl, udata_port); + } else { + int dport; + if (kuri || (!kuri && !uuri)) { + dport = kdata_port; + } else { + /* No kernel URI, use the UST port. */ + dport = udata_port; + } + ret = snprintf(dst, size, "%s [data: %d]", tmp_urls, dport); + } + +error: + return ret; +} + +/* + * Get run-time attributes if the session has been started (discarded events, + * lost packets). + */ +static int get_kernel_runtime_stats(struct ltt_session *session, + struct ltt_kernel_channel *kchan, uint64_t *discarded_events, + uint64_t *lost_packets) +{ + int ret; + + if (!session->has_been_started) { + ret = 0; + *discarded_events = 0; + *lost_packets = 0; + goto end; + } + + ret = consumer_get_discarded_events(session->id, kchan->key, + session->kernel_session->consumer, + discarded_events); + if (ret < 0) { + goto end; + } + + ret = consumer_get_lost_packets(session->id, kchan->key, + session->kernel_session->consumer, + lost_packets); + if (ret < 0) { + goto end; + } + +end: + return ret; +} + +/* + * Get run-time attributes if the session has been started (discarded events, + * lost packets). + */ +static int get_ust_runtime_stats(struct ltt_session *session, + struct ltt_ust_channel *uchan, uint64_t *discarded_events, + uint64_t *lost_packets) +{ + int ret; + struct ltt_ust_session *usess; + + if (!discarded_events || !lost_packets) { + ret = -1; + goto end; + } + + usess = session->ust_session; + LTTNG_ASSERT(discarded_events); + LTTNG_ASSERT(lost_packets); + + if (!usess || !session->has_been_started) { + *discarded_events = 0; + *lost_packets = 0; + ret = 0; + goto end; + } + + if (usess->buffer_type == LTTNG_BUFFER_PER_UID) { + ret = ust_app_uid_get_channel_runtime_stats(usess->id, + &usess->buffer_reg_uid_list, + usess->consumer, uchan->id, + uchan->attr.overwrite, + discarded_events, + lost_packets); + } else if (usess->buffer_type == LTTNG_BUFFER_PER_PID) { + ret = ust_app_pid_get_channel_runtime_stats(usess, + uchan, usess->consumer, + uchan->attr.overwrite, + discarded_events, + lost_packets); + if (ret < 0) { + goto end; + } + *discarded_events += uchan->per_pid_closed_app_discarded; + *lost_packets += uchan->per_pid_closed_app_lost; + } else { + ERR("Unsupported buffer type"); + abort(); + ret = -1; + goto end; + } + +end: + return ret; +} + +/* + * Fill lttng_channel array of all channels. + */ +static ssize_t list_lttng_channels(enum lttng_domain_type domain, + struct ltt_session *session, struct lttng_channel *channels, + struct lttng_channel_extended *chan_exts) +{ + int i = 0, ret = 0; + struct ltt_kernel_channel *kchan; + + DBG("Listing channels for session %s", session->name); + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + /* Kernel channels */ + if (session->kernel_session != NULL) { + cds_list_for_each_entry(kchan, + &session->kernel_session->channel_list.head, list) { + uint64_t discarded_events, lost_packets; + struct lttng_channel_extended *extended; + + extended = (struct lttng_channel_extended *) + kchan->channel->attr.extended.ptr; + + ret = get_kernel_runtime_stats(session, kchan, + &discarded_events, &lost_packets); + if (ret < 0) { + goto end; + } + /* Copy lttng_channel struct to array */ + memcpy(&channels[i], kchan->channel, sizeof(struct lttng_channel)); + channels[i].enabled = kchan->enabled; + chan_exts[i].discarded_events = + discarded_events; + chan_exts[i].lost_packets = lost_packets; + chan_exts[i].monitor_timer_interval = + extended->monitor_timer_interval; + chan_exts[i].blocking_timeout = 0; + i++; + } + } + break; + case LTTNG_DOMAIN_UST: + { + struct lttng_ht_iter iter; + struct ltt_ust_channel *uchan; + + rcu_read_lock(); + cds_lfht_for_each_entry(session->ust_session->domain_global.channels->ht, + &iter.iter, uchan, node.node) { + uint64_t discarded_events = 0, lost_packets = 0; + + if (lttng_strncpy(channels[i].name, uchan->name, + LTTNG_SYMBOL_NAME_LEN)) { + break; + } + channels[i].attr.overwrite = uchan->attr.overwrite; + channels[i].attr.subbuf_size = uchan->attr.subbuf_size; + channels[i].attr.num_subbuf = uchan->attr.num_subbuf; + channels[i].attr.switch_timer_interval = + uchan->attr.switch_timer_interval; + channels[i].attr.read_timer_interval = + uchan->attr.read_timer_interval; + channels[i].enabled = uchan->enabled; + channels[i].attr.tracefile_size = uchan->tracefile_size; + channels[i].attr.tracefile_count = uchan->tracefile_count; + + /* + * Map enum lttng_ust_output to enum lttng_event_output. + */ + switch (uchan->attr.output) { + case LTTNG_UST_ABI_MMAP: + channels[i].attr.output = LTTNG_EVENT_MMAP; + break; + default: + /* + * LTTNG_UST_MMAP is the only supported UST + * output mode. + */ + abort(); + break; + } + + chan_exts[i].monitor_timer_interval = + uchan->monitor_timer_interval; + chan_exts[i].blocking_timeout = + uchan->attr.u.s.blocking_timeout; + + ret = get_ust_runtime_stats(session, uchan, + &discarded_events, &lost_packets); + if (ret < 0) { + break; + } + chan_exts[i].discarded_events = discarded_events; + chan_exts[i].lost_packets = lost_packets; + i++; + } + rcu_read_unlock(); + break; + } + default: + break; + } + +end: + if (ret < 0) { + return -LTTNG_ERR_FATAL; + } else { + return LTTNG_OK; + } +} + +static int append_extended_info(const char *filter_expression, + struct lttng_event_exclusion *exclusion, + struct lttng_userspace_probe_location *probe_location, + struct lttng_payload *payload) +{ + int ret = 0; + size_t filter_len = 0; + size_t nb_exclusions = 0; + size_t userspace_probe_location_len = 0; + struct lttcomm_event_extended_header extended_header = {}; + struct lttcomm_event_extended_header *p_extended_header; + const size_t original_payload_size = payload->buffer.size; + + ret = lttng_dynamic_buffer_append(&payload->buffer, &extended_header, + sizeof(extended_header)); + if (ret) { + goto end; + } + + if (filter_expression) { + filter_len = strlen(filter_expression) + 1; + ret = lttng_dynamic_buffer_append(&payload->buffer, + filter_expression, filter_len); + if (ret) { + goto end; + } + } + + if (exclusion) { + const size_t len = exclusion->count * LTTNG_SYMBOL_NAME_LEN; + + nb_exclusions = exclusion->count; + + ret = lttng_dynamic_buffer_append( + &payload->buffer, &exclusion->names, len); + if (ret) { + goto end; + } + } + + if (probe_location) { + const size_t size_before_probe = payload->buffer.size; + + ret = lttng_userspace_probe_location_serialize(probe_location, + payload); + if (ret < 0) { + ret = -1; + goto end; + } + + userspace_probe_location_len = + payload->buffer.size - size_before_probe; + } + + /* Set header fields */ + p_extended_header = (struct lttcomm_event_extended_header *) + (payload->buffer.data + original_payload_size); + + p_extended_header->filter_len = filter_len; + p_extended_header->nb_exclusions = nb_exclusions; + p_extended_header->userspace_probe_location_len = + userspace_probe_location_len; + + ret = 0; +end: + return ret; +} + +/* + * Create a list of agent domain events. + * + * Return number of events in list on success or else a negative value. + */ +static int list_lttng_agent_events(struct agent *agt, + struct lttng_payload *payload) +{ + int nb_events = 0, ret = 0; + const struct agent_event *agent_event; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(agt); + + DBG3("Listing agent events"); + + rcu_read_lock(); + cds_lfht_for_each_entry ( + agt->events->ht, &iter.iter, agent_event, node.node) { + struct lttng_event event {}; + + event.loglevel_type = agent_event->loglevel_type; + event.loglevel = agent_event->loglevel_value; + event.enabled = AGENT_EVENT_IS_ENABLED(agent_event); + + ret = lttng_strncpy(event.name, agent_event->name, sizeof(event.name)); + if (ret) { + /* Internal error, invalid name. */ + ERR("Invalid event name while listing agent events: '%s' exceeds the maximal allowed length of %zu bytes", + agent_event->name, sizeof(event.name)); + ret = -LTTNG_ERR_UNK; + goto end; + } + + ret = lttng_dynamic_buffer_append( + &payload->buffer, &event, sizeof(event)); + if (ret) { + ERR("Failed to append event to payload"); + ret = -LTTNG_ERR_NOMEM; + goto end; + } + + nb_events++; + } + + cds_lfht_for_each_entry ( + agt->events->ht, &iter.iter, agent_event, node.node) { + /* Append extended info. */ + ret = append_extended_info(agent_event->filter_expression, NULL, + NULL, payload); + if (ret) { + ERR("Failed to append extended event info to payload"); + ret = -LTTNG_ERR_NOMEM; + goto end; + } + } + + ret = nb_events; +end: + rcu_read_unlock(); + return ret; +} + +/* + * Create a list of ust global domain events. + */ +static int list_lttng_ust_global_events(char *channel_name, + struct ltt_ust_domain_global *ust_global, + struct lttng_payload *payload) +{ + int ret = 0; + unsigned int nb_events = 0; + struct lttng_ht_iter iter; + const struct lttng_ht_node_str *node; + const struct ltt_ust_channel *uchan; + const struct ltt_ust_event *uevent; + + DBG("Listing UST global events for channel %s", channel_name); + + rcu_read_lock(); + + lttng_ht_lookup(ust_global->channels, (void *) channel_name, &iter); + node = lttng_ht_iter_get_node_str(&iter); + if (node == NULL) { + ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; + goto end; + } + + uchan = caa_container_of(&node->node, struct ltt_ust_channel, node.node); + + DBG3("Listing UST global events"); + + cds_lfht_for_each_entry(uchan->events->ht, &iter.iter, uevent, node.node) { + struct lttng_event event = {}; + + if (uevent->internal) { + continue; + } + + ret = lttng_strncpy(event.name, uevent->attr.name, sizeof(event.name)); + if (ret) { + /* Internal error, invalid name. */ + ERR("Invalid event name while listing user space tracer events: '%s' exceeds the maximal allowed length of %zu bytes", + uevent->attr.name, sizeof(event.name)); + ret = -LTTNG_ERR_UNK; + goto end; + } + + event.enabled = uevent->enabled; + + switch (uevent->attr.instrumentation) { + case LTTNG_UST_ABI_TRACEPOINT: + event.type = LTTNG_EVENT_TRACEPOINT; + break; + case LTTNG_UST_ABI_PROBE: + event.type = LTTNG_EVENT_PROBE; + break; + case LTTNG_UST_ABI_FUNCTION: + event.type = LTTNG_EVENT_FUNCTION; + break; + } + + event.loglevel = uevent->attr.loglevel; + switch (uevent->attr.loglevel_type) { + case LTTNG_UST_ABI_LOGLEVEL_ALL: + event.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; + break; + case LTTNG_UST_ABI_LOGLEVEL_RANGE: + event.loglevel_type = LTTNG_EVENT_LOGLEVEL_RANGE; + break; + case LTTNG_UST_ABI_LOGLEVEL_SINGLE: + event.loglevel_type = LTTNG_EVENT_LOGLEVEL_SINGLE; + break; + } + + if (uevent->filter) { + event.filter = 1; + } + + if (uevent->exclusion) { + event.exclusion = 1; + } + + ret = lttng_dynamic_buffer_append(&payload->buffer, &event, sizeof(event)); + if (ret) { + ERR("Failed to append event to payload"); + ret = -LTTNG_ERR_NOMEM; + goto end; + } + + nb_events++; + } + + cds_lfht_for_each_entry(uchan->events->ht, &iter.iter, uevent, node.node) { + /* Append extended info. */ + ret = append_extended_info(uevent->filter_expression, + uevent->exclusion, NULL, payload); + if (ret) { + ERR("Failed to append extended event info to payload"); + ret = -LTTNG_ERR_FATAL; + goto end; + } + } + + ret = nb_events; +end: + rcu_read_unlock(); + return ret; +} + +/* + * Fill lttng_event array of all kernel events in the channel. + */ +static int list_lttng_kernel_events(char *channel_name, + struct ltt_kernel_session *kernel_session, + struct lttng_payload *payload) +{ + int ret; + unsigned int nb_event; + const struct ltt_kernel_event *kevent; + const struct ltt_kernel_channel *kchan; + + kchan = trace_kernel_get_channel_by_name(channel_name, kernel_session); + if (kchan == NULL) { + ret = LTTNG_ERR_KERN_CHAN_NOT_FOUND; + goto error; + } + + nb_event = kchan->event_count; + + DBG("Listing events for channel %s", kchan->channel->name); + + /* Kernel channels */ + cds_list_for_each_entry(kevent, &kchan->events_list.head , list) { + struct lttng_event event = {}; + + ret = lttng_strncpy(event.name, kevent->event->name, sizeof(event.name)); + if (ret) { + /* Internal error, invalid name. */ + ERR("Invalid event name while listing kernel events: '%s' exceeds the maximal allowed length of %zu bytes", + kevent->event->name, + sizeof(event.name)); + ret = -LTTNG_ERR_UNK; + goto end; + } + + event.enabled = kevent->enabled; + event.filter = (unsigned char) !!kevent->filter_expression; + + switch (kevent->event->instrumentation) { + case LTTNG_KERNEL_ABI_TRACEPOINT: + event.type = LTTNG_EVENT_TRACEPOINT; + break; + case LTTNG_KERNEL_ABI_KRETPROBE: + event.type = LTTNG_EVENT_FUNCTION; + memcpy(&event.attr.probe, &kevent->event->u.kprobe, + sizeof(struct lttng_kernel_abi_kprobe)); + break; + case LTTNG_KERNEL_ABI_KPROBE: + event.type = LTTNG_EVENT_PROBE; + memcpy(&event.attr.probe, &kevent->event->u.kprobe, + sizeof(struct lttng_kernel_abi_kprobe)); + break; + case LTTNG_KERNEL_ABI_UPROBE: + event.type = LTTNG_EVENT_USERSPACE_PROBE; + break; + case LTTNG_KERNEL_ABI_FUNCTION: + event.type = LTTNG_EVENT_FUNCTION; + memcpy(&event.attr.ftrace, &kevent->event->u.ftrace, + sizeof(struct lttng_kernel_abi_function)); + break; + case LTTNG_KERNEL_ABI_NOOP: + event.type = LTTNG_EVENT_NOOP; + break; + case LTTNG_KERNEL_ABI_SYSCALL: + event.type = LTTNG_EVENT_SYSCALL; + break; + case LTTNG_KERNEL_ABI_ALL: + /* fall-through. */ + default: + abort(); + break; + } + + ret = lttng_dynamic_buffer_append( + &payload->buffer, &event, sizeof(event)); + if (ret) { + ERR("Failed to append event to payload"); + ret = -LTTNG_ERR_NOMEM; + goto end; + } + } + + cds_list_for_each_entry(kevent, &kchan->events_list.head , list) { + /* Append extended info. */ + ret = append_extended_info(kevent->filter_expression, NULL, + kevent->userspace_probe_location, payload); + if (ret) { + DBG("Error appending extended info message"); + ret = -LTTNG_ERR_FATAL; + goto error; + } + } + +end: + return nb_event; +error: + return ret; +} + +/* + * Add URI so the consumer output object. Set the correct path depending on the + * domain adding the default trace directory. + */ +static enum lttng_error_code add_uri_to_consumer( + const struct ltt_session *session, + struct consumer_output *consumer, + struct lttng_uri *uri, enum lttng_domain_type domain) +{ + int ret; + enum lttng_error_code ret_code = LTTNG_OK; + + LTTNG_ASSERT(uri); + + if (consumer == NULL) { + DBG("No consumer detected. Don't add URI. Stopping."); + ret_code = LTTNG_ERR_NO_CONSUMER; + goto error; + } + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + ret = lttng_strncpy(consumer->domain_subdir, + DEFAULT_KERNEL_TRACE_DIR, + sizeof(consumer->domain_subdir)); + break; + case LTTNG_DOMAIN_UST: + ret = lttng_strncpy(consumer->domain_subdir, + DEFAULT_UST_TRACE_DIR, + sizeof(consumer->domain_subdir)); + break; + default: + /* + * This case is possible is we try to add the URI to the global + * tracing session consumer object which in this case there is + * no subdir. + */ + memset(consumer->domain_subdir, 0, + sizeof(consumer->domain_subdir)); + ret = 0; + } + if (ret) { + ERR("Failed to initialize consumer output domain subdirectory"); + ret_code = LTTNG_ERR_FATAL; + goto error; + } + + switch (uri->dtype) { + case LTTNG_DST_IPV4: + case LTTNG_DST_IPV6: + DBG2("Setting network URI to consumer"); + + if (consumer->type == CONSUMER_DST_NET) { + if ((uri->stype == LTTNG_STREAM_CONTROL && + consumer->dst.net.control_isset) || + (uri->stype == LTTNG_STREAM_DATA && + consumer->dst.net.data_isset)) { + ret_code = LTTNG_ERR_URL_EXIST; + goto error; + } + } else { + memset(&consumer->dst, 0, sizeof(consumer->dst)); + } + + /* Set URI into consumer output object */ + ret = consumer_set_network_uri(session, consumer, uri); + if (ret < 0) { + ret_code = (lttng_error_code) -ret; + goto error; + } else if (ret == 1) { + /* + * URI was the same in the consumer so we do not append the subdir + * again so to not duplicate output dir. + */ + ret_code = LTTNG_OK; + goto error; + } + break; + case LTTNG_DST_PATH: + if (*uri->dst.path != '/' || strstr(uri->dst.path, "../")) { + ret_code = LTTNG_ERR_INVALID; + goto error; + } + DBG2("Setting trace directory path from URI to %s", + uri->dst.path); + memset(&consumer->dst, 0, sizeof(consumer->dst)); + + ret = lttng_strncpy(consumer->dst.session_root_path, + uri->dst.path, + sizeof(consumer->dst.session_root_path)); + if (ret) { + ret_code = LTTNG_ERR_FATAL; + goto error; + } + consumer->type = CONSUMER_DST_LOCAL; + break; + } + + ret_code = LTTNG_OK; +error: + return ret_code; +} + +/* + * Init tracing by creating trace directory and sending fds kernel consumer. + */ +static int init_kernel_tracing(struct ltt_kernel_session *session) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct consumer_socket *socket; + + LTTNG_ASSERT(session); + + rcu_read_lock(); + + if (session->consumer_fds_sent == 0 && session->consumer != NULL) { + cds_lfht_for_each_entry(session->consumer->socks->ht, &iter.iter, + socket, node.node) { + pthread_mutex_lock(socket->lock); + ret = kernel_consumer_send_session(socket, session); + pthread_mutex_unlock(socket->lock); + if (ret < 0) { + ret = LTTNG_ERR_KERN_CONSUMER_FAIL; + goto error; + } + } + } + +error: + rcu_read_unlock(); + return ret; +} + +/* + * Create a socket to the relayd using the URI. + * + * On success, the relayd_sock pointer is set to the created socket. + * Else, it remains untouched and an LTTng error code is returned. + */ +static enum lttng_error_code create_connect_relayd(struct lttng_uri *uri, + struct lttcomm_relayd_sock **relayd_sock, + struct consumer_output *consumer) +{ + int ret; + enum lttng_error_code status = LTTNG_OK; + struct lttcomm_relayd_sock *rsock; + + rsock = lttcomm_alloc_relayd_sock(uri, RELAYD_VERSION_COMM_MAJOR, + RELAYD_VERSION_COMM_MINOR); + if (!rsock) { + status = LTTNG_ERR_FATAL; + goto error; + } + + /* + * Connect to relayd so we can proceed with a session creation. This call + * can possibly block for an arbitrary amount of time to set the health + * state to be in poll execution. + */ + health_poll_entry(); + ret = relayd_connect(rsock); + health_poll_exit(); + if (ret < 0) { + ERR("Unable to reach lttng-relayd"); + status = LTTNG_ERR_RELAYD_CONNECT_FAIL; + goto free_sock; + } + + /* Create socket for control stream. */ + if (uri->stype == LTTNG_STREAM_CONTROL) { + uint64_t result_flags; + + DBG3("Creating relayd stream socket from URI"); + + /* Check relayd version */ + ret = relayd_version_check(rsock); + if (ret == LTTNG_ERR_RELAYD_VERSION_FAIL) { + status = LTTNG_ERR_RELAYD_VERSION_FAIL; + goto close_sock; + } else if (ret < 0) { + ERR("Unable to reach lttng-relayd"); + status = LTTNG_ERR_RELAYD_CONNECT_FAIL; + goto close_sock; + } + consumer->relay_major_version = rsock->major; + consumer->relay_minor_version = rsock->minor; + ret = relayd_get_configuration(rsock, 0, + &result_flags); + if (ret < 0) { + ERR("Unable to get relayd configuration"); + status = LTTNG_ERR_RELAYD_CONNECT_FAIL; + goto close_sock; + } + if (result_flags & LTTCOMM_RELAYD_CONFIGURATION_FLAG_CLEAR_ALLOWED) { + consumer->relay_allows_clear = true; + } + } else if (uri->stype == LTTNG_STREAM_DATA) { + DBG3("Creating relayd data socket from URI"); + } else { + /* Command is not valid */ + ERR("Relayd invalid stream type: %d", uri->stype); + status = LTTNG_ERR_INVALID; + goto close_sock; + } + + *relayd_sock = rsock; + + return status; + +close_sock: + /* The returned value is not useful since we are on an error path. */ + (void) relayd_close(rsock); +free_sock: + free(rsock); +error: + return status; +} + +/* + * Connect to the relayd using URI and send the socket to the right consumer. + * + * The consumer socket lock must be held by the caller. + * + * Returns LTTNG_OK on success or an LTTng error code on failure. + */ +static enum lttng_error_code send_consumer_relayd_socket( + unsigned int session_id, + struct lttng_uri *relayd_uri, + struct consumer_output *consumer, + struct consumer_socket *consumer_sock, + const char *session_name, const char *hostname, + const char *base_path, int session_live_timer, + const uint64_t *current_chunk_id, + time_t session_creation_time, + bool session_name_contains_creation_time) +{ + int ret; + struct lttcomm_relayd_sock *rsock = NULL; + enum lttng_error_code status; + + /* Connect to relayd and make version check if uri is the control. */ + status = create_connect_relayd(relayd_uri, &rsock, consumer); + if (status != LTTNG_OK) { + goto relayd_comm_error; + } + LTTNG_ASSERT(rsock); + + /* Set the network sequence index if not set. */ + if (consumer->net_seq_index == (uint64_t) -1ULL) { + pthread_mutex_lock(&relayd_net_seq_idx_lock); + /* + * Increment net_seq_idx because we are about to transfer the + * new relayd socket to the consumer. + * Assign unique key so the consumer can match streams. + */ + consumer->net_seq_index = ++relayd_net_seq_idx; + pthread_mutex_unlock(&relayd_net_seq_idx_lock); + } + + /* Send relayd socket to consumer. */ + ret = consumer_send_relayd_socket(consumer_sock, rsock, consumer, + relayd_uri->stype, session_id, + session_name, hostname, base_path, + session_live_timer, current_chunk_id, + session_creation_time, session_name_contains_creation_time); + if (ret < 0) { + status = LTTNG_ERR_ENABLE_CONSUMER_FAIL; + goto close_sock; + } + + /* Flag that the corresponding socket was sent. */ + if (relayd_uri->stype == LTTNG_STREAM_CONTROL) { + consumer_sock->control_sock_sent = 1; + } else if (relayd_uri->stype == LTTNG_STREAM_DATA) { + consumer_sock->data_sock_sent = 1; + } + + /* + * Close socket which was dup on the consumer side. The session daemon does + * NOT keep track of the relayd socket(s) once transfer to the consumer. + */ + +close_sock: + if (status != LTTNG_OK) { + /* + * The consumer output for this session should not be used anymore + * since the relayd connection failed thus making any tracing or/and + * streaming not usable. + */ + consumer->enabled = 0; + } + (void) relayd_close(rsock); + free(rsock); + +relayd_comm_error: + return status; +} + +/* + * Send both relayd sockets to a specific consumer and domain. This is a + * helper function to facilitate sending the information to the consumer for a + * session. + * + * The consumer socket lock must be held by the caller. + * + * Returns LTTNG_OK, or an LTTng error code on failure. + */ +static enum lttng_error_code send_consumer_relayd_sockets( + enum lttng_domain_type domain, + unsigned int session_id, struct consumer_output *consumer, + struct consumer_socket *sock, const char *session_name, + const char *hostname, const char *base_path, int session_live_timer, + const uint64_t *current_chunk_id, time_t session_creation_time, + bool session_name_contains_creation_time) +{ + enum lttng_error_code status = LTTNG_OK; + + LTTNG_ASSERT(consumer); + LTTNG_ASSERT(sock); + + /* Sending control relayd socket. */ + if (!sock->control_sock_sent) { + status = send_consumer_relayd_socket(session_id, + &consumer->dst.net.control, consumer, sock, + session_name, hostname, base_path, session_live_timer, + current_chunk_id, session_creation_time, + session_name_contains_creation_time); + if (status != LTTNG_OK) { + goto error; + } + } + + /* Sending data relayd socket. */ + if (!sock->data_sock_sent) { + status = send_consumer_relayd_socket(session_id, + &consumer->dst.net.data, consumer, sock, + session_name, hostname, base_path, session_live_timer, + current_chunk_id, session_creation_time, + session_name_contains_creation_time); + if (status != LTTNG_OK) { + goto error; + } + } + +error: + return status; +} + +/* + * Setup relayd connections for a tracing session. First creates the socket to + * the relayd and send them to the right domain consumer. Consumer type MUST be + * network. + */ +int cmd_setup_relayd(struct ltt_session *session) +{ + int ret = LTTNG_OK; + struct ltt_ust_session *usess; + struct ltt_kernel_session *ksess; + struct consumer_socket *socket; + struct lttng_ht_iter iter; + LTTNG_OPTIONAL(uint64_t) current_chunk_id = {}; + + LTTNG_ASSERT(session); + + usess = session->ust_session; + ksess = session->kernel_session; + + DBG("Setting relayd for session %s", session->name); + + rcu_read_lock(); + if (session->current_trace_chunk) { + enum lttng_trace_chunk_status status = lttng_trace_chunk_get_id( + session->current_trace_chunk, ¤t_chunk_id.value); + + if (status == LTTNG_TRACE_CHUNK_STATUS_OK) { + current_chunk_id.is_set = true; + } else { + ERR("Failed to get current trace chunk id"); + ret = LTTNG_ERR_UNK; + goto error; + } + } + + if (usess && usess->consumer && usess->consumer->type == CONSUMER_DST_NET + && usess->consumer->enabled) { + /* For each consumer socket, send relayd sockets */ + cds_lfht_for_each_entry(usess->consumer->socks->ht, &iter.iter, + socket, node.node) { + pthread_mutex_lock(socket->lock); + ret = send_consumer_relayd_sockets(LTTNG_DOMAIN_UST, session->id, + usess->consumer, socket, + session->name, session->hostname, + session->base_path, + session->live_timer, + current_chunk_id.is_set ? ¤t_chunk_id.value : NULL, + session->creation_time, + session->name_contains_creation_time); + pthread_mutex_unlock(socket->lock); + if (ret != LTTNG_OK) { + goto error; + } + /* Session is now ready for network streaming. */ + session->net_handle = 1; + } + session->consumer->relay_major_version = + usess->consumer->relay_major_version; + session->consumer->relay_minor_version = + usess->consumer->relay_minor_version; + session->consumer->relay_allows_clear = + usess->consumer->relay_allows_clear; + } + + if (ksess && ksess->consumer && ksess->consumer->type == CONSUMER_DST_NET + && ksess->consumer->enabled) { + cds_lfht_for_each_entry(ksess->consumer->socks->ht, &iter.iter, + socket, node.node) { + pthread_mutex_lock(socket->lock); + ret = send_consumer_relayd_sockets(LTTNG_DOMAIN_KERNEL, session->id, + ksess->consumer, socket, + session->name, session->hostname, + session->base_path, + session->live_timer, + current_chunk_id.is_set ? ¤t_chunk_id.value : NULL, + session->creation_time, + session->name_contains_creation_time); + pthread_mutex_unlock(socket->lock); + if (ret != LTTNG_OK) { + goto error; + } + /* Session is now ready for network streaming. */ + session->net_handle = 1; + } + session->consumer->relay_major_version = + ksess->consumer->relay_major_version; + session->consumer->relay_minor_version = + ksess->consumer->relay_minor_version; + session->consumer->relay_allows_clear = + ksess->consumer->relay_allows_clear; + } + +error: + rcu_read_unlock(); + return ret; +} + +/* + * Start a kernel session by opening all necessary streams. + */ +int start_kernel_session(struct ltt_kernel_session *ksess) +{ + int ret; + struct ltt_kernel_channel *kchan; + + /* Open kernel metadata */ + if (ksess->metadata == NULL && ksess->output_traces) { + ret = kernel_open_metadata(ksess); + if (ret < 0) { + ret = LTTNG_ERR_KERN_META_FAIL; + goto error; + } + } + + /* Open kernel metadata stream */ + if (ksess->metadata && ksess->metadata_stream_fd < 0) { + ret = kernel_open_metadata_stream(ksess); + if (ret < 0) { + ERR("Kernel create metadata stream failed"); + ret = LTTNG_ERR_KERN_STREAM_FAIL; + goto error; + } + } + + /* For each channel */ + cds_list_for_each_entry(kchan, &ksess->channel_list.head, list) { + if (kchan->stream_count == 0) { + ret = kernel_open_channel_stream(kchan); + if (ret < 0) { + ret = LTTNG_ERR_KERN_STREAM_FAIL; + goto error; + } + /* Update the stream global counter */ + ksess->stream_count_global += ret; + } + } + + /* Setup kernel consumer socket and send fds to it */ + ret = init_kernel_tracing(ksess); + if (ret != 0) { + ret = LTTNG_ERR_KERN_START_FAIL; + goto error; + } + + /* This start the kernel tracing */ + ret = kernel_start_session(ksess); + if (ret < 0) { + ret = LTTNG_ERR_KERN_START_FAIL; + goto error; + } + + /* Quiescent wait after starting trace */ + kernel_wait_quiescent(); + + ksess->active = 1; + + ret = LTTNG_OK; + +error: + return ret; +} + +int stop_kernel_session(struct ltt_kernel_session *ksess) +{ + struct ltt_kernel_channel *kchan; + bool error_occurred = false; + int ret; + + if (!ksess || !ksess->active) { + return LTTNG_OK; + } + DBG("Stopping kernel tracing"); + + ret = kernel_stop_session(ksess); + if (ret < 0) { + ret = LTTNG_ERR_KERN_STOP_FAIL; + goto error; + } + + kernel_wait_quiescent(); + + /* Flush metadata after stopping (if exists) */ + if (ksess->metadata_stream_fd >= 0) { + ret = kernel_metadata_flush_buffer(ksess->metadata_stream_fd); + if (ret < 0) { + ERR("Kernel metadata flush failed"); + error_occurred = true; + } + } + + /* Flush all buffers after stopping */ + cds_list_for_each_entry(kchan, &ksess->channel_list.head, list) { + ret = kernel_flush_buffer(kchan); + if (ret < 0) { + ERR("Kernel flush buffer error"); + error_occurred = true; + } + } + + ksess->active = 0; + if (error_occurred) { + ret = LTTNG_ERR_UNK; + } else { + ret = LTTNG_OK; + } +error: + return ret; +} + +/* + * Command LTTNG_DISABLE_CHANNEL processed by the client thread. + */ +int cmd_disable_channel(struct ltt_session *session, + enum lttng_domain_type domain, char *channel_name) +{ + int ret; + struct ltt_ust_session *usess; + + usess = session->ust_session; + + rcu_read_lock(); + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + { + ret = channel_kernel_disable(session->kernel_session, + channel_name); + if (ret != LTTNG_OK) { + goto error; + } + + kernel_wait_quiescent(); + break; + } + case LTTNG_DOMAIN_UST: + { + struct ltt_ust_channel *uchan; + struct lttng_ht *chan_ht; + + chan_ht = usess->domain_global.channels; + + uchan = trace_ust_find_channel_by_name(chan_ht, channel_name); + if (uchan == NULL) { + ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; + goto error; + } + + ret = channel_ust_disable(usess, uchan); + if (ret != LTTNG_OK) { + goto error; + } + break; + } + default: + ret = LTTNG_ERR_UNKNOWN_DOMAIN; + goto error; + } + + ret = LTTNG_OK; + +error: + rcu_read_unlock(); + return ret; +} + +/* + * Command LTTNG_ENABLE_CHANNEL processed by the client thread. + * + * The wpipe arguments is used as a notifier for the kernel thread. + */ +int cmd_enable_channel(struct ltt_session *session, + const struct lttng_domain *domain, const struct lttng_channel *_attr, int wpipe) +{ + int ret; + struct ltt_ust_session *usess = session->ust_session; + struct lttng_ht *chan_ht; + size_t len; + struct lttng_channel attr; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(_attr); + LTTNG_ASSERT(domain); + + attr = *_attr; + len = lttng_strnlen(attr.name, sizeof(attr.name)); + + /* Validate channel name */ + if (attr.name[0] == '.' || + memchr(attr.name, '/', len) != NULL) { + ret = LTTNG_ERR_INVALID_CHANNEL_NAME; + goto end; + } + + DBG("Enabling channel %s for session %s", attr.name, session->name); + + rcu_read_lock(); + + /* + * If the session is a live session, remove the switch timer, the + * live timer does the same thing but sends also synchronisation + * beacons for inactive streams. + */ + if (session->live_timer > 0) { + attr.attr.live_timer_interval = session->live_timer; + attr.attr.switch_timer_interval = 0; + } + + /* Check for feature support */ + switch (domain->type) { + case LTTNG_DOMAIN_KERNEL: + { + if (kernel_supports_ring_buffer_snapshot_sample_positions() != 1) { + /* Sampling position of buffer is not supported */ + WARN("Kernel tracer does not support buffer monitoring. " + "Setting the monitor interval timer to 0 " + "(disabled) for channel '%s' of session '%s'", + attr.name, session->name); + lttng_channel_set_monitor_timer_interval(&attr, 0); + } + break; + } + case LTTNG_DOMAIN_UST: + break; + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + if (!agent_tracing_is_enabled()) { + DBG("Attempted to enable a channel in an agent domain but the agent thread is not running"); + ret = LTTNG_ERR_AGENT_TRACING_DISABLED; + goto error; + } + break; + default: + ret = LTTNG_ERR_UNKNOWN_DOMAIN; + goto error; + } + + switch (domain->type) { + case LTTNG_DOMAIN_KERNEL: + { + struct ltt_kernel_channel *kchan; + + kchan = trace_kernel_get_channel_by_name(attr.name, + session->kernel_session); + if (kchan == NULL) { + /* + * Don't try to create a channel if the session has been started at + * some point in time before. The tracer does not allow it. + */ + if (session->has_been_started) { + ret = LTTNG_ERR_TRACE_ALREADY_STARTED; + goto error; + } + + if (session->snapshot.nb_output > 0 || + session->snapshot_mode) { + /* Enforce mmap output for snapshot sessions. */ + attr.attr.output = LTTNG_EVENT_MMAP; + } + ret = channel_kernel_create(session->kernel_session, &attr, wpipe); + if (attr.name[0] != '\0') { + session->kernel_session->has_non_default_channel = 1; + } + } else { + ret = channel_kernel_enable(session->kernel_session, kchan); + } + + if (ret != LTTNG_OK) { + goto error; + } + + kernel_wait_quiescent(); + break; + } + case LTTNG_DOMAIN_UST: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + { + struct ltt_ust_channel *uchan; + + /* + * FIXME + * + * Current agent implementation limitations force us to allow + * only one channel at once in "agent" subdomains. Each + * subdomain has a default channel name which must be strictly + * adhered to. + */ + if (domain->type == LTTNG_DOMAIN_JUL) { + if (strncmp(attr.name, DEFAULT_JUL_CHANNEL_NAME, + LTTNG_SYMBOL_NAME_LEN)) { + ret = LTTNG_ERR_INVALID_CHANNEL_NAME; + goto error; + } + } else if (domain->type == LTTNG_DOMAIN_LOG4J) { + if (strncmp(attr.name, DEFAULT_LOG4J_CHANNEL_NAME, + LTTNG_SYMBOL_NAME_LEN)) { + ret = LTTNG_ERR_INVALID_CHANNEL_NAME; + goto error; + } + } else if (domain->type == LTTNG_DOMAIN_PYTHON) { + if (strncmp(attr.name, DEFAULT_PYTHON_CHANNEL_NAME, + LTTNG_SYMBOL_NAME_LEN)) { + ret = LTTNG_ERR_INVALID_CHANNEL_NAME; + goto error; + } + } + + chan_ht = usess->domain_global.channels; + + uchan = trace_ust_find_channel_by_name(chan_ht, attr.name); + if (uchan == NULL) { + /* + * Don't try to create a channel if the session has been started at + * some point in time before. The tracer does not allow it. + */ + if (session->has_been_started) { + ret = LTTNG_ERR_TRACE_ALREADY_STARTED; + goto error; + } + + ret = channel_ust_create(usess, &attr, domain->buf_type); + if (attr.name[0] != '\0') { + usess->has_non_default_channel = 1; + } + } else { + ret = channel_ust_enable(usess, uchan); + } + break; + } + default: + ret = LTTNG_ERR_UNKNOWN_DOMAIN; + goto error; + } + + if (ret == LTTNG_OK && attr.attr.output != LTTNG_EVENT_MMAP) { + session->has_non_mmap_channel = true; + } +error: + rcu_read_unlock(); +end: + return ret; +} + +enum lttng_error_code cmd_process_attr_tracker_get_tracking_policy( + struct ltt_session *session, + enum lttng_domain_type domain, + enum lttng_process_attr process_attr, + enum lttng_tracking_policy *policy) +{ + enum lttng_error_code ret_code = LTTNG_OK; + const struct process_attr_tracker *tracker; + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + if (!session->kernel_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + tracker = kernel_get_process_attr_tracker( + session->kernel_session, process_attr); + break; + case LTTNG_DOMAIN_UST: + if (!session->ust_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + tracker = trace_ust_get_process_attr_tracker( + session->ust_session, process_attr); + break; + default: + ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; + goto end; + } + if (tracker) { + *policy = process_attr_tracker_get_tracking_policy(tracker); + } else { + ret_code = LTTNG_ERR_INVALID; + } +end: + return ret_code; +} + +enum lttng_error_code cmd_process_attr_tracker_set_tracking_policy( + struct ltt_session *session, + enum lttng_domain_type domain, + enum lttng_process_attr process_attr, + enum lttng_tracking_policy policy) +{ + enum lttng_error_code ret_code = LTTNG_OK; + + switch (policy) { + case LTTNG_TRACKING_POLICY_INCLUDE_SET: + case LTTNG_TRACKING_POLICY_EXCLUDE_ALL: + case LTTNG_TRACKING_POLICY_INCLUDE_ALL: + break; + default: + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + if (!session->kernel_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + ret_code = kernel_process_attr_tracker_set_tracking_policy( + session->kernel_session, process_attr, policy); + break; + case LTTNG_DOMAIN_UST: + if (!session->ust_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + ret_code = trace_ust_process_attr_tracker_set_tracking_policy( + session->ust_session, process_attr, policy); + break; + default: + ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; + break; + } +end: + return ret_code; +} + +enum lttng_error_code cmd_process_attr_tracker_inclusion_set_add_value( + struct ltt_session *session, + enum lttng_domain_type domain, + enum lttng_process_attr process_attr, + const struct process_attr_value *value) +{ + enum lttng_error_code ret_code = LTTNG_OK; + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + if (!session->kernel_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + ret_code = kernel_process_attr_tracker_inclusion_set_add_value( + session->kernel_session, process_attr, value); + break; + case LTTNG_DOMAIN_UST: + if (!session->ust_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + ret_code = trace_ust_process_attr_tracker_inclusion_set_add_value( + session->ust_session, process_attr, value); + break; + default: + ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; + break; + } +end: + return ret_code; +} + +enum lttng_error_code cmd_process_attr_tracker_inclusion_set_remove_value( + struct ltt_session *session, + enum lttng_domain_type domain, + enum lttng_process_attr process_attr, + const struct process_attr_value *value) +{ + enum lttng_error_code ret_code = LTTNG_OK; + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + if (!session->kernel_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + ret_code = kernel_process_attr_tracker_inclusion_set_remove_value( + session->kernel_session, process_attr, value); + break; + case LTTNG_DOMAIN_UST: + if (!session->ust_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + ret_code = trace_ust_process_attr_tracker_inclusion_set_remove_value( + session->ust_session, process_attr, value); + break; + default: + ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; + break; + } +end: + return ret_code; +} + +enum lttng_error_code cmd_process_attr_tracker_get_inclusion_set( + struct ltt_session *session, + enum lttng_domain_type domain, + enum lttng_process_attr process_attr, + struct lttng_process_attr_values **values) +{ + enum lttng_error_code ret_code = LTTNG_OK; + const struct process_attr_tracker *tracker; + enum process_attr_tracker_status status; + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + if (!session->kernel_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + tracker = kernel_get_process_attr_tracker( + session->kernel_session, process_attr); + break; + case LTTNG_DOMAIN_UST: + if (!session->ust_session) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + tracker = trace_ust_get_process_attr_tracker( + session->ust_session, process_attr); + break; + default: + ret_code = LTTNG_ERR_UNSUPPORTED_DOMAIN; + goto end; + } + + if (!tracker) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + status = process_attr_tracker_get_inclusion_set(tracker, values); + switch (status) { + case PROCESS_ATTR_TRACKER_STATUS_OK: + ret_code = LTTNG_OK; + break; + case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: + ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; + break; + case PROCESS_ATTR_TRACKER_STATUS_ERROR: + ret_code = LTTNG_ERR_NOMEM; + break; + default: + ret_code = LTTNG_ERR_UNK; + break; + } + +end: + return ret_code; +} + +/* + * Command LTTNG_DISABLE_EVENT processed by the client thread. + */ +int cmd_disable_event(struct ltt_session *session, + enum lttng_domain_type domain, const char *channel_name, + const struct lttng_event *event) +{ + int ret; + const char *event_name; + + DBG("Disable event command for event \'%s\'", event->name); + + event_name = event->name; + + /* Error out on unhandled search criteria */ + if (event->loglevel_type || event->loglevel != -1 || event->enabled + || event->pid || event->filter || event->exclusion) { + ret = LTTNG_ERR_UNK; + goto error; + } + + rcu_read_lock(); + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + { + struct ltt_kernel_channel *kchan; + struct ltt_kernel_session *ksess; + + ksess = session->kernel_session; + + /* + * If a non-default channel has been created in the + * session, explicitely require that -c chan_name needs + * to be provided. + */ + if (ksess->has_non_default_channel && channel_name[0] == '\0') { + ret = LTTNG_ERR_NEED_CHANNEL_NAME; + goto error_unlock; + } + + kchan = trace_kernel_get_channel_by_name(channel_name, ksess); + if (kchan == NULL) { + ret = LTTNG_ERR_KERN_CHAN_NOT_FOUND; + goto error_unlock; + } + + switch (event->type) { + case LTTNG_EVENT_ALL: + case LTTNG_EVENT_TRACEPOINT: + case LTTNG_EVENT_SYSCALL: + case LTTNG_EVENT_PROBE: + case LTTNG_EVENT_FUNCTION: + case LTTNG_EVENT_FUNCTION_ENTRY:/* fall-through */ + if (event_name[0] == '\0') { + ret = event_kernel_disable_event(kchan, + NULL, event->type); + } else { + ret = event_kernel_disable_event(kchan, + event_name, event->type); + } + if (ret != LTTNG_OK) { + goto error_unlock; + } + break; + default: + ret = LTTNG_ERR_UNK; + goto error_unlock; + } + + kernel_wait_quiescent(); + break; + } + case LTTNG_DOMAIN_UST: + { + struct ltt_ust_channel *uchan; + struct ltt_ust_session *usess; + + usess = session->ust_session; + + if (validate_ust_event_name(event_name)) { + ret = LTTNG_ERR_INVALID_EVENT_NAME; + goto error_unlock; + } + + /* + * If a non-default channel has been created in the + * session, explicitly require that -c chan_name needs + * to be provided. + */ + if (usess->has_non_default_channel && channel_name[0] == '\0') { + ret = LTTNG_ERR_NEED_CHANNEL_NAME; + goto error_unlock; + } + + uchan = trace_ust_find_channel_by_name(usess->domain_global.channels, + channel_name); + if (uchan == NULL) { + ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; + goto error_unlock; + } + + switch (event->type) { + case LTTNG_EVENT_ALL: + /* + * An empty event name means that everything + * should be disabled. + */ + if (event->name[0] == '\0') { + ret = event_ust_disable_all_tracepoints(usess, uchan); + } else { + ret = event_ust_disable_tracepoint(usess, uchan, + event_name); + } + if (ret != LTTNG_OK) { + goto error_unlock; + } + break; + default: + ret = LTTNG_ERR_UNK; + goto error_unlock; + } + + DBG3("Disable UST event %s in channel %s completed", event_name, + channel_name); + break; + } + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_PYTHON: + { + struct agent *agt; + struct ltt_ust_session *usess = session->ust_session; + + LTTNG_ASSERT(usess); + + switch (event->type) { + case LTTNG_EVENT_ALL: + break; + default: + ret = LTTNG_ERR_UNK; + goto error_unlock; + } + + agt = trace_ust_find_agent(usess, domain); + if (!agt) { + ret = -LTTNG_ERR_UST_EVENT_NOT_FOUND; + goto error_unlock; + } + /* + * An empty event name means that everything + * should be disabled. + */ + if (event->name[0] == '\0') { + ret = event_agent_disable_all(usess, agt); + } else { + ret = event_agent_disable(usess, agt, event_name); + } + if (ret != LTTNG_OK) { + goto error_unlock; + } + + break; + } + default: + ret = LTTNG_ERR_UND; + goto error_unlock; + } + + ret = LTTNG_OK; + +error_unlock: + rcu_read_unlock(); +error: + return ret; +} + +/* + * Command LTTNG_ADD_CONTEXT processed by the client thread. + */ +int cmd_add_context(struct ltt_session *session, enum lttng_domain_type domain, + char *channel_name, const struct lttng_event_context *ctx, int kwpipe) +{ + int ret, chan_kern_created = 0, chan_ust_created = 0; + char *app_ctx_provider_name = NULL, *app_ctx_name = NULL; + + /* + * Don't try to add a context if the session has been started at + * some point in time before. The tracer does not allow it and would + * result in a corrupted trace. + */ + if (session->has_been_started) { + ret = LTTNG_ERR_TRACE_ALREADY_STARTED; + goto end; + } + + if (ctx->ctx == LTTNG_EVENT_CONTEXT_APP_CONTEXT) { + app_ctx_provider_name = ctx->u.app_ctx.provider_name; + app_ctx_name = ctx->u.app_ctx.ctx_name; + } + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + LTTNG_ASSERT(session->kernel_session); + + if (session->kernel_session->channel_count == 0) { + /* Create default channel */ + ret = channel_kernel_create(session->kernel_session, NULL, kwpipe); + if (ret != LTTNG_OK) { + goto error; + } + chan_kern_created = 1; + } + /* Add kernel context to kernel tracer */ + ret = context_kernel_add(session->kernel_session, ctx, channel_name); + if (ret != LTTNG_OK) { + goto error; + } + break; + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + { + /* + * Validate channel name. + * If no channel name is given and the domain is JUL or LOG4J, + * set it to the appropriate domain-specific channel name. If + * a name is provided but does not match the expexted channel + * name, return an error. + */ + if (domain == LTTNG_DOMAIN_JUL && *channel_name && + strcmp(channel_name, + DEFAULT_JUL_CHANNEL_NAME)) { + ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; + goto error; + } else if (domain == LTTNG_DOMAIN_LOG4J && *channel_name && + strcmp(channel_name, + DEFAULT_LOG4J_CHANNEL_NAME)) { + ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; + goto error; + } + /* break is _not_ missing here. */ + } + case LTTNG_DOMAIN_UST: + { + struct ltt_ust_session *usess = session->ust_session; + unsigned int chan_count; + + LTTNG_ASSERT(usess); + + chan_count = lttng_ht_get_count(usess->domain_global.channels); + if (chan_count == 0) { + struct lttng_channel *attr; + /* Create default channel */ + attr = channel_new_default_attr(domain, usess->buffer_type); + if (attr == NULL) { + ret = LTTNG_ERR_FATAL; + goto error; + } + + ret = channel_ust_create(usess, attr, usess->buffer_type); + if (ret != LTTNG_OK) { + free(attr); + goto error; + } + channel_attr_destroy(attr); + chan_ust_created = 1; + } + + ret = context_ust_add(usess, domain, ctx, channel_name); + free(app_ctx_provider_name); + free(app_ctx_name); + app_ctx_name = NULL; + app_ctx_provider_name = NULL; + if (ret != LTTNG_OK) { + goto error; + } + break; + } + default: + ret = LTTNG_ERR_UND; + goto error; + } + + ret = LTTNG_OK; + goto end; + +error: + if (chan_kern_created) { + struct ltt_kernel_channel *kchan = + trace_kernel_get_channel_by_name(DEFAULT_CHANNEL_NAME, + session->kernel_session); + /* Created previously, this should NOT fail. */ + LTTNG_ASSERT(kchan); + kernel_destroy_channel(kchan); + } + + if (chan_ust_created) { + struct ltt_ust_channel *uchan = + trace_ust_find_channel_by_name( + session->ust_session->domain_global.channels, + DEFAULT_CHANNEL_NAME); + /* Created previously, this should NOT fail. */ + LTTNG_ASSERT(uchan); + /* Remove from the channel list of the session. */ + trace_ust_delete_channel(session->ust_session->domain_global.channels, + uchan); + trace_ust_destroy_channel(uchan); + } +end: + free(app_ctx_provider_name); + free(app_ctx_name); + return ret; +} + +static inline bool name_starts_with(const char *name, const char *prefix) +{ + const size_t max_cmp_len = std::min(strlen(prefix), (size_t) LTTNG_SYMBOL_NAME_LEN); + + return !strncmp(name, prefix, max_cmp_len); +} + +/* Perform userspace-specific event name validation */ +static int validate_ust_event_name(const char *name) +{ + int ret = 0; + + if (!name) { + ret = -1; + goto end; + } + + /* + * Check name against all internal UST event component namespaces used + * by the agents. + */ + if (name_starts_with(name, DEFAULT_JUL_EVENT_COMPONENT) || + name_starts_with(name, DEFAULT_LOG4J_EVENT_COMPONENT) || + name_starts_with(name, DEFAULT_PYTHON_EVENT_COMPONENT)) { + ret = -1; + } + +end: + return ret; +} + +/* + * Internal version of cmd_enable_event() with a supplemental + * "internal_event" flag which is used to enable internal events which should + * be hidden from clients. Such events are used in the agent implementation to + * enable the events through which all "agent" events are funeled. + */ +static int _cmd_enable_event(struct ltt_session *session, + const struct lttng_domain *domain, + char *channel_name, struct lttng_event *event, + char *filter_expression, + struct lttng_bytecode *filter, + struct lttng_event_exclusion *exclusion, + int wpipe, bool internal_event) +{ + int ret = 0, channel_created = 0; + struct lttng_channel *attr = NULL; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(event); + LTTNG_ASSERT(channel_name); + + /* If we have a filter, we must have its filter expression */ + LTTNG_ASSERT(!(!!filter_expression ^ !!filter)); + + /* Normalize event name as a globbing pattern */ + strutils_normalize_star_glob_pattern(event->name); + + /* Normalize exclusion names as globbing patterns */ + if (exclusion) { + size_t i; + + for (i = 0; i < exclusion->count; i++) { + char *name = LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, i); + + strutils_normalize_star_glob_pattern(name); + } + } + + DBG("Enable event command for event \'%s\'", event->name); + + rcu_read_lock(); + + switch (domain->type) { + case LTTNG_DOMAIN_KERNEL: + { + struct ltt_kernel_channel *kchan; + + /* + * If a non-default channel has been created in the + * session, explicitely require that -c chan_name needs + * to be provided. + */ + if (session->kernel_session->has_non_default_channel + && channel_name[0] == '\0') { + ret = LTTNG_ERR_NEED_CHANNEL_NAME; + goto error; + } + + kchan = trace_kernel_get_channel_by_name(channel_name, + session->kernel_session); + if (kchan == NULL) { + attr = channel_new_default_attr(LTTNG_DOMAIN_KERNEL, + LTTNG_BUFFER_GLOBAL); + if (attr == NULL) { + ret = LTTNG_ERR_FATAL; + goto error; + } + if (lttng_strncpy(attr->name, channel_name, + sizeof(attr->name))) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + ret = cmd_enable_channel(session, domain, attr, wpipe); + if (ret != LTTNG_OK) { + goto error; + } + channel_created = 1; + } + + /* Get the newly created kernel channel pointer */ + kchan = trace_kernel_get_channel_by_name(channel_name, + session->kernel_session); + if (kchan == NULL) { + /* This sould not happen... */ + ret = LTTNG_ERR_FATAL; + goto error; + } + + switch (event->type) { + case LTTNG_EVENT_ALL: + { + char *filter_expression_a = NULL; + struct lttng_bytecode *filter_a = NULL; + + /* + * We need to duplicate filter_expression and filter, + * because ownership is passed to first enable + * event. + */ + if (filter_expression) { + filter_expression_a = strdup(filter_expression); + if (!filter_expression_a) { + ret = LTTNG_ERR_FATAL; + goto error; + } + } + if (filter) { + filter_a = (lttng_bytecode *) zmalloc(sizeof(*filter_a) + filter->len); + if (!filter_a) { + free(filter_expression_a); + ret = LTTNG_ERR_FATAL; + goto error; + } + memcpy(filter_a, filter, sizeof(*filter_a) + filter->len); + } + event->type = LTTNG_EVENT_TRACEPOINT; /* Hack */ + ret = event_kernel_enable_event(kchan, event, + filter_expression, filter); + /* We have passed ownership */ + filter_expression = NULL; + filter = NULL; + if (ret != LTTNG_OK) { + if (channel_created) { + /* Let's not leak a useless channel. */ + kernel_destroy_channel(kchan); + } + free(filter_expression_a); + free(filter_a); + goto error; + } + event->type = LTTNG_EVENT_SYSCALL; /* Hack */ + ret = event_kernel_enable_event(kchan, event, + filter_expression_a, filter_a); + /* We have passed ownership */ + filter_expression_a = NULL; + filter_a = NULL; + if (ret != LTTNG_OK) { + goto error; + } + break; + } + case LTTNG_EVENT_PROBE: + case LTTNG_EVENT_USERSPACE_PROBE: + case LTTNG_EVENT_FUNCTION: + case LTTNG_EVENT_FUNCTION_ENTRY: + case LTTNG_EVENT_TRACEPOINT: + ret = event_kernel_enable_event(kchan, event, + filter_expression, filter); + /* We have passed ownership */ + filter_expression = NULL; + filter = NULL; + if (ret != LTTNG_OK) { + if (channel_created) { + /* Let's not leak a useless channel. */ + kernel_destroy_channel(kchan); + } + goto error; + } + break; + case LTTNG_EVENT_SYSCALL: + ret = event_kernel_enable_event(kchan, event, + filter_expression, filter); + /* We have passed ownership */ + filter_expression = NULL; + filter = NULL; + if (ret != LTTNG_OK) { + goto error; + } + break; + default: + ret = LTTNG_ERR_UNK; + goto error; + } + + kernel_wait_quiescent(); + break; + } + case LTTNG_DOMAIN_UST: + { + struct ltt_ust_channel *uchan; + struct ltt_ust_session *usess = session->ust_session; + + LTTNG_ASSERT(usess); + + /* + * If a non-default channel has been created in the + * session, explicitely require that -c chan_name needs + * to be provided. + */ + if (usess->has_non_default_channel && channel_name[0] == '\0') { + ret = LTTNG_ERR_NEED_CHANNEL_NAME; + goto error; + } + + /* Get channel from global UST domain */ + uchan = trace_ust_find_channel_by_name(usess->domain_global.channels, + channel_name); + if (uchan == NULL) { + /* Create default channel */ + attr = channel_new_default_attr(LTTNG_DOMAIN_UST, + usess->buffer_type); + if (attr == NULL) { + ret = LTTNG_ERR_FATAL; + goto error; + } + if (lttng_strncpy(attr->name, channel_name, + sizeof(attr->name))) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + ret = cmd_enable_channel(session, domain, attr, wpipe); + if (ret != LTTNG_OK) { + goto error; + } + + /* Get the newly created channel reference back */ + uchan = trace_ust_find_channel_by_name( + usess->domain_global.channels, channel_name); + LTTNG_ASSERT(uchan); + } + + if (uchan->domain != LTTNG_DOMAIN_UST && !internal_event) { + /* + * Don't allow users to add UST events to channels which + * are assigned to a userspace subdomain (JUL, Log4J, + * Python, etc.). + */ + ret = LTTNG_ERR_INVALID_CHANNEL_DOMAIN; + goto error; + } + + if (!internal_event) { + /* + * Ensure the event name is not reserved for internal + * use. + */ + ret = validate_ust_event_name(event->name); + if (ret) { + WARN("Userspace event name %s failed validation.", + event->name); + ret = LTTNG_ERR_INVALID_EVENT_NAME; + goto error; + } + } + + /* At this point, the session and channel exist on the tracer */ + ret = event_ust_enable_tracepoint(usess, uchan, event, + filter_expression, filter, exclusion, + internal_event); + /* We have passed ownership */ + filter_expression = NULL; + filter = NULL; + exclusion = NULL; + if (ret == LTTNG_ERR_UST_EVENT_ENABLED) { + goto already_enabled; + } else if (ret != LTTNG_OK) { + goto error; + } + break; + } + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_PYTHON: + { + const char *default_event_name, *default_chan_name; + struct agent *agt; + struct lttng_event uevent; + struct lttng_domain tmp_dom; + struct ltt_ust_session *usess = session->ust_session; + + LTTNG_ASSERT(usess); + + if (!agent_tracing_is_enabled()) { + DBG("Attempted to enable an event in an agent domain but the agent thread is not running"); + ret = LTTNG_ERR_AGENT_TRACING_DISABLED; + goto error; + } + + agt = trace_ust_find_agent(usess, domain->type); + if (!agt) { + agt = agent_create(domain->type); + if (!agt) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + agent_add(agt, usess->agents); + } + + /* Create the default tracepoint. */ + memset(&uevent, 0, sizeof(uevent)); + uevent.type = LTTNG_EVENT_TRACEPOINT; + uevent.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; + default_event_name = event_get_default_agent_ust_name( + domain->type); + if (!default_event_name) { + ret = LTTNG_ERR_FATAL; + goto error; + } + strncpy(uevent.name, default_event_name, sizeof(uevent.name)); + uevent.name[sizeof(uevent.name) - 1] = '\0'; + + /* + * The domain type is changed because we are about to enable the + * default channel and event for the JUL domain that are hardcoded. + * This happens in the UST domain. + */ + memcpy(&tmp_dom, domain, sizeof(tmp_dom)); + tmp_dom.type = LTTNG_DOMAIN_UST; + + switch (domain->type) { + case LTTNG_DOMAIN_LOG4J: + default_chan_name = DEFAULT_LOG4J_CHANNEL_NAME; + break; + case LTTNG_DOMAIN_JUL: + default_chan_name = DEFAULT_JUL_CHANNEL_NAME; + break; + case LTTNG_DOMAIN_PYTHON: + default_chan_name = DEFAULT_PYTHON_CHANNEL_NAME; + break; + default: + /* The switch/case we are in makes this impossible */ + abort(); + } + + { + char *filter_expression_copy = NULL; + struct lttng_bytecode *filter_copy = NULL; + + if (filter) { + const size_t filter_size = sizeof( + struct lttng_bytecode) + + filter->len; + + filter_copy = (lttng_bytecode *) zmalloc(filter_size); + if (!filter_copy) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + memcpy(filter_copy, filter, filter_size); + + filter_expression_copy = + strdup(filter_expression); + if (!filter_expression) { + ret = LTTNG_ERR_NOMEM; + } + + if (!filter_expression_copy || !filter_copy) { + free(filter_expression_copy); + free(filter_copy); + goto error; + } + } + + ret = cmd_enable_event_internal(session, &tmp_dom, + (char *) default_chan_name, + &uevent, filter_expression_copy, + filter_copy, NULL, wpipe); + } + + if (ret == LTTNG_ERR_UST_EVENT_ENABLED) { + goto already_enabled; + } else if (ret != LTTNG_OK) { + goto error; + } + + /* The wild card * means that everything should be enabled. */ + if (strncmp(event->name, "*", 1) == 0 && strlen(event->name) == 1) { + ret = event_agent_enable_all(usess, agt, event, filter, + filter_expression); + } else { + ret = event_agent_enable(usess, agt, event, filter, + filter_expression); + } + filter = NULL; + filter_expression = NULL; + if (ret != LTTNG_OK) { + goto error; + } + + break; + } + default: + ret = LTTNG_ERR_UND; + goto error; + } + + ret = LTTNG_OK; + +already_enabled: +error: + free(filter_expression); + free(filter); + free(exclusion); + channel_attr_destroy(attr); + rcu_read_unlock(); + return ret; +} + +/* + * Command LTTNG_ENABLE_EVENT processed by the client thread. + * We own filter, exclusion, and filter_expression. + */ +int cmd_enable_event(struct ltt_session *session, + const struct lttng_domain *domain, + char *channel_name, struct lttng_event *event, + char *filter_expression, + struct lttng_bytecode *filter, + struct lttng_event_exclusion *exclusion, + int wpipe) +{ + return _cmd_enable_event(session, domain, channel_name, event, + filter_expression, filter, exclusion, wpipe, false); +} + +/* + * Enable an event which is internal to LTTng. An internal should + * never be made visible to clients and are immune to checks such as + * reserved names. + */ +static int cmd_enable_event_internal(struct ltt_session *session, + const struct lttng_domain *domain, + char *channel_name, struct lttng_event *event, + char *filter_expression, + struct lttng_bytecode *filter, + struct lttng_event_exclusion *exclusion, + int wpipe) +{ + return _cmd_enable_event(session, domain, channel_name, event, + filter_expression, filter, exclusion, wpipe, true); +} + +/* + * Command LTTNG_LIST_TRACEPOINTS processed by the client thread. + */ +ssize_t cmd_list_tracepoints(enum lttng_domain_type domain, + struct lttng_event **events) +{ + int ret; + ssize_t nb_events = 0; + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + nb_events = kernel_list_events(events); + if (nb_events < 0) { + ret = LTTNG_ERR_KERN_LIST_FAIL; + goto error; + } + break; + case LTTNG_DOMAIN_UST: + nb_events = ust_app_list_events(events); + if (nb_events < 0) { + ret = LTTNG_ERR_UST_LIST_FAIL; + goto error; + } + break; + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_PYTHON: + nb_events = agent_list_events(events, domain); + if (nb_events < 0) { + ret = LTTNG_ERR_UST_LIST_FAIL; + goto error; + } + break; + default: + ret = LTTNG_ERR_UND; + goto error; + } + + return nb_events; + +error: + /* Return negative value to differentiate return code */ + return -ret; +} + +/* + * Command LTTNG_LIST_TRACEPOINT_FIELDS processed by the client thread. + */ +ssize_t cmd_list_tracepoint_fields(enum lttng_domain_type domain, + struct lttng_event_field **fields) +{ + int ret; + ssize_t nb_fields = 0; + + switch (domain) { + case LTTNG_DOMAIN_UST: + nb_fields = ust_app_list_event_fields(fields); + if (nb_fields < 0) { + ret = LTTNG_ERR_UST_LIST_FAIL; + goto error; + } + break; + case LTTNG_DOMAIN_KERNEL: + default: /* fall-through */ + ret = LTTNG_ERR_UND; + goto error; + } + + return nb_fields; + +error: + /* Return negative value to differentiate return code */ + return -ret; +} + +ssize_t cmd_list_syscalls(struct lttng_event **events) +{ + return syscall_table_list(events); +} + +/* + * Command LTTNG_START_TRACE processed by the client thread. + * + * Called with session mutex held. + */ +int cmd_start_trace(struct ltt_session *session) +{ + enum lttng_error_code ret; + unsigned long nb_chan = 0; + struct ltt_kernel_session *ksession; + struct ltt_ust_session *usess; + const bool session_rotated_after_last_stop = + session->rotated_after_last_stop; + const bool session_cleared_after_last_stop = + session->cleared_after_last_stop; + + LTTNG_ASSERT(session); + + /* Ease our life a bit ;) */ + ksession = session->kernel_session; + usess = session->ust_session; + + /* Is the session already started? */ + if (session->active) { + ret = LTTNG_ERR_TRACE_ALREADY_STARTED; + /* Perform nothing */ + goto end; + } + + if (session->rotation_state == LTTNG_ROTATION_STATE_ONGOING && + !session->current_trace_chunk) { + /* + * A rotation was launched while the session was stopped and + * it has not been completed yet. It is not possible to start + * the session since starting the session here would require a + * rotation from "NULL" to a new trace chunk. That rotation + * would overlap with the ongoing rotation, which is not + * supported. + */ + WARN("Refusing to start session \"%s\" as a rotation launched after the last \"stop\" is still ongoing", + session->name); + ret = LTTNG_ERR_ROTATION_PENDING; + goto error; + } + + /* + * Starting a session without channel is useless since after that it's not + * possible to enable channel thus inform the client. + */ + if (usess && usess->domain_global.channels) { + nb_chan += lttng_ht_get_count(usess->domain_global.channels); + } + if (ksession) { + nb_chan += ksession->channel_count; + } + if (!nb_chan) { + ret = LTTNG_ERR_NO_CHANNEL; + goto error; + } + + session->active = 1; + session->rotated_after_last_stop = false; + session->cleared_after_last_stop = false; + if (session->output_traces && !session->current_trace_chunk) { + if (!session->has_been_started) { + struct lttng_trace_chunk *trace_chunk; + + DBG("Creating initial trace chunk of session \"%s\"", + session->name); + trace_chunk = session_create_new_trace_chunk( + session, NULL, NULL, NULL); + if (!trace_chunk) { + ret = LTTNG_ERR_CREATE_DIR_FAIL; + goto error; + } + LTTNG_ASSERT(!session->current_trace_chunk); + ret = (lttng_error_code) session_set_trace_chunk(session, trace_chunk, + NULL); + lttng_trace_chunk_put(trace_chunk); + if (ret) { + ret = LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; + goto error; + } + } else { + DBG("Rotating session \"%s\" from its current \"NULL\" trace chunk to a new chunk", + session->name); + /* + * Rotate existing streams into the new chunk. + * This is a "quiet" rotation has no client has + * explicitly requested this operation. + * + * There is also no need to wait for the rotation + * to complete as it will happen immediately. No data + * was produced as the session was stopped, so the + * rotation should happen on reception of the command. + */ + ret = (lttng_error_code) cmd_rotate_session(session, NULL, true, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION); + if (ret != LTTNG_OK) { + goto error; + } + } + } + + /* Kernel tracing */ + if (ksession != NULL) { + DBG("Start kernel tracing session %s", session->name); + ret = (lttng_error_code) start_kernel_session(ksession); + if (ret != LTTNG_OK) { + goto error; + } + } + + /* Flag session that trace should start automatically */ + if (usess) { + int int_ret = ust_app_start_trace_all(usess); + + if (int_ret < 0) { + ret = LTTNG_ERR_UST_START_FAIL; + goto error; + } + } + + /* + * Open a packet in every stream of the session to ensure that viewers + * can correctly identify the boundaries of the periods during which + * tracing was active for this session. + */ + ret = session_open_packets(session); + if (ret != LTTNG_OK) { + goto error; + } + + /* + * Clear the flag that indicates that a rotation was done while the + * session was stopped. + */ + session->rotated_after_last_stop = false; + + if (session->rotate_timer_period) { + int int_ret = timer_session_rotation_schedule_timer_start( + session, session->rotate_timer_period); + + if (int_ret < 0) { + ERR("Failed to enable rotate timer"); + ret = LTTNG_ERR_UNK; + goto error; + } + } + + ret = LTTNG_OK; + +error: + if (ret == LTTNG_OK) { + /* Flag this after a successful start. */ + session->has_been_started |= 1; + } else { + session->active = 0; + /* Restore initial state on error. */ + session->rotated_after_last_stop = + session_rotated_after_last_stop; + session->cleared_after_last_stop = + session_cleared_after_last_stop; + } +end: + return ret; +} + +/* + * Command LTTNG_STOP_TRACE processed by the client thread. + */ +int cmd_stop_trace(struct ltt_session *session) +{ + int ret; + struct ltt_kernel_session *ksession; + struct ltt_ust_session *usess; + + LTTNG_ASSERT(session); + + DBG("Begin stop session \"%s\" (id %" PRIu64 ")", session->name, session->id); + /* Short cut */ + ksession = session->kernel_session; + usess = session->ust_session; + + /* Session is not active. Skip everythong and inform the client. */ + if (!session->active) { + ret = LTTNG_ERR_TRACE_ALREADY_STOPPED; + goto error; + } + + ret = stop_kernel_session(ksession); + if (ret != LTTNG_OK) { + goto error; + } + + if (usess && usess->active) { + ret = ust_app_stop_trace_all(usess); + if (ret < 0) { + ret = LTTNG_ERR_UST_STOP_FAIL; + goto error; + } + } + + DBG("Completed stop session \"%s\" (id %" PRIu64 ")", session->name, + session->id); + /* Flag inactive after a successful stop. */ + session->active = 0; + ret = LTTNG_OK; + +error: + return ret; +} + +/* + * Set the base_path of the session only if subdir of a control uris is set. + * Return LTTNG_OK on success, otherwise LTTNG_ERR_*. + */ +static int set_session_base_path_from_uris(struct ltt_session *session, + size_t nb_uri, + struct lttng_uri *uris) +{ + int ret; + size_t i; + + for (i = 0; i < nb_uri; i++) { + if (uris[i].stype != LTTNG_STREAM_CONTROL || + uris[i].subdir[0] == '\0') { + /* Not interested in these URIs */ + continue; + } + + if (session->base_path != NULL) { + free(session->base_path); + session->base_path = NULL; + } + + /* Set session base_path */ + session->base_path = strdup(uris[i].subdir); + if (!session->base_path) { + PERROR("Failed to copy base path \"%s\" to session \"%s\"", + uris[i].subdir, session->name); + ret = LTTNG_ERR_NOMEM; + goto error; + } + DBG2("Setting base path \"%s\" for session \"%s\"", + session->base_path, session->name); + } + ret = LTTNG_OK; +error: + return ret; +} + +/* + * Command LTTNG_SET_CONSUMER_URI processed by the client thread. + */ +int cmd_set_consumer_uri(struct ltt_session *session, size_t nb_uri, + struct lttng_uri *uris) +{ + int ret, i; + struct ltt_kernel_session *ksess = session->kernel_session; + struct ltt_ust_session *usess = session->ust_session; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(uris); + LTTNG_ASSERT(nb_uri > 0); + + /* Can't set consumer URI if the session is active. */ + if (session->active) { + ret = LTTNG_ERR_TRACE_ALREADY_STARTED; + goto error; + } + + /* + * Set the session base path if any. This is done inside + * cmd_set_consumer_uri to preserve backward compatibility of the + * previous session creation api vs the session descriptor api. + */ + ret = set_session_base_path_from_uris(session, nb_uri, uris); + if (ret != LTTNG_OK) { + goto error; + } + + /* Set the "global" consumer URIs */ + for (i = 0; i < nb_uri; i++) { + ret = add_uri_to_consumer(session, session->consumer, &uris[i], + LTTNG_DOMAIN_NONE); + if (ret != LTTNG_OK) { + goto error; + } + } + + /* Set UST session URIs */ + if (session->ust_session) { + for (i = 0; i < nb_uri; i++) { + ret = add_uri_to_consumer(session, + session->ust_session->consumer, + &uris[i], LTTNG_DOMAIN_UST); + if (ret != LTTNG_OK) { + goto error; + } + } + } + + /* Set kernel session URIs */ + if (session->kernel_session) { + for (i = 0; i < nb_uri; i++) { + ret = add_uri_to_consumer(session, + session->kernel_session->consumer, + &uris[i], LTTNG_DOMAIN_KERNEL); + if (ret != LTTNG_OK) { + goto error; + } + } + } + + /* + * Make sure to set the session in output mode after we set URI since a + * session can be created without URL (thus flagged in no output mode). + */ + session->output_traces = 1; + if (ksess) { + ksess->output_traces = 1; + } + + if (usess) { + usess->output_traces = 1; + } + + /* All good! */ + ret = LTTNG_OK; + +error: + return ret; +} + +static +enum lttng_error_code set_session_output_from_descriptor( + struct ltt_session *session, + const struct lttng_session_descriptor *descriptor) +{ + int ret; + enum lttng_error_code ret_code = LTTNG_OK; + enum lttng_session_descriptor_type session_type = + lttng_session_descriptor_get_type(descriptor); + enum lttng_session_descriptor_output_type output_type = + lttng_session_descriptor_get_output_type(descriptor); + struct lttng_uri uris[2] = {}; + size_t uri_count = 0; + + switch (output_type) { + case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NONE: + goto end; + case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_LOCAL: + lttng_session_descriptor_get_local_output_uri(descriptor, + &uris[0]); + uri_count = 1; + break; + case LTTNG_SESSION_DESCRIPTOR_OUTPUT_TYPE_NETWORK: + lttng_session_descriptor_get_network_output_uris(descriptor, + &uris[0], &uris[1]); + uri_count = 2; + break; + default: + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + switch (session_type) { + case LTTNG_SESSION_DESCRIPTOR_TYPE_SNAPSHOT: + { + struct snapshot_output *new_output = NULL; + + new_output = snapshot_output_alloc(); + if (!new_output) { + ret_code = LTTNG_ERR_NOMEM; + goto end; + } + + ret = snapshot_output_init_with_uri(session, + DEFAULT_SNAPSHOT_MAX_SIZE, + NULL, uris, uri_count, session->consumer, + new_output, &session->snapshot); + if (ret < 0) { + ret_code = (ret == -ENOMEM) ? + LTTNG_ERR_NOMEM : LTTNG_ERR_INVALID; + snapshot_output_destroy(new_output); + goto end; + } + snapshot_add_output(&session->snapshot, new_output); + break; + } + case LTTNG_SESSION_DESCRIPTOR_TYPE_REGULAR: + case LTTNG_SESSION_DESCRIPTOR_TYPE_LIVE: + { + ret_code = (lttng_error_code) cmd_set_consumer_uri(session, uri_count, uris); + break; + } + default: + ret_code = LTTNG_ERR_INVALID; + goto end; + } +end: + return ret_code; +} + +static +enum lttng_error_code cmd_create_session_from_descriptor( + struct lttng_session_descriptor *descriptor, + const lttng_sock_cred *creds, + const char *home_path) +{ + int ret; + enum lttng_error_code ret_code; + const char *session_name; + struct ltt_session *new_session = NULL; + enum lttng_session_descriptor_status descriptor_status; + + session_lock_list(); + if (home_path) { + if (*home_path != '/') { + ERR("Home path provided by client is not absolute"); + ret_code = LTTNG_ERR_INVALID; + goto end; + } + } + + descriptor_status = lttng_session_descriptor_get_session_name( + descriptor, &session_name); + switch (descriptor_status) { + case LTTNG_SESSION_DESCRIPTOR_STATUS_OK: + break; + case LTTNG_SESSION_DESCRIPTOR_STATUS_UNSET: + session_name = NULL; + break; + default: + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + ret_code = session_create(session_name, creds->uid, creds->gid, + &new_session); + if (ret_code != LTTNG_OK) { + goto end; + } + + if (!session_name) { + ret = lttng_session_descriptor_set_session_name(descriptor, + new_session->name); + if (ret) { + ret_code = LTTNG_ERR_SESSION_FAIL; + goto end; + } + } + + if (!lttng_session_descriptor_is_output_destination_initialized( + descriptor)) { + /* + * Only include the session's creation time in the output + * destination if the name of the session itself was + * not auto-generated. + */ + ret_code = lttng_session_descriptor_set_default_output( + descriptor, + session_name ? &new_session->creation_time : NULL, + home_path); + if (ret_code != LTTNG_OK) { + goto end; + } + } else { + new_session->has_user_specified_directory = + lttng_session_descriptor_has_output_directory( + descriptor); + } + + switch (lttng_session_descriptor_get_type(descriptor)) { + case LTTNG_SESSION_DESCRIPTOR_TYPE_SNAPSHOT: + new_session->snapshot_mode = 1; + break; + case LTTNG_SESSION_DESCRIPTOR_TYPE_LIVE: + new_session->live_timer = + lttng_session_descriptor_live_get_timer_interval( + descriptor); + break; + default: + break; + } + + ret_code = set_session_output_from_descriptor(new_session, descriptor); + if (ret_code != LTTNG_OK) { + goto end; + } + new_session->consumer->enabled = 1; + ret_code = LTTNG_OK; +end: + /* Release reference provided by the session_create function. */ + session_put(new_session); + if (ret_code != LTTNG_OK && new_session) { + /* Release the global reference on error. */ + session_destroy(new_session); + } + session_unlock_list(); + return ret_code; +} + +enum lttng_error_code cmd_create_session(struct command_ctx *cmd_ctx, int sock, + struct lttng_session_descriptor **return_descriptor) +{ + int ret; + size_t payload_size; + struct lttng_dynamic_buffer payload; + struct lttng_buffer_view home_dir_view; + struct lttng_buffer_view session_descriptor_view; + struct lttng_session_descriptor *session_descriptor = NULL; + enum lttng_error_code ret_code; + + lttng_dynamic_buffer_init(&payload); + if (cmd_ctx->lsm.u.create_session.home_dir_size >= + LTTNG_PATH_MAX) { + ret_code = LTTNG_ERR_INVALID; + goto error; + } + if (cmd_ctx->lsm.u.create_session.session_descriptor_size > + LTTNG_SESSION_DESCRIPTOR_MAX_LEN) { + ret_code = LTTNG_ERR_INVALID; + goto error; + } + + payload_size = cmd_ctx->lsm.u.create_session.home_dir_size + + cmd_ctx->lsm.u.create_session.session_descriptor_size; + ret = lttng_dynamic_buffer_set_size(&payload, payload_size); + if (ret) { + ret_code = LTTNG_ERR_NOMEM; + goto error; + } + + ret = lttcomm_recv_unix_sock(sock, payload.data, payload.size); + if (ret <= 0) { + ERR("Reception of session descriptor failed, aborting."); + ret_code = LTTNG_ERR_SESSION_FAIL; + goto error; + } + + home_dir_view = lttng_buffer_view_from_dynamic_buffer( + &payload, + 0, + cmd_ctx->lsm.u.create_session.home_dir_size); + if (cmd_ctx->lsm.u.create_session.home_dir_size > 0 && + !lttng_buffer_view_is_valid(&home_dir_view)) { + ERR("Invalid payload in \"create session\" command: buffer too short to contain home directory"); + ret_code = LTTNG_ERR_INVALID_PROTOCOL; + goto error; + } + + session_descriptor_view = lttng_buffer_view_from_dynamic_buffer( + &payload, + cmd_ctx->lsm.u.create_session.home_dir_size, + cmd_ctx->lsm.u.create_session.session_descriptor_size); + if (!lttng_buffer_view_is_valid(&session_descriptor_view)) { + ERR("Invalid payload in \"create session\" command: buffer too short to contain session descriptor"); + ret_code = LTTNG_ERR_INVALID_PROTOCOL; + goto error; + } + + ret = lttng_session_descriptor_create_from_buffer( + &session_descriptor_view, &session_descriptor); + if (ret < 0) { + ERR("Failed to create session descriptor from payload of \"create session\" command"); + ret_code = LTTNG_ERR_INVALID; + goto error; + } + + /* + * Sets the descriptor's auto-generated properties (name, output) if + * needed. + */ + ret_code = cmd_create_session_from_descriptor(session_descriptor, + &cmd_ctx->creds, + home_dir_view.size ? home_dir_view.data : NULL); + if (ret_code != LTTNG_OK) { + goto error; + } + + ret_code = LTTNG_OK; + *return_descriptor = session_descriptor; + session_descriptor = NULL; +error: + lttng_dynamic_buffer_reset(&payload); + lttng_session_descriptor_destroy(session_descriptor); + return ret_code; +} + +static +void cmd_destroy_session_reply(const struct ltt_session *session, + void *_reply_context) +{ + int ret; + ssize_t comm_ret; + const struct cmd_destroy_session_reply_context *reply_context = + (cmd_destroy_session_reply_context *) _reply_context; + struct lttng_dynamic_buffer payload; + struct lttcomm_session_destroy_command_header cmd_header; + struct lttng_trace_archive_location *location = NULL; + struct lttcomm_lttng_msg llm = { + .cmd_type = LTTNG_DESTROY_SESSION, + .ret_code = reply_context->destruction_status, + .pid = UINT32_MAX, + .cmd_header_size = + sizeof(struct lttcomm_session_destroy_command_header), + .data_size = 0, + }; + size_t payload_size_before_location; + + lttng_dynamic_buffer_init(&payload); + + ret = lttng_dynamic_buffer_append(&payload, &llm, sizeof(llm)); + if (ret) { + ERR("Failed to append session destruction message"); + goto error; + } + + cmd_header.rotation_state = + (int32_t) (reply_context->implicit_rotation_on_destroy ? + session->rotation_state : + LTTNG_ROTATION_STATE_NO_ROTATION); + ret = lttng_dynamic_buffer_append(&payload, &cmd_header, + sizeof(cmd_header)); + if (ret) { + ERR("Failed to append session destruction command header"); + goto error; + } + + if (!reply_context->implicit_rotation_on_destroy) { + DBG("No implicit rotation performed during the destruction of session \"%s\", sending reply", + session->name); + goto send_reply; + } + if (session->rotation_state != LTTNG_ROTATION_STATE_COMPLETED) { + DBG("Rotation state of session \"%s\" is not \"completed\", sending session destruction reply", + session->name); + goto send_reply; + } + + location = session_get_trace_archive_location(session); + if (!location) { + ERR("Failed to get the location of the trace archive produced during the destruction of session \"%s\"", + session->name); + goto error; + } + + payload_size_before_location = payload.size; + comm_ret = lttng_trace_archive_location_serialize(location, + &payload); + lttng_trace_archive_location_put(location); + if (comm_ret < 0) { + ERR("Failed to serialize the location of the trace archive produced during the destruction of session \"%s\"", + session->name); + goto error; + } + /* Update the message to indicate the location's length. */ + ((struct lttcomm_lttng_msg *) payload.data)->data_size = + payload.size - payload_size_before_location; +send_reply: + comm_ret = lttcomm_send_unix_sock(reply_context->reply_sock_fd, + payload.data, payload.size); + if (comm_ret != (ssize_t) payload.size) { + ERR("Failed to send result of the destruction of session \"%s\" to client", + session->name); + } +error: + ret = close(reply_context->reply_sock_fd); + if (ret) { + PERROR("Failed to close client socket in deferred session destroy reply"); + } + lttng_dynamic_buffer_reset(&payload); + free(_reply_context); +} + +/* + * Command LTTNG_DESTROY_SESSION processed by the client thread. + * + * Called with session lock held. + */ +int cmd_destroy_session(struct ltt_session *session, + struct notification_thread_handle *notification_thread_handle, + int *sock_fd) +{ + int ret; + enum lttng_error_code destruction_last_error = LTTNG_OK; + struct cmd_destroy_session_reply_context *reply_context = NULL; + + if (sock_fd) { + reply_context = (cmd_destroy_session_reply_context *) zmalloc(sizeof(*reply_context)); + if (!reply_context) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + reply_context->reply_sock_fd = *sock_fd; + } + + /* Safety net */ + LTTNG_ASSERT(session); + + DBG("Begin destroy session %s (id %" PRIu64 ")", session->name, + session->id); + if (session->active) { + DBG("Session \"%s\" is active, attempting to stop it before destroying it", + session->name); + ret = cmd_stop_trace(session); + if (ret != LTTNG_OK && ret != LTTNG_ERR_TRACE_ALREADY_STOPPED) { + /* Carry on with the destruction of the session. */ + ERR("Failed to stop session \"%s\" as part of its destruction: %s", + session->name, lttng_strerror(-ret)); + destruction_last_error = (lttng_error_code) ret; + } + } + + if (session->rotation_schedule_timer_enabled) { + if (timer_session_rotation_schedule_timer_stop( + session)) { + ERR("Failed to stop the \"rotation schedule\" timer of session %s", + session->name); + destruction_last_error = LTTNG_ERR_TIMER_STOP_ERROR; + } + } + + if (session->rotate_size) { + unsubscribe_session_consumed_size_rotation(session, notification_thread_handle); + session->rotate_size = 0; + } + + if (session->rotated && session->current_trace_chunk && session->output_traces) { + /* + * Perform a last rotation on destruction if rotations have + * occurred during the session's lifetime. + */ + ret = cmd_rotate_session(session, NULL, false, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); + if (ret != LTTNG_OK) { + ERR("Failed to perform an implicit rotation as part of the destruction of session \"%s\": %s", + session->name, lttng_strerror(-ret)); + destruction_last_error = (lttng_error_code) -ret; + } + if (reply_context) { + reply_context->implicit_rotation_on_destroy = true; + } + } else if (session->has_been_started && session->current_trace_chunk) { + /* + * The user has not triggered a session rotation. However, to + * ensure all data has been consumed, the session is rotated + * to a 'null' trace chunk before it is destroyed. + * + * This is a "quiet" rotation meaning that no notification is + * emitted and no renaming of the current trace chunk takes + * place. + */ + ret = cmd_rotate_session(session, NULL, true, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION); + /* + * Rotation operations may not be supported by the kernel + * tracer. Hence, do not consider this implicit rotation as + * a session destruction error. The library has already stopped + * the session and waited for pending data; there is nothing + * left to do but complete the destruction of the session. + */ + if (ret != LTTNG_OK && + ret != -LTTNG_ERR_ROTATION_NOT_AVAILABLE_KERNEL) { + ERR("Failed to perform a quiet rotation as part of the destruction of session \"%s\": %s", + session->name, lttng_strerror(ret)); + destruction_last_error = (lttng_error_code) -ret; + } + } + + if (session->shm_path[0]) { + /* + * When a session is created with an explicit shm_path, + * the consumer daemon will create its shared memory files + * at that location and will *not* unlink them. This is normal + * as the intention of that feature is to make it possible + * to retrieve the content of those files should a crash occur. + * + * To ensure the content of those files can be used, the + * sessiond daemon will replicate the content of the metadata + * cache in a metadata file. + * + * On clean-up, it is expected that the consumer daemon will + * unlink the shared memory files and that the session daemon + * will unlink the metadata file. Then, the session's directory + * in the shm path can be removed. + * + * Unfortunately, a flaw in the design of the sessiond's and + * consumerd's tear down of channels makes it impossible to + * determine when the sessiond _and_ the consumerd have both + * destroyed their representation of a channel. For one, the + * unlinking, close, and rmdir happen in deferred 'call_rcu' + * callbacks in both daemons. + * + * However, it is also impossible for the sessiond to know when + * the consumer daemon is done destroying its channel(s) since + * it occurs as a reaction to the closing of the channel's file + * descriptor. There is no resulting communication initiated + * from the consumerd to the sessiond to confirm that the + * operation is completed (and was successful). + * + * Until this is all fixed, the session daemon checks for the + * removal of the session's shm path which makes it possible + * to safely advertise a session as having been destroyed. + * + * Prior to this fix, it was not possible to reliably save + * a session making use of the --shm-path option, destroy it, + * and load it again. This is because the creation of the + * session would fail upon seeing the session's shm path + * already in existence. + * + * Note that none of the error paths in the check for the + * directory's existence return an error. This is normal + * as there isn't much that can be done. The session will + * be destroyed properly, except that we can't offer the + * guarantee that the same session can be re-created. + */ + current_completion_handler = &destroy_completion_handler.handler; + ret = lttng_strncpy(destroy_completion_handler.shm_path, + session->shm_path, + sizeof(destroy_completion_handler.shm_path)); + LTTNG_ASSERT(!ret); + } + + /* + * The session is destroyed. However, note that the command context + * still holds a reference to the session, thus delaying its destruction + * _at least_ up to the point when that reference is released. + */ + session_destroy(session); + if (reply_context) { + reply_context->destruction_status = destruction_last_error; + ret = session_add_destroy_notifier(session, + cmd_destroy_session_reply, + (void *) reply_context); + if (ret) { + ret = LTTNG_ERR_FATAL; + goto end; + } else { + *sock_fd = -1; + } + } + ret = LTTNG_OK; +end: + return ret; +} + +/* + * Command LTTNG_REGISTER_CONSUMER processed by the client thread. + */ +int cmd_register_consumer(struct ltt_session *session, + enum lttng_domain_type domain, const char *sock_path, + struct consumer_data *cdata) +{ + int ret, sock; + struct consumer_socket *socket = NULL; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(cdata); + LTTNG_ASSERT(sock_path); + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + { + struct ltt_kernel_session *ksess = session->kernel_session; + + LTTNG_ASSERT(ksess); + + /* Can't register a consumer if there is already one */ + if (ksess->consumer_fds_sent != 0) { + ret = LTTNG_ERR_KERN_CONSUMER_FAIL; + goto error; + } + + sock = lttcomm_connect_unix_sock(sock_path); + if (sock < 0) { + ret = LTTNG_ERR_CONNECT_FAIL; + goto error; + } + cdata->cmd_sock = sock; + + socket = consumer_allocate_socket(&cdata->cmd_sock); + if (socket == NULL) { + ret = close(sock); + if (ret < 0) { + PERROR("close register consumer"); + } + cdata->cmd_sock = -1; + ret = LTTNG_ERR_FATAL; + goto error; + } + + socket->lock = (pthread_mutex_t *) zmalloc(sizeof(pthread_mutex_t)); + if (socket->lock == NULL) { + PERROR("zmalloc pthread mutex"); + ret = LTTNG_ERR_FATAL; + goto error; + } + pthread_mutex_init(socket->lock, NULL); + socket->registered = 1; + + rcu_read_lock(); + consumer_add_socket(socket, ksess->consumer); + rcu_read_unlock(); + + pthread_mutex_lock(&cdata->pid_mutex); + cdata->pid = -1; + pthread_mutex_unlock(&cdata->pid_mutex); + + break; + } + default: + /* TODO: Userspace tracing */ + ret = LTTNG_ERR_UND; + goto error; + } + + return LTTNG_OK; + +error: + if (socket) { + consumer_destroy_socket(socket); + } + return ret; +} + +/* + * Command LTTNG_LIST_DOMAINS processed by the client thread. + */ +ssize_t cmd_list_domains(struct ltt_session *session, + struct lttng_domain **domains) +{ + int ret, index = 0; + ssize_t nb_dom = 0; + struct agent *agt; + struct lttng_ht_iter iter; + + if (session->kernel_session != NULL) { + DBG3("Listing domains found kernel domain"); + nb_dom++; + } + + if (session->ust_session != NULL) { + DBG3("Listing domains found UST global domain"); + nb_dom++; + + rcu_read_lock(); + cds_lfht_for_each_entry(session->ust_session->agents->ht, &iter.iter, + agt, node.node) { + if (agt->being_used) { + nb_dom++; + } + } + rcu_read_unlock(); + } + + if (!nb_dom) { + goto end; + } + + *domains = (lttng_domain *) zmalloc(nb_dom * sizeof(struct lttng_domain)); + if (*domains == NULL) { + ret = LTTNG_ERR_FATAL; + goto error; + } + + if (session->kernel_session != NULL) { + (*domains)[index].type = LTTNG_DOMAIN_KERNEL; + + /* Kernel session buffer type is always GLOBAL */ + (*domains)[index].buf_type = LTTNG_BUFFER_GLOBAL; + + index++; + } + + if (session->ust_session != NULL) { + (*domains)[index].type = LTTNG_DOMAIN_UST; + (*domains)[index].buf_type = session->ust_session->buffer_type; + index++; + + rcu_read_lock(); + cds_lfht_for_each_entry(session->ust_session->agents->ht, &iter.iter, + agt, node.node) { + if (agt->being_used) { + (*domains)[index].type = agt->domain; + (*domains)[index].buf_type = session->ust_session->buffer_type; + index++; + } + } + rcu_read_unlock(); + } +end: + return nb_dom; + +error: + /* Return negative value to differentiate return code */ + return -ret; +} + + +/* + * Command LTTNG_LIST_CHANNELS processed by the client thread. + */ +ssize_t cmd_list_channels(enum lttng_domain_type domain, + struct ltt_session *session, struct lttng_channel **channels) +{ + ssize_t nb_chan = 0, payload_size = 0, ret; + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + if (session->kernel_session != NULL) { + nb_chan = session->kernel_session->channel_count; + } + DBG3("Number of kernel channels %zd", nb_chan); + if (nb_chan <= 0) { + ret = -LTTNG_ERR_KERN_CHAN_NOT_FOUND; + goto end; + } + break; + case LTTNG_DOMAIN_UST: + if (session->ust_session != NULL) { + rcu_read_lock(); + nb_chan = lttng_ht_get_count( + session->ust_session->domain_global.channels); + rcu_read_unlock(); + } + DBG3("Number of UST global channels %zd", nb_chan); + if (nb_chan < 0) { + ret = -LTTNG_ERR_UST_CHAN_NOT_FOUND; + goto end; + } + break; + default: + ret = -LTTNG_ERR_UND; + goto end; + } + + if (nb_chan > 0) { + const size_t channel_size = sizeof(struct lttng_channel) + + sizeof(struct lttng_channel_extended); + struct lttng_channel_extended *channel_exts; + + payload_size = nb_chan * channel_size; + *channels = (lttng_channel *) zmalloc(payload_size); + if (*channels == NULL) { + ret = -LTTNG_ERR_FATAL; + goto end; + } + + channel_exts = (lttng_channel_extended *) + (((char *) *channels) + (nb_chan * sizeof(struct lttng_channel))); + ret = list_lttng_channels(domain, session, *channels, channel_exts); + if (ret != LTTNG_OK) { + free(*channels); + *channels = NULL; + goto end; + } + } else { + *channels = NULL; + } + + ret = payload_size; +end: + return ret; +} + +/* + * Command LTTNG_LIST_EVENTS processed by the client thread. + */ +ssize_t cmd_list_events(enum lttng_domain_type domain, + struct ltt_session *session, char *channel_name, + struct lttng_payload *payload) +{ + int ret = 0; + ssize_t nb_events = 0; + struct lttcomm_event_command_header cmd_header = {}; + const size_t cmd_header_offset = payload->buffer.size; + + ret = lttng_dynamic_buffer_append( + &payload->buffer, &cmd_header, sizeof(cmd_header)); + if (ret) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + if (session->kernel_session != NULL) { + nb_events = list_lttng_kernel_events(channel_name, + session->kernel_session, payload); + } + break; + case LTTNG_DOMAIN_UST: + { + if (session->ust_session != NULL) { + nb_events = list_lttng_ust_global_events(channel_name, + &session->ust_session->domain_global, + payload); + } + break; + } + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_PYTHON: + if (session->ust_session) { + struct lttng_ht_iter iter; + struct agent *agt; + + rcu_read_lock(); + cds_lfht_for_each_entry(session->ust_session->agents->ht, + &iter.iter, agt, node.node) { + if (agt->domain == domain) { + nb_events = list_lttng_agent_events( + agt, payload); + break; + } + } + rcu_read_unlock(); + } + break; + default: + ret = LTTNG_ERR_UND; + goto error; + } + + ((struct lttcomm_event_command_header *) (payload->buffer.data + + cmd_header_offset))->nb_events = (uint32_t) nb_events; + + return nb_events; + +error: + /* Return negative value to differentiate return code */ + return -ret; +} + +/* + * Using the session list, filled a lttng_session array to send back to the + * client for session listing. + * + * The session list lock MUST be acquired before calling this function. Use + * session_lock_list() and session_unlock_list(). + */ +void cmd_list_lttng_sessions(struct lttng_session *sessions, + size_t session_count, uid_t uid, gid_t gid) +{ + int ret; + unsigned int i = 0; + struct ltt_session *session; + struct ltt_session_list *list = session_get_list(); + struct lttng_session_extended *extended = + (typeof(extended)) (&sessions[session_count]); + + DBG("Getting all available session for UID %d GID %d", + uid, gid); + /* + * Iterate over session list and append data after the control struct in + * the buffer. + */ + cds_list_for_each_entry(session, &list->head, list) { + if (!session_get(session)) { + continue; + } + /* + * Only list the sessions the user can control. + */ + if (!session_access_ok(session, uid) || + session->destroyed) { + session_put(session); + continue; + } + + struct ltt_kernel_session *ksess = session->kernel_session; + struct ltt_ust_session *usess = session->ust_session; + + if (session->consumer->type == CONSUMER_DST_NET || + (ksess && ksess->consumer->type == CONSUMER_DST_NET) || + (usess && usess->consumer->type == CONSUMER_DST_NET)) { + ret = build_network_session_path(sessions[i].path, + sizeof(sessions[i].path), session); + } else { + ret = snprintf(sessions[i].path, sizeof(sessions[i].path), "%s", + session->consumer->dst.session_root_path); + } + if (ret < 0) { + PERROR("snprintf session path"); + session_put(session); + continue; + } + + strncpy(sessions[i].name, session->name, NAME_MAX); + sessions[i].name[NAME_MAX - 1] = '\0'; + sessions[i].enabled = session->active; + sessions[i].snapshot_mode = session->snapshot_mode; + sessions[i].live_timer_interval = session->live_timer; + extended[i].creation_time.value = (uint64_t) session->creation_time; + extended[i].creation_time.is_set = 1; + i++; + session_put(session); + } +} + +/* + * Command LTTNG_DATA_PENDING returning 0 if the data is NOT pending meaning + * ready for trace analysis (or any kind of reader) or else 1 for pending data. + */ +int cmd_data_pending(struct ltt_session *session) +{ + int ret; + struct ltt_kernel_session *ksess = session->kernel_session; + struct ltt_ust_session *usess = session->ust_session; + + LTTNG_ASSERT(session); + + DBG("Data pending for session %s", session->name); + + /* Session MUST be stopped to ask for data availability. */ + if (session->active) { + ret = LTTNG_ERR_SESSION_STARTED; + goto error; + } else { + /* + * If stopped, just make sure we've started before else the above call + * will always send that there is data pending. + * + * The consumer assumes that when the data pending command is received, + * the trace has been started before or else no output data is written + * by the streams which is a condition for data pending. So, this is + * *VERY* important that we don't ask the consumer before a start + * trace. + */ + if (!session->has_been_started) { + ret = 0; + goto error; + } + } + + /* A rotation is still pending, we have to wait. */ + if (session->rotation_state == LTTNG_ROTATION_STATE_ONGOING) { + DBG("Rotate still pending for session %s", session->name); + ret = 1; + goto error; + } + + if (ksess && ksess->consumer) { + ret = consumer_is_data_pending(ksess->id, ksess->consumer); + if (ret == 1) { + /* Data is still being extracted for the kernel. */ + goto error; + } + } + + if (usess && usess->consumer) { + ret = consumer_is_data_pending(usess->id, usess->consumer); + if (ret == 1) { + /* Data is still being extracted for the kernel. */ + goto error; + } + } + + /* Data is ready to be read by a viewer */ + ret = 0; + +error: + return ret; +} + +/* + * Command LTTNG_SNAPSHOT_ADD_OUTPUT from the lttng ctl library. + * + * Return LTTNG_OK on success or else a LTTNG_ERR code. + */ +int cmd_snapshot_add_output(struct ltt_session *session, + const struct lttng_snapshot_output *output, uint32_t *id) +{ + int ret; + struct snapshot_output *new_output; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(output); + + DBG("Cmd snapshot add output for session %s", session->name); + + /* + * Can't create an output if the session is not set in no-output mode. + */ + if (session->output_traces) { + ret = LTTNG_ERR_NOT_SNAPSHOT_SESSION; + goto error; + } + + if (session->has_non_mmap_channel) { + ret = LTTNG_ERR_SNAPSHOT_UNSUPPORTED; + goto error; + } + + /* Only one output is allowed until we have the "tee" feature. */ + if (session->snapshot.nb_output == 1) { + ret = LTTNG_ERR_SNAPSHOT_OUTPUT_EXIST; + goto error; + } + + new_output = snapshot_output_alloc(); + if (!new_output) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + + ret = snapshot_output_init(session, output->max_size, output->name, + output->ctrl_url, output->data_url, session->consumer, new_output, + &session->snapshot); + if (ret < 0) { + if (ret == -ENOMEM) { + ret = LTTNG_ERR_NOMEM; + } else { + ret = LTTNG_ERR_INVALID; + } + goto free_error; + } + + rcu_read_lock(); + snapshot_add_output(&session->snapshot, new_output); + if (id) { + *id = new_output->id; + } + rcu_read_unlock(); + + return LTTNG_OK; + +free_error: + snapshot_output_destroy(new_output); +error: + return ret; +} + +/* + * Command LTTNG_SNAPSHOT_DEL_OUTPUT from lib lttng ctl. + * + * Return LTTNG_OK on success or else a LTTNG_ERR code. + */ +int cmd_snapshot_del_output(struct ltt_session *session, + const struct lttng_snapshot_output *output) +{ + int ret; + struct snapshot_output *sout = NULL; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(output); + + rcu_read_lock(); + + /* + * Permission denied to create an output if the session is not + * set in no output mode. + */ + if (session->output_traces) { + ret = LTTNG_ERR_NOT_SNAPSHOT_SESSION; + goto error; + } + + if (output->id) { + DBG("Cmd snapshot del output id %" PRIu32 " for session %s", output->id, + session->name); + sout = snapshot_find_output_by_id(output->id, &session->snapshot); + } else if (*output->name != '\0') { + DBG("Cmd snapshot del output name %s for session %s", output->name, + session->name); + sout = snapshot_find_output_by_name(output->name, &session->snapshot); + } + if (!sout) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + snapshot_delete_output(&session->snapshot, sout); + snapshot_output_destroy(sout); + ret = LTTNG_OK; + +error: + rcu_read_unlock(); + return ret; +} + +/* + * Command LTTNG_SNAPSHOT_LIST_OUTPUT from lib lttng ctl. + * + * If no output is available, outputs is untouched and 0 is returned. + * + * Return the size of the newly allocated outputs or a negative LTTNG_ERR code. + */ +ssize_t cmd_snapshot_list_outputs(struct ltt_session *session, + struct lttng_snapshot_output **outputs) +{ + int ret, idx = 0; + struct lttng_snapshot_output *list = NULL; + struct lttng_ht_iter iter; + struct snapshot_output *output; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(outputs); + + DBG("Cmd snapshot list outputs for session %s", session->name); + + /* + * Permission denied to create an output if the session is not + * set in no output mode. + */ + if (session->output_traces) { + ret = -LTTNG_ERR_NOT_SNAPSHOT_SESSION; + goto end; + } + + if (session->snapshot.nb_output == 0) { + ret = 0; + goto end; + } + + list = (lttng_snapshot_output *) zmalloc(session->snapshot.nb_output * sizeof(*list)); + if (!list) { + ret = -LTTNG_ERR_NOMEM; + goto end; + } + + /* Copy list from session to the new list object. */ + rcu_read_lock(); + cds_lfht_for_each_entry(session->snapshot.output_ht->ht, &iter.iter, + output, node.node) { + LTTNG_ASSERT(output->consumer); + list[idx].id = output->id; + list[idx].max_size = output->max_size; + if (lttng_strncpy(list[idx].name, output->name, + sizeof(list[idx].name))) { + ret = -LTTNG_ERR_INVALID; + goto error; + } + if (output->consumer->type == CONSUMER_DST_LOCAL) { + if (lttng_strncpy(list[idx].ctrl_url, + output->consumer->dst.session_root_path, + sizeof(list[idx].ctrl_url))) { + ret = -LTTNG_ERR_INVALID; + goto error; + } + } else { + /* Control URI. */ + ret = uri_to_str_url(&output->consumer->dst.net.control, + list[idx].ctrl_url, sizeof(list[idx].ctrl_url)); + if (ret < 0) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + + /* Data URI. */ + ret = uri_to_str_url(&output->consumer->dst.net.data, + list[idx].data_url, sizeof(list[idx].data_url)); + if (ret < 0) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + } + idx++; + } + + *outputs = list; + list = NULL; + ret = session->snapshot.nb_output; +error: + rcu_read_unlock(); + free(list); +end: + return ret; +} + +/* + * Check if we can regenerate the metadata for this session. + * Only kernel, UST per-uid and non-live sessions are supported. + * + * Return 0 if the metadata can be generated, a LTTNG_ERR code otherwise. + */ +static +int check_regenerate_metadata_support(struct ltt_session *session) +{ + int ret; + + LTTNG_ASSERT(session); + + if (session->live_timer != 0) { + ret = LTTNG_ERR_LIVE_SESSION; + goto end; + } + if (!session->active) { + ret = LTTNG_ERR_SESSION_NOT_STARTED; + goto end; + } + if (session->ust_session) { + switch (session->ust_session->buffer_type) { + case LTTNG_BUFFER_PER_UID: + break; + case LTTNG_BUFFER_PER_PID: + ret = LTTNG_ERR_PER_PID_SESSION; + goto end; + default: + abort(); + ret = LTTNG_ERR_UNK; + goto end; + } + } + if (session->consumer->type == CONSUMER_DST_NET && + session->consumer->relay_minor_version < 8) { + ret = LTTNG_ERR_RELAYD_VERSION_FAIL; + goto end; + } + ret = 0; + +end: + return ret; +} + +static +int clear_metadata_file(int fd) +{ + int ret; + off_t lseek_ret; + + lseek_ret = lseek(fd, 0, SEEK_SET); + if (lseek_ret < 0) { + PERROR("lseek"); + ret = -1; + goto end; + } + + ret = ftruncate(fd, 0); + if (ret < 0) { + PERROR("ftruncate"); + goto end; + } + +end: + return ret; +} + +static +int ust_regenerate_metadata(struct ltt_ust_session *usess) +{ + int ret = 0; + struct buffer_reg_uid *uid_reg = NULL; + struct buffer_reg_session *session_reg = NULL; + + rcu_read_lock(); + cds_list_for_each_entry(uid_reg, &usess->buffer_reg_uid_list, lnode) { + struct ust_registry_session *registry; + struct ust_registry_channel *chan; + struct lttng_ht_iter iter_chan; + + session_reg = uid_reg->registry; + registry = session_reg->reg.ust; + + pthread_mutex_lock(®istry->lock); + registry->metadata_len_sent = 0; + memset(registry->metadata, 0, registry->metadata_alloc_len); + registry->metadata_len = 0; + registry->metadata_version++; + if (registry->metadata_fd > 0) { + /* Clear the metadata file's content. */ + ret = clear_metadata_file(registry->metadata_fd); + if (ret) { + pthread_mutex_unlock(®istry->lock); + goto end; + } + } + + ret = ust_metadata_session_statedump(registry, NULL, + registry->major, registry->minor); + if (ret) { + pthread_mutex_unlock(®istry->lock); + ERR("Failed to generate session metadata (err = %d)", + ret); + goto end; + } + cds_lfht_for_each_entry(registry->channels->ht, &iter_chan.iter, + chan, node.node) { + struct ust_registry_event *event; + struct lttng_ht_iter iter_event; + + ret = ust_metadata_channel_statedump(registry, chan); + if (ret) { + pthread_mutex_unlock(®istry->lock); + ERR("Failed to generate channel metadata " + "(err = %d)", ret); + goto end; + } + cds_lfht_for_each_entry(chan->ht->ht, &iter_event.iter, + event, node.node) { + ret = ust_metadata_event_statedump(registry, + chan, event); + if (ret) { + pthread_mutex_unlock(®istry->lock); + ERR("Failed to generate event metadata " + "(err = %d)", ret); + goto end; + } + } + } + pthread_mutex_unlock(®istry->lock); + } + +end: + rcu_read_unlock(); + return ret; +} + +/* + * Command LTTNG_REGENERATE_METADATA from the lttng-ctl library. + * + * Ask the consumer to truncate the existing metadata file(s) and + * then regenerate the metadata. Live and per-pid sessions are not + * supported and return an error. + * + * Return LTTNG_OK on success or else a LTTNG_ERR code. + */ +int cmd_regenerate_metadata(struct ltt_session *session) +{ + int ret; + + LTTNG_ASSERT(session); + + ret = check_regenerate_metadata_support(session); + if (ret) { + goto end; + } + + if (session->kernel_session) { + ret = kernctl_session_regenerate_metadata( + session->kernel_session->fd); + if (ret < 0) { + ERR("Failed to regenerate the kernel metadata"); + goto end; + } + } + + if (session->ust_session) { + ret = ust_regenerate_metadata(session->ust_session); + if (ret < 0) { + ERR("Failed to regenerate the UST metadata"); + goto end; + } + } + DBG("Cmd metadata regenerate for session %s", session->name); + ret = LTTNG_OK; + +end: + return ret; +} + +/* + * Command LTTNG_REGENERATE_STATEDUMP from the lttng-ctl library. + * + * Ask the tracer to regenerate a new statedump. + * + * Return LTTNG_OK on success or else a LTTNG_ERR code. + */ +int cmd_regenerate_statedump(struct ltt_session *session) +{ + int ret; + + LTTNG_ASSERT(session); + + if (!session->active) { + ret = LTTNG_ERR_SESSION_NOT_STARTED; + goto end; + } + + if (session->kernel_session) { + ret = kernctl_session_regenerate_statedump( + session->kernel_session->fd); + /* + * Currently, the statedump in kernel can only fail if out + * of memory. + */ + if (ret < 0) { + if (ret == -ENOMEM) { + ret = LTTNG_ERR_REGEN_STATEDUMP_NOMEM; + } else { + ret = LTTNG_ERR_REGEN_STATEDUMP_FAIL; + } + ERR("Failed to regenerate the kernel statedump"); + goto end; + } + } + + if (session->ust_session) { + ret = ust_app_regenerate_statedump_all(session->ust_session); + /* + * Currently, the statedump in UST always returns 0. + */ + if (ret < 0) { + ret = LTTNG_ERR_REGEN_STATEDUMP_FAIL; + ERR("Failed to regenerate the UST statedump"); + goto end; + } + } + DBG("Cmd regenerate statedump for session %s", session->name); + ret = LTTNG_OK; + +end: + return ret; +} + +static +enum lttng_error_code synchronize_tracer_notifier_register( + struct notification_thread_handle *notification_thread, + struct lttng_trigger *trigger, const struct lttng_credentials *cmd_creds) +{ + enum lttng_error_code ret_code; + const struct lttng_condition *condition = + lttng_trigger_get_const_condition(trigger); + const char *trigger_name; + uid_t trigger_owner; + enum lttng_trigger_status trigger_status; + const enum lttng_domain_type trigger_domain = + lttng_trigger_get_underlying_domain_type_restriction( + trigger); + + trigger_status = lttng_trigger_get_owner_uid(trigger, &trigger_owner); + LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + LTTNG_ASSERT(condition); + LTTNG_ASSERT(lttng_condition_get_type(condition) == + LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); + + trigger_status = lttng_trigger_get_name(trigger, &trigger_name); + trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? + trigger_name : "(anonymous)"; + + session_lock_list(); + switch (trigger_domain) { + case LTTNG_DOMAIN_KERNEL: + { + ret_code = kernel_register_event_notifier(trigger, cmd_creds); + if (ret_code != LTTNG_OK) { + enum lttng_error_code notif_thread_unregister_ret; + + notif_thread_unregister_ret = + notification_thread_command_unregister_trigger( + notification_thread, trigger); + + if (notif_thread_unregister_ret != LTTNG_OK) { + /* Return the original error code. */ + ERR("Failed to unregister trigger from notification thread during error recovery: trigger name = '%s', trigger owner uid = %d, error code = %d", + trigger_name, + (int) trigger_owner, + ret_code); + } + } + break; + } + case LTTNG_DOMAIN_UST: + ust_app_global_update_all_event_notifier_rules(); + break; + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + { + /* Agent domains. */ + struct agent *agt = agent_find_by_event_notifier_domain( + trigger_domain); + + if (!agt) { + agt = agent_create(trigger_domain); + if (!agt) { + ret_code = LTTNG_ERR_NOMEM; + goto end_unlock_session_list; + } + + agent_add(agt, the_trigger_agents_ht_by_domain); + } + + ret_code = (lttng_error_code) trigger_agent_enable(trigger, agt); + if (ret_code != LTTNG_OK) { + goto end_unlock_session_list; + } + + break; + } + case LTTNG_DOMAIN_NONE: + default: + abort(); + } + + ret_code = LTTNG_OK; +end_unlock_session_list: + session_unlock_list(); + return ret_code; +} + +enum lttng_error_code cmd_register_trigger(const struct lttng_credentials *cmd_creds, + struct lttng_trigger *trigger, + bool is_trigger_anonymous, + struct notification_thread_handle *notification_thread, + struct lttng_trigger **return_trigger) +{ + enum lttng_error_code ret_code; + const char *trigger_name; + uid_t trigger_owner; + enum lttng_trigger_status trigger_status; + + trigger_status = lttng_trigger_get_name(trigger, &trigger_name); + trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? + trigger_name : "(anonymous)"; + + trigger_status = lttng_trigger_get_owner_uid( + trigger, &trigger_owner); + LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + DBG("Running register trigger command: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", + trigger_name, (int) trigger_owner, + (int) lttng_credentials_get_uid(cmd_creds)); + + /* + * Validate the trigger credentials against the command credentials. + * Only the root user can register a trigger with non-matching + * credentials. + */ + if (!lttng_credentials_is_equal_uid( + lttng_trigger_get_credentials(trigger), + cmd_creds)) { + if (lttng_credentials_get_uid(cmd_creds) != 0) { + ERR("Trigger credentials do not match the command credentials: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", + trigger_name, (int) trigger_owner, + (int) lttng_credentials_get_uid(cmd_creds)); + ret_code = LTTNG_ERR_INVALID_TRIGGER; + goto end; + } + } + + /* + * The bytecode generation also serves as a validation step for the + * bytecode expressions. + */ + ret_code = lttng_trigger_generate_bytecode(trigger, cmd_creds); + if (ret_code != LTTNG_OK) { + ERR("Failed to generate bytecode of trigger: trigger name = '%s', trigger owner uid = %d, error code = %d", + trigger_name, (int) trigger_owner, ret_code); + goto end; + } + + /* + * A reference to the trigger is acquired by the notification thread. + * It is safe to return the same trigger to the caller since it the + * other user holds a reference. + * + * The trigger is modified during the execution of the + * "register trigger" command. However, by the time the command returns, + * it is safe to use without any locking as its properties are + * immutable. + */ + ret_code = notification_thread_command_register_trigger( + notification_thread, trigger, is_trigger_anonymous); + if (ret_code != LTTNG_OK) { + DBG("Failed to register trigger to notification thread: trigger name = '%s', trigger owner uid = %d, error code = %d", + trigger_name, (int) trigger_owner, ret_code); + goto end; + } + + trigger_status = lttng_trigger_get_name(trigger, &trigger_name); + trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? + trigger_name : "(anonymous)"; + + /* + * Synchronize tracers if the trigger adds an event notifier. + */ + if (lttng_trigger_needs_tracer_notifier(trigger)) { + ret_code = synchronize_tracer_notifier_register(notification_thread, + trigger, cmd_creds); + if (ret_code != LTTNG_OK) { + ERR("Error registering tracer notifier: %s", + lttng_strerror(-ret_code)); + goto end; + } + } + + /* + * Return an updated trigger to the client. + * + * Since a modified version of the same trigger is returned, acquire a + * reference to the trigger so the caller doesn't have to care if those + * are distinct instances or not. + */ + if (ret_code == LTTNG_OK) { + lttng_trigger_get(trigger); + *return_trigger = trigger; + /* Ownership of trigger was transferred to caller. */ + trigger = NULL; + } +end: + return ret_code; +} + +static +enum lttng_error_code synchronize_tracer_notifier_unregister( + const struct lttng_trigger *trigger) +{ + enum lttng_error_code ret_code; + const struct lttng_condition *condition = + lttng_trigger_get_const_condition(trigger); + const enum lttng_domain_type trigger_domain = + lttng_trigger_get_underlying_domain_type_restriction( + trigger); + + LTTNG_ASSERT(condition); + LTTNG_ASSERT(lttng_condition_get_type(condition) == + LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); + + session_lock_list(); + switch (trigger_domain) { + case LTTNG_DOMAIN_KERNEL: + ret_code = kernel_unregister_event_notifier(trigger); + if (ret_code != LTTNG_OK) { + goto end_unlock_session_list; + } + + break; + case LTTNG_DOMAIN_UST: + ust_app_global_update_all_event_notifier_rules(); + break; + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + { + /* Agent domains. */ + struct agent *agt = agent_find_by_event_notifier_domain( + trigger_domain); + + /* + * This trigger was never registered in the first place. Calling + * this function under those circumstances is an internal error. + */ + LTTNG_ASSERT(agt); + ret_code = (lttng_error_code) trigger_agent_disable(trigger, agt); + if (ret_code != LTTNG_OK) { + goto end_unlock_session_list; + } + + break; + } + case LTTNG_DOMAIN_NONE: + default: + abort(); + } + + ret_code = LTTNG_OK; + +end_unlock_session_list: + session_unlock_list(); + return ret_code; +} + +enum lttng_error_code cmd_unregister_trigger(const struct lttng_credentials *cmd_creds, + const struct lttng_trigger *trigger, + struct notification_thread_handle *notification_thread) +{ + enum lttng_error_code ret_code; + const char *trigger_name; + uid_t trigger_owner; + enum lttng_trigger_status trigger_status; + struct lttng_trigger *sessiond_trigger = NULL; + + trigger_status = lttng_trigger_get_name(trigger, &trigger_name); + trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? trigger_name : "(anonymous)"; + trigger_status = lttng_trigger_get_owner_uid(trigger, &trigger_owner); + LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + DBG("Running unregister trigger command: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", + trigger_name, (int) trigger_owner, + (int) lttng_credentials_get_uid(cmd_creds)); + + /* + * Validate the trigger credentials against the command credentials. + * Only the root user can unregister a trigger with non-matching + * credentials. + */ + if (!lttng_credentials_is_equal_uid( + lttng_trigger_get_credentials(trigger), + cmd_creds)) { + if (lttng_credentials_get_uid(cmd_creds) != 0) { + ERR("Trigger credentials do not match the command credentials: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", + trigger_name, (int) trigger_owner, + (int) lttng_credentials_get_uid(cmd_creds)); + ret_code = LTTNG_ERR_INVALID_TRIGGER; + goto end; + } + } + + /* Fetch the sessiond side trigger object. */ + ret_code = notification_thread_command_get_trigger( + notification_thread, trigger, &sessiond_trigger); + if (ret_code != LTTNG_OK) { + DBG("Failed to get trigger from notification thread during unregister: trigger name = '%s', trigger owner uid = %d, error code = %d", + trigger_name, (int) trigger_owner, ret_code); + goto end; + } + + LTTNG_ASSERT(sessiond_trigger); + + /* + * From this point on, no matter what, consider the trigger + * unregistered. + * + * We set the unregistered state of the sessiond side trigger object in + * the client thread since we want to minimize the possibility of the + * notification thread being stalled due to a long execution of an + * action that required the trigger lock. + */ + lttng_trigger_set_as_unregistered(sessiond_trigger); + + ret_code = notification_thread_command_unregister_trigger(notification_thread, + trigger); + if (ret_code != LTTNG_OK) { + DBG("Failed to unregister trigger from notification thread: trigger name = '%s', trigger owner uid = %d, error code = %d", + trigger_name, (int) trigger_owner, ret_code); + goto end; + } + + /* + * Synchronize tracers if the trigger removes an event notifier. + * Do this even if the trigger unregistration failed to at least stop + * the tracers from producing notifications associated with this + * event notifier. + */ + if (lttng_trigger_needs_tracer_notifier(trigger)) { + ret_code = synchronize_tracer_notifier_unregister(trigger); + if (ret_code != LTTNG_OK) { + ERR("Error unregistering trigger to tracer."); + goto end; + } + + } + +end: + lttng_trigger_put(sessiond_trigger); + return ret_code; +} + +enum lttng_error_code cmd_list_triggers(struct command_ctx *cmd_ctx, + struct notification_thread_handle *notification_thread, + struct lttng_triggers **return_triggers) +{ + int ret; + enum lttng_error_code ret_code; + struct lttng_triggers *triggers = NULL; + + /* Get the set of triggers from the notification thread. */ + ret_code = notification_thread_command_list_triggers( + notification_thread, cmd_ctx->creds.uid, &triggers); + if (ret_code != LTTNG_OK) { + goto end; + } + + ret = lttng_triggers_remove_hidden_triggers(triggers); + if (ret) { + ret_code = LTTNG_ERR_UNK; + goto end; + } + + *return_triggers = triggers; + triggers = NULL; + ret_code = LTTNG_OK; +end: + lttng_triggers_destroy(triggers); + return ret_code; +} + +enum lttng_error_code cmd_execute_error_query(const struct lttng_credentials *cmd_creds, + const struct lttng_error_query *query, + struct lttng_error_query_results **_results, + struct notification_thread_handle *notification_thread) +{ + enum lttng_error_code ret_code; + const struct lttng_trigger *query_target_trigger; + const struct lttng_action *query_target_action = NULL; + struct lttng_trigger *matching_trigger = NULL; + const char *trigger_name; + uid_t trigger_owner; + enum lttng_trigger_status trigger_status; + struct lttng_error_query_results *results = NULL; + + switch (lttng_error_query_get_target_type(query)) { + case LTTNG_ERROR_QUERY_TARGET_TYPE_TRIGGER: + query_target_trigger = lttng_error_query_trigger_borrow_target(query); + break; + case LTTNG_ERROR_QUERY_TARGET_TYPE_CONDITION: + query_target_trigger = + lttng_error_query_condition_borrow_target(query); + break; + case LTTNG_ERROR_QUERY_TARGET_TYPE_ACTION: + query_target_trigger = lttng_error_query_action_borrow_trigger_target( + query); + break; + default: + abort(); + } + + LTTNG_ASSERT(query_target_trigger); + + ret_code = notification_thread_command_get_trigger(notification_thread, + query_target_trigger, &matching_trigger); + if (ret_code != LTTNG_OK) { + goto end; + } + + /* No longer needed. */ + query_target_trigger = NULL; + + if (lttng_error_query_get_target_type(query) == + LTTNG_ERROR_QUERY_TARGET_TYPE_ACTION) { + /* Get the sessiond-side version of the target action. */ + query_target_action = + lttng_error_query_action_borrow_action_target( + query, matching_trigger); + } + + trigger_status = lttng_trigger_get_name(matching_trigger, &trigger_name); + trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? + trigger_name : "(anonymous)"; + trigger_status = lttng_trigger_get_owner_uid(matching_trigger, + &trigger_owner); + LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + results = lttng_error_query_results_create(); + if (!results) { + ret_code = LTTNG_ERR_NOMEM; + goto end; + } + + DBG("Running \"execute error query\" command: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", + trigger_name, (int) trigger_owner, + (int) lttng_credentials_get_uid(cmd_creds)); + + /* + * Validate the trigger credentials against the command credentials. + * Only the root user can target a trigger with non-matching + * credentials. + */ + if (!lttng_credentials_is_equal_uid( + lttng_trigger_get_credentials(matching_trigger), + cmd_creds)) { + if (lttng_credentials_get_uid(cmd_creds) != 0) { + ERR("Trigger credentials do not match the command credentials: trigger name = '%s', trigger owner uid = %d, command creds uid = %d", + trigger_name, (int) trigger_owner, + (int) lttng_credentials_get_uid(cmd_creds)); + ret_code = LTTNG_ERR_INVALID_TRIGGER; + goto end; + } + } + + switch (lttng_error_query_get_target_type(query)) { + case LTTNG_ERROR_QUERY_TARGET_TYPE_TRIGGER: + trigger_status = lttng_trigger_add_error_results( + matching_trigger, results); + + switch (trigger_status) { + case LTTNG_TRIGGER_STATUS_OK: + break; + default: + ret_code = LTTNG_ERR_UNK; + goto end; + } + + break; + case LTTNG_ERROR_QUERY_TARGET_TYPE_CONDITION: + { + trigger_status = lttng_trigger_condition_add_error_results( + matching_trigger, results); + + switch (trigger_status) { + case LTTNG_TRIGGER_STATUS_OK: + break; + default: + ret_code = LTTNG_ERR_UNK; + goto end; + } + + break; + } + case LTTNG_ERROR_QUERY_TARGET_TYPE_ACTION: + { + const enum lttng_action_status action_status = + lttng_action_add_error_query_results( + query_target_action, results); + + switch (action_status) { + case LTTNG_ACTION_STATUS_OK: + break; + default: + ret_code = LTTNG_ERR_UNK; + goto end; + } + + break; + } + default: + abort(); + break; + } + + *_results = results; + results = NULL; + ret_code = LTTNG_OK; +end: + lttng_trigger_put(matching_trigger); + lttng_error_query_results_destroy(results); + return ret_code; +} + +/* + * Send relayd sockets from snapshot output to consumer. Ignore request if the + * snapshot output is *not* set with a remote destination. + * + * Return LTTNG_OK on success or a LTTNG_ERR code. + */ +static enum lttng_error_code set_relayd_for_snapshot( + struct consumer_output *output, + const struct ltt_session *session) +{ + enum lttng_error_code status = LTTNG_OK; + struct lttng_ht_iter iter; + struct consumer_socket *socket; + LTTNG_OPTIONAL(uint64_t) current_chunk_id = {}; + const char *base_path; + + LTTNG_ASSERT(output); + LTTNG_ASSERT(session); + + DBG2("Set relayd object from snapshot output"); + + if (session->current_trace_chunk) { + enum lttng_trace_chunk_status chunk_status = + lttng_trace_chunk_get_id( + session->current_trace_chunk, + ¤t_chunk_id.value); + + if (chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK) { + current_chunk_id.is_set = true; + } else { + ERR("Failed to get current trace chunk id"); + status = LTTNG_ERR_UNK; + goto error; + } + } + + /* Ignore if snapshot consumer output is not network. */ + if (output->type != CONSUMER_DST_NET) { + goto error; + } + + /* + * The snapshot record URI base path overrides the session + * base path. + */ + if (output->dst.net.control.subdir[0] != '\0') { + base_path = output->dst.net.control.subdir; + } else { + base_path = session->base_path; + } + + /* + * For each consumer socket, create and send the relayd object of the + * snapshot output. + */ + rcu_read_lock(); + cds_lfht_for_each_entry(output->socks->ht, &iter.iter, + socket, node.node) { + pthread_mutex_lock(socket->lock); + status = send_consumer_relayd_sockets(LTTNG_DOMAIN_NONE, session->id, + output, socket, + session->name, session->hostname, + base_path, + session->live_timer, + current_chunk_id.is_set ? ¤t_chunk_id.value : NULL, + session->creation_time, + session->name_contains_creation_time); + pthread_mutex_unlock(socket->lock); + if (status != LTTNG_OK) { + rcu_read_unlock(); + goto error; + } + } + rcu_read_unlock(); + +error: + return status; +} + +/* + * Record a kernel snapshot. + * + * Return LTTNG_OK on success or a LTTNG_ERR code. + */ +static enum lttng_error_code record_kernel_snapshot( + struct ltt_kernel_session *ksess, + const struct consumer_output *output, + const struct ltt_session *session, + int wait, uint64_t nb_packets_per_stream) +{ + enum lttng_error_code status; + + LTTNG_ASSERT(ksess); + LTTNG_ASSERT(output); + LTTNG_ASSERT(session); + + status = kernel_snapshot_record( + ksess, output, wait, nb_packets_per_stream); + return status; +} + +/* + * Record a UST snapshot. + * + * Returns LTTNG_OK on success or a LTTNG_ERR error code. + */ +static enum lttng_error_code record_ust_snapshot(struct ltt_ust_session *usess, + const struct consumer_output *output, + const struct ltt_session *session, + int wait, uint64_t nb_packets_per_stream) +{ + enum lttng_error_code status; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(output); + LTTNG_ASSERT(session); + + status = ust_app_snapshot_record( + usess, output, wait, nb_packets_per_stream); + return status; +} + +static +uint64_t get_session_size_one_more_packet_per_stream( + const struct ltt_session *session, uint64_t cur_nr_packets) +{ + uint64_t tot_size = 0; + + if (session->kernel_session) { + struct ltt_kernel_channel *chan; + const struct ltt_kernel_session *ksess = + session->kernel_session; + + cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { + if (cur_nr_packets >= chan->channel->attr.num_subbuf) { + /* + * Don't take channel into account if we + * already grab all its packets. + */ + continue; + } + tot_size += chan->channel->attr.subbuf_size + * chan->stream_count; + } + } + + if (session->ust_session) { + const struct ltt_ust_session *usess = session->ust_session; + + tot_size += ust_app_get_size_one_more_packet_per_stream(usess, + cur_nr_packets); + } + + return tot_size; +} + +/* + * Calculate the number of packets we can grab from each stream that + * fits within the overall snapshot max size. + * + * Returns -1 on error, 0 means infinite number of packets, else > 0 is + * the number of packets per stream. + * + * TODO: this approach is not perfect: we consider the worse case + * (packet filling the sub-buffers) as an upper bound, but we could do + * better if we do this calculation while we actually grab the packet + * content: we would know how much padding we don't actually store into + * the file. + * + * This algorithm is currently bounded by the number of packets per + * stream. + * + * Since we call this algorithm before actually grabbing the data, it's + * an approximation: for instance, applications could appear/disappear + * in between this call and actually grabbing data. + */ +static +int64_t get_session_nb_packets_per_stream(const struct ltt_session *session, + uint64_t max_size) +{ + int64_t size_left; + uint64_t cur_nb_packets = 0; + + if (!max_size) { + return 0; /* Infinite */ + } + + size_left = max_size; + for (;;) { + uint64_t one_more_packet_tot_size; + + one_more_packet_tot_size = get_session_size_one_more_packet_per_stream( + session, cur_nb_packets); + if (!one_more_packet_tot_size) { + /* We are already grabbing all packets. */ + break; + } + size_left -= one_more_packet_tot_size; + if (size_left < 0) { + break; + } + cur_nb_packets++; + } + if (!cur_nb_packets && size_left != max_size) { + /* Not enough room to grab one packet of each stream, error. */ + return -1; + } + return cur_nb_packets; +} + +static +enum lttng_error_code snapshot_record(struct ltt_session *session, + const struct snapshot_output *snapshot_output, int wait) +{ + int64_t nb_packets_per_stream; + char snapshot_chunk_name[LTTNG_NAME_MAX]; + int ret; + enum lttng_error_code ret_code = LTTNG_OK; + struct lttng_trace_chunk *snapshot_trace_chunk; + struct consumer_output *original_ust_consumer_output = NULL; + struct consumer_output *original_kernel_consumer_output = NULL; + struct consumer_output *snapshot_ust_consumer_output = NULL; + struct consumer_output *snapshot_kernel_consumer_output = NULL; + + ret = snprintf(snapshot_chunk_name, sizeof(snapshot_chunk_name), + "%s-%s-%" PRIu64, + snapshot_output->name, + snapshot_output->datetime, + snapshot_output->nb_snapshot); + if (ret < 0 || ret >= sizeof(snapshot_chunk_name)) { + ERR("Failed to format snapshot name"); + ret_code = LTTNG_ERR_INVALID; + goto error; + } + DBG("Recording snapshot \"%s\" for session \"%s\" with chunk name \"%s\"", + snapshot_output->name, session->name, + snapshot_chunk_name); + if (!session->kernel_session && !session->ust_session) { + ERR("Failed to record snapshot as no channels exist"); + ret_code = LTTNG_ERR_NO_CHANNEL; + goto error; + } + + if (session->kernel_session) { + original_kernel_consumer_output = + session->kernel_session->consumer; + snapshot_kernel_consumer_output = + consumer_copy_output(snapshot_output->consumer); + strcpy(snapshot_kernel_consumer_output->chunk_path, + snapshot_chunk_name); + + /* Copy the original domain subdir. */ + strcpy(snapshot_kernel_consumer_output->domain_subdir, + original_kernel_consumer_output->domain_subdir); + + ret = consumer_copy_sockets(snapshot_kernel_consumer_output, + original_kernel_consumer_output); + if (ret < 0) { + ERR("Failed to copy consumer sockets from snapshot output configuration"); + ret_code = LTTNG_ERR_NOMEM; + goto error; + } + ret_code = set_relayd_for_snapshot( + snapshot_kernel_consumer_output, session); + if (ret_code != LTTNG_OK) { + ERR("Failed to setup relay daemon for kernel tracer snapshot"); + goto error; + } + session->kernel_session->consumer = + snapshot_kernel_consumer_output; + } + if (session->ust_session) { + original_ust_consumer_output = session->ust_session->consumer; + snapshot_ust_consumer_output = + consumer_copy_output(snapshot_output->consumer); + strcpy(snapshot_ust_consumer_output->chunk_path, + snapshot_chunk_name); + + /* Copy the original domain subdir. */ + strcpy(snapshot_ust_consumer_output->domain_subdir, + original_ust_consumer_output->domain_subdir); + + ret = consumer_copy_sockets(snapshot_ust_consumer_output, + original_ust_consumer_output); + if (ret < 0) { + ERR("Failed to copy consumer sockets from snapshot output configuration"); + ret_code = LTTNG_ERR_NOMEM; + goto error; + } + ret_code = set_relayd_for_snapshot( + snapshot_ust_consumer_output, session); + if (ret_code != LTTNG_OK) { + ERR("Failed to setup relay daemon for userspace tracer snapshot"); + goto error; + } + session->ust_session->consumer = + snapshot_ust_consumer_output; + } + + snapshot_trace_chunk = session_create_new_trace_chunk(session, + snapshot_kernel_consumer_output ?: + snapshot_ust_consumer_output, + consumer_output_get_base_path( + snapshot_output->consumer), + snapshot_chunk_name); + if (!snapshot_trace_chunk) { + ERR("Failed to create temporary trace chunk to record a snapshot of session \"%s\"", + session->name); + ret_code = LTTNG_ERR_CREATE_DIR_FAIL; + goto error; + } + LTTNG_ASSERT(!session->current_trace_chunk); + ret = session_set_trace_chunk(session, snapshot_trace_chunk, NULL); + lttng_trace_chunk_put(snapshot_trace_chunk); + snapshot_trace_chunk = NULL; + if (ret) { + ERR("Failed to set temporary trace chunk to record a snapshot of session \"%s\"", + session->name); + ret_code = LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; + goto error; + } + + nb_packets_per_stream = get_session_nb_packets_per_stream(session, + snapshot_output->max_size); + if (nb_packets_per_stream < 0) { + ret_code = LTTNG_ERR_MAX_SIZE_INVALID; + goto error_close_trace_chunk; + } + + if (session->kernel_session) { + ret_code = record_kernel_snapshot(session->kernel_session, + snapshot_kernel_consumer_output, session, + wait, nb_packets_per_stream); + if (ret_code != LTTNG_OK) { + goto error_close_trace_chunk; + } + } + + if (session->ust_session) { + ret_code = record_ust_snapshot(session->ust_session, + snapshot_ust_consumer_output, session, + wait, nb_packets_per_stream); + if (ret_code != LTTNG_OK) { + goto error_close_trace_chunk; + } + } + +error_close_trace_chunk: + if (session_set_trace_chunk(session, NULL, &snapshot_trace_chunk)) { + ERR("Failed to release the current trace chunk of session \"%s\"", + session->name); + ret_code = LTTNG_ERR_UNK; + } + + if (session_close_trace_chunk(session, snapshot_trace_chunk, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION, NULL)) { + /* + * Don't goto end; make sure the chunk is closed for the session + * to allow future snapshots. + */ + ERR("Failed to close snapshot trace chunk of session \"%s\"", + session->name); + ret_code = LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; + } +error: + if (original_ust_consumer_output) { + session->ust_session->consumer = original_ust_consumer_output; + } + if (original_kernel_consumer_output) { + session->kernel_session->consumer = + original_kernel_consumer_output; + } + consumer_output_put(snapshot_ust_consumer_output); + consumer_output_put(snapshot_kernel_consumer_output); + return ret_code; +} + +/* + * Command LTTNG_SNAPSHOT_RECORD from lib lttng ctl. + * + * The wait parameter is ignored so this call always wait for the snapshot to + * complete before returning. + * + * Return LTTNG_OK on success or else a LTTNG_ERR code. + */ +int cmd_snapshot_record(struct ltt_session *session, + const struct lttng_snapshot_output *output, int wait) +{ + enum lttng_error_code cmd_ret = LTTNG_OK; + int ret; + unsigned int snapshot_success = 0; + char datetime[16]; + struct snapshot_output *tmp_output = NULL; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(output); + + DBG("Cmd snapshot record for session %s", session->name); + + /* Get the datetime for the snapshot output directory. */ + ret = utils_get_current_time_str("%Y%m%d-%H%M%S", datetime, + sizeof(datetime)); + if (!ret) { + cmd_ret = LTTNG_ERR_INVALID; + goto error; + } + + /* + * Permission denied to create an output if the session is not + * set in no output mode. + */ + if (session->output_traces) { + cmd_ret = LTTNG_ERR_NOT_SNAPSHOT_SESSION; + goto error; + } + + /* The session needs to be started at least once. */ + if (!session->has_been_started) { + cmd_ret = LTTNG_ERR_START_SESSION_ONCE; + goto error; + } + + /* Use temporary output for the session. */ + if (*output->ctrl_url != '\0') { + tmp_output = snapshot_output_alloc(); + if (!tmp_output) { + cmd_ret = LTTNG_ERR_NOMEM; + goto error; + } + + ret = snapshot_output_init(session, output->max_size, + output->name, + output->ctrl_url, output->data_url, + session->consumer, + tmp_output, NULL); + if (ret < 0) { + if (ret == -ENOMEM) { + cmd_ret = LTTNG_ERR_NOMEM; + } else { + cmd_ret = LTTNG_ERR_INVALID; + } + goto error; + } + /* Use the global session count for the temporary snapshot. */ + tmp_output->nb_snapshot = session->snapshot.nb_snapshot; + + /* Use the global datetime */ + memcpy(tmp_output->datetime, datetime, sizeof(datetime)); + cmd_ret = snapshot_record(session, tmp_output, wait); + if (cmd_ret != LTTNG_OK) { + goto error; + } + snapshot_success = 1; + } else { + struct snapshot_output *sout; + struct lttng_ht_iter iter; + + rcu_read_lock(); + cds_lfht_for_each_entry(session->snapshot.output_ht->ht, + &iter.iter, sout, node.node) { + struct snapshot_output output_copy; + + /* + * Make a local copy of the output and override output + * parameters with those provided as part of the + * command. + */ + memcpy(&output_copy, sout, sizeof(output_copy)); + + if (output->max_size != (uint64_t) -1ULL) { + output_copy.max_size = output->max_size; + } + + output_copy.nb_snapshot = session->snapshot.nb_snapshot; + memcpy(output_copy.datetime, datetime, + sizeof(datetime)); + + /* Use temporary name. */ + if (*output->name != '\0') { + if (lttng_strncpy(output_copy.name, + output->name, + sizeof(output_copy.name))) { + cmd_ret = LTTNG_ERR_INVALID; + rcu_read_unlock(); + goto error; + } + } + + cmd_ret = snapshot_record(session, &output_copy, wait); + if (cmd_ret != LTTNG_OK) { + rcu_read_unlock(); + goto error; + } + snapshot_success = 1; + } + rcu_read_unlock(); + } + + if (snapshot_success) { + session->snapshot.nb_snapshot++; + } else { + cmd_ret = LTTNG_ERR_SNAPSHOT_FAIL; + } + +error: + if (tmp_output) { + snapshot_output_destroy(tmp_output); + } + return cmd_ret; +} + +/* + * Command LTTNG_SET_SESSION_SHM_PATH processed by the client thread. + */ +int cmd_set_session_shm_path(struct ltt_session *session, + const char *shm_path) +{ + /* Safety net */ + LTTNG_ASSERT(session); + + /* + * Can only set shm path before session is started. + */ + if (session->has_been_started) { + return LTTNG_ERR_SESSION_STARTED; + } + + strncpy(session->shm_path, shm_path, + sizeof(session->shm_path)); + session->shm_path[sizeof(session->shm_path) - 1] = '\0'; + + return LTTNG_OK; +} + +/* + * Command LTTNG_ROTATE_SESSION from the lttng-ctl library. + * + * Ask the consumer to rotate the session output directory. + * The session lock must be held. + * + * Returns LTTNG_OK on success or else a negative LTTng error code. + */ +int cmd_rotate_session(struct ltt_session *session, + struct lttng_rotate_session_return *rotate_return, + bool quiet_rotation, + enum lttng_trace_chunk_command_type command) +{ + int ret; + uint64_t ongoing_rotation_chunk_id; + enum lttng_error_code cmd_ret = LTTNG_OK; + struct lttng_trace_chunk *chunk_being_archived = NULL; + struct lttng_trace_chunk *new_trace_chunk = NULL; + enum lttng_trace_chunk_status chunk_status; + bool failed_to_rotate = false; + enum lttng_error_code rotation_fail_code = LTTNG_OK; + + LTTNG_ASSERT(session); + + if (!session->has_been_started) { + cmd_ret = LTTNG_ERR_START_SESSION_ONCE; + goto end; + } + + /* + * Explicit rotation is not supported for live sessions. + * However, live sessions can perform a quiet rotation on + * destroy. + * Rotation is not supported for snapshot traces (no output). + */ + if ((!quiet_rotation && session->live_timer) || + !session->output_traces) { + cmd_ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE; + goto end; + } + + /* Unsupported feature in lttng-relayd before 2.11. */ + if (!quiet_rotation && session->consumer->type == CONSUMER_DST_NET && + (session->consumer->relay_major_version == 2 && + session->consumer->relay_minor_version < 11)) { + cmd_ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE_RELAY; + goto end; + } + + /* Unsupported feature in lttng-modules before 2.8 (lack of sequence number). */ + if (session->kernel_session && !kernel_supports_ring_buffer_packet_sequence_number()) { + cmd_ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE_KERNEL; + goto end; + } + + if (session->rotation_state == LTTNG_ROTATION_STATE_ONGOING) { + DBG("Refusing to launch a rotation; a rotation is already in progress for session %s", + session->name); + cmd_ret = LTTNG_ERR_ROTATION_PENDING; + goto end; + } + + /* + * After a stop, we only allow one rotation to occur, the other ones are + * useless until a new start. + */ + if (session->rotated_after_last_stop) { + DBG("Session \"%s\" was already rotated after stop, refusing rotation", + session->name); + cmd_ret = LTTNG_ERR_ROTATION_MULTIPLE_AFTER_STOP; + goto end; + } + + /* + * After a stop followed by a clear, disallow following rotations a they would + * generate empty chunks. + */ + if (session->cleared_after_last_stop) { + DBG("Session \"%s\" was already cleared after stop, refusing rotation", + session->name); + cmd_ret = LTTNG_ERR_ROTATION_AFTER_STOP_CLEAR; + goto end; + } + + if (session->active) { + new_trace_chunk = session_create_new_trace_chunk(session, NULL, + NULL, NULL); + if (!new_trace_chunk) { + cmd_ret = LTTNG_ERR_CREATE_DIR_FAIL; + goto error; + } + } + + /* + * The current trace chunk becomes the chunk being archived. + * + * After this point, "chunk_being_archived" must absolutely + * be closed on the consumer(s), otherwise it will never be + * cleaned-up, which will result in a leak. + */ + ret = session_set_trace_chunk(session, new_trace_chunk, + &chunk_being_archived); + if (ret) { + cmd_ret = LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; + goto error; + } + + if (session->kernel_session) { + cmd_ret = kernel_rotate_session(session); + if (cmd_ret != LTTNG_OK) { + failed_to_rotate = true; + rotation_fail_code = cmd_ret; + } + } + if (session->ust_session) { + cmd_ret = ust_app_rotate_session(session); + if (cmd_ret != LTTNG_OK) { + failed_to_rotate = true; + rotation_fail_code = cmd_ret; + } + } + + if (!session->active) { + session->rotated_after_last_stop = true; + } + + if (!chunk_being_archived) { + DBG("Rotating session \"%s\" from a \"NULL\" trace chunk to a new trace chunk, skipping completion check", + session->name); + if (failed_to_rotate) { + cmd_ret = rotation_fail_code; + goto error; + } + cmd_ret = LTTNG_OK; + goto end; + } + + session->rotation_state = LTTNG_ROTATION_STATE_ONGOING; + chunk_status = lttng_trace_chunk_get_id(chunk_being_archived, + &ongoing_rotation_chunk_id); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + + ret = session_close_trace_chunk(session, chunk_being_archived, + command, session->last_chunk_path); + if (ret) { + cmd_ret = LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; + goto error; + } + + if (failed_to_rotate) { + cmd_ret = rotation_fail_code; + goto error; + } + + session->quiet_rotation = quiet_rotation; + ret = timer_session_rotation_pending_check_start(session, + DEFAULT_ROTATE_PENDING_TIMER); + if (ret) { + cmd_ret = LTTNG_ERR_UNK; + goto error; + } + + if (rotate_return) { + rotate_return->rotation_id = ongoing_rotation_chunk_id; + } + + session->chunk_being_archived = chunk_being_archived; + chunk_being_archived = NULL; + if (!quiet_rotation) { + ret = notification_thread_command_session_rotation_ongoing( + the_notification_thread_handle, session->name, + session->uid, session->gid, + ongoing_rotation_chunk_id); + if (ret != LTTNG_OK) { + ERR("Failed to notify notification thread that a session rotation is ongoing for session %s", + session->name); + cmd_ret = (lttng_error_code) ret; + } + } + + DBG("Cmd rotate session %s, archive_id %" PRIu64 " sent", + session->name, ongoing_rotation_chunk_id); +end: + lttng_trace_chunk_put(new_trace_chunk); + lttng_trace_chunk_put(chunk_being_archived); + ret = (cmd_ret == LTTNG_OK) ? cmd_ret : -((int) cmd_ret); + return ret; +error: + if (session_reset_rotation_state(session, + LTTNG_ROTATION_STATE_ERROR)) { + ERR("Failed to reset rotation state of session \"%s\"", + session->name); + } + goto end; +} + +/* + * Command LTTNG_ROTATION_GET_INFO from the lttng-ctl library. + * + * Check if the session has finished its rotation. + * + * Return LTTNG_OK on success or else an LTTNG_ERR code. + */ +int cmd_rotate_get_info(struct ltt_session *session, + struct lttng_rotation_get_info_return *info_return, + uint64_t rotation_id) +{ + enum lttng_error_code cmd_ret = LTTNG_OK; + enum lttng_rotation_state rotation_state; + + DBG("Cmd rotate_get_info session %s, rotation id %" PRIu64, session->name, + session->most_recent_chunk_id.value); + + if (session->chunk_being_archived) { + enum lttng_trace_chunk_status chunk_status; + uint64_t chunk_id; + + chunk_status = lttng_trace_chunk_get_id( + session->chunk_being_archived, + &chunk_id); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + + rotation_state = rotation_id == chunk_id ? + LTTNG_ROTATION_STATE_ONGOING : + LTTNG_ROTATION_STATE_EXPIRED; + } else { + if (session->last_archived_chunk_id.is_set && + rotation_id != session->last_archived_chunk_id.value) { + rotation_state = LTTNG_ROTATION_STATE_EXPIRED; + } else { + rotation_state = session->rotation_state; + } + } + + switch (rotation_state) { + case LTTNG_ROTATION_STATE_NO_ROTATION: + DBG("Reporting that no rotation has occurred within the lifetime of session \"%s\"", + session->name); + goto end; + case LTTNG_ROTATION_STATE_EXPIRED: + DBG("Reporting that the rotation state of rotation id %" PRIu64 " of session \"%s\" has expired", + rotation_id, session->name); + break; + case LTTNG_ROTATION_STATE_ONGOING: + DBG("Reporting that rotation id %" PRIu64 " of session \"%s\" is still pending", + rotation_id, session->name); + break; + case LTTNG_ROTATION_STATE_COMPLETED: + { + int fmt_ret; + char *chunk_path; + char *current_tracing_path_reply; + size_t current_tracing_path_reply_len; + + DBG("Reporting that rotation id %" PRIu64 " of session \"%s\" is completed", + rotation_id, session->name); + + switch (session_get_consumer_destination_type(session)) { + case CONSUMER_DST_LOCAL: + current_tracing_path_reply = + info_return->location.local.absolute_path; + current_tracing_path_reply_len = + sizeof(info_return->location.local.absolute_path); + info_return->location_type = + (int8_t) LTTNG_TRACE_ARCHIVE_LOCATION_TYPE_LOCAL; + fmt_ret = asprintf(&chunk_path, + "%s/" DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY "/%s", + session_get_base_path(session), + session->last_archived_chunk_name); + if (fmt_ret == -1) { + PERROR("Failed to format the path of the last archived trace chunk"); + info_return->status = LTTNG_ROTATION_STATUS_ERROR; + cmd_ret = LTTNG_ERR_UNK; + goto end; + } + break; + case CONSUMER_DST_NET: + { + uint16_t ctrl_port, data_port; + + current_tracing_path_reply = + info_return->location.relay.relative_path; + current_tracing_path_reply_len = + sizeof(info_return->location.relay.relative_path); + /* Currently the only supported relay protocol. */ + info_return->location.relay.protocol = + (int8_t) LTTNG_TRACE_ARCHIVE_LOCATION_RELAY_PROTOCOL_TYPE_TCP; + + fmt_ret = lttng_strncpy(info_return->location.relay.host, + session_get_net_consumer_hostname(session), + sizeof(info_return->location.relay.host)); + if (fmt_ret) { + ERR("Failed to copy host name to rotate_get_info reply"); + info_return->status = LTTNG_ROTATION_STATUS_ERROR; + cmd_ret = LTTNG_ERR_SET_URL; + goto end; + } + + session_get_net_consumer_ports(session, &ctrl_port, &data_port); + info_return->location.relay.ports.control = ctrl_port; + info_return->location.relay.ports.data = data_port; + info_return->location_type = + (int8_t) LTTNG_TRACE_ARCHIVE_LOCATION_TYPE_RELAY; + chunk_path = strdup(session->last_chunk_path); + if (!chunk_path) { + ERR("Failed to allocate the path of the last archived trace chunk"); + info_return->status = LTTNG_ROTATION_STATUS_ERROR; + cmd_ret = LTTNG_ERR_UNK; + goto end; + } + break; + } + default: + abort(); + } + + fmt_ret = lttng_strncpy(current_tracing_path_reply, + chunk_path, current_tracing_path_reply_len); + free(chunk_path); + if (fmt_ret) { + ERR("Failed to copy path of the last archived trace chunk to rotate_get_info reply"); + info_return->status = LTTNG_ROTATION_STATUS_ERROR; + cmd_ret = LTTNG_ERR_UNK; + goto end; + } + + break; + } + case LTTNG_ROTATION_STATE_ERROR: + DBG("Reporting that an error occurred during rotation %" PRIu64 " of session \"%s\"", + rotation_id, session->name); + break; + default: + abort(); + } + + cmd_ret = LTTNG_OK; +end: + info_return->status = (int32_t) rotation_state; + return cmd_ret; +} + +/* + * Command LTTNG_ROTATION_SET_SCHEDULE from the lttng-ctl library. + * + * Configure the automatic rotation parameters. + * 'activate' to true means activate the rotation schedule type with 'new_value'. + * 'activate' to false means deactivate the rotation schedule and validate that + * 'new_value' has the same value as the currently active value. + * + * Return LTTNG_OK on success or else a positive LTTNG_ERR code. + */ +int cmd_rotation_set_schedule(struct ltt_session *session, + bool activate, enum lttng_rotation_schedule_type schedule_type, + uint64_t new_value, + struct notification_thread_handle *notification_thread_handle) +{ + int ret; + uint64_t *parameter_value; + + LTTNG_ASSERT(session); + + DBG("Cmd rotate set schedule session %s", session->name); + + if (session->live_timer || !session->output_traces) { + DBG("Failing ROTATION_SET_SCHEDULE command as the rotation feature is not available for this session"); + ret = LTTNG_ERR_ROTATION_NOT_AVAILABLE; + goto end; + } + + switch (schedule_type) { + case LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD: + parameter_value = &session->rotate_size; + break; + case LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC: + parameter_value = &session->rotate_timer_period; + if (new_value >= UINT_MAX) { + DBG("Failing ROTATION_SET_SCHEDULE command as the value requested for a periodic rotation schedule is invalid: %" PRIu64 " > %u (UINT_MAX)", + new_value, UINT_MAX); + ret = LTTNG_ERR_INVALID; + goto end; + } + break; + default: + WARN("Failing ROTATION_SET_SCHEDULE command on unknown schedule type"); + ret = LTTNG_ERR_INVALID; + goto end; + } + + /* Improper use of the API. */ + if (new_value == -1ULL) { + WARN("Failing ROTATION_SET_SCHEDULE command as the value requested is -1"); + ret = LTTNG_ERR_INVALID; + goto end; + } + + /* + * As indicated in struct ltt_session's comments, a value of == 0 means + * this schedule rotation type is not in use. + * + * Reject the command if we were asked to activate a schedule that was + * already active. + */ + if (activate && *parameter_value != 0) { + DBG("Failing ROTATION_SET_SCHEDULE (activate) command as the schedule is already active"); + ret = LTTNG_ERR_ROTATION_SCHEDULE_SET; + goto end; + } + + /* + * Reject the command if we were asked to deactivate a schedule that was + * not active. + */ + if (!activate && *parameter_value == 0) { + DBG("Failing ROTATION_SET_SCHEDULE (deactivate) command as the schedule is already inactive"); + ret = LTTNG_ERR_ROTATION_SCHEDULE_NOT_SET; + goto end; + } + + /* + * Reject the command if we were asked to deactivate a schedule that + * doesn't exist. + */ + if (!activate && *parameter_value != new_value) { + DBG("Failing ROTATION_SET_SCHEDULE (deactivate) command as an inexistant schedule was provided"); + ret = LTTNG_ERR_ROTATION_SCHEDULE_NOT_SET; + goto end; + } + + *parameter_value = activate ? new_value : 0; + + switch (schedule_type) { + case LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC: + if (activate && session->active) { + /* + * Only start the timer if the session is active, + * otherwise it will be started when the session starts. + */ + ret = timer_session_rotation_schedule_timer_start( + session, new_value); + if (ret) { + ERR("Failed to enable session rotation timer in ROTATION_SET_SCHEDULE command"); + ret = LTTNG_ERR_UNK; + goto end; + } + } else { + ret = timer_session_rotation_schedule_timer_stop( + session); + if (ret) { + ERR("Failed to disable session rotation timer in ROTATION_SET_SCHEDULE command"); + ret = LTTNG_ERR_UNK; + goto end; + } + } + break; + case LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD: + if (activate) { + ret = subscribe_session_consumed_size_rotation(session, + new_value, notification_thread_handle); + if (ret) { + ERR("Failed to enable consumed-size notification in ROTATION_SET_SCHEDULE command"); + ret = LTTNG_ERR_UNK; + goto end; + } + } else { + ret = unsubscribe_session_consumed_size_rotation(session, + notification_thread_handle); + if (ret) { + ERR("Failed to disable consumed-size notification in ROTATION_SET_SCHEDULE command"); + ret = LTTNG_ERR_UNK; + goto end; + } + + } + break; + default: + /* Would have been caught before. */ + abort(); + } + + ret = LTTNG_OK; + + goto end; + +end: + return ret; +} + +/* Wait for a given path to be removed before continuing. */ +static enum lttng_error_code wait_on_path(void *path_data) +{ + const char *shm_path = (const char *) path_data; + + DBG("Waiting for the shm path at %s to be removed before completing session destruction", + shm_path); + while (true) { + int ret; + struct stat st; + + ret = stat(shm_path, &st); + if (ret) { + if (errno != ENOENT) { + PERROR("stat() returned an error while checking for the existence of the shm path"); + } else { + DBG("shm path no longer exists, completing the destruction of session"); + } + break; + } else { + if (!S_ISDIR(st.st_mode)) { + ERR("The type of shm path %s returned by stat() is not a directory; aborting the wait for shm path removal", + shm_path); + break; + } + } + usleep(SESSION_DESTROY_SHM_PATH_CHECK_DELAY_US); + } + return LTTNG_OK; +} + +/* + * Returns a pointer to a handler to run on completion of a command. + * Returns NULL if no handler has to be run for the last command executed. + */ +const struct cmd_completion_handler *cmd_pop_completion_handler(void) +{ + struct cmd_completion_handler *handler = current_completion_handler; + + current_completion_handler = NULL; + return handler; +} + +/* + * Init command subsystem. + */ +void cmd_init(void) +{ + /* + * Set network sequence index to 1 for streams to match a relayd + * socket on the consumer side. + */ + pthread_mutex_lock(&relayd_net_seq_idx_lock); + relayd_net_seq_idx = 1; + pthread_mutex_unlock(&relayd_net_seq_idx_lock); + + DBG("Command subsystem initialized"); +} diff --git a/src/bin/lttng-sessiond/condition-internal.c b/src/bin/lttng-sessiond/condition-internal.c deleted file mode 100644 index dd9c244b3..000000000 --- a/src/bin/lttng-sessiond/condition-internal.c +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2020 Francis Deslauriers - * - * SPDX-License-Identifier: LGPL-2.1-only - * - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "condition-internal.h" - -static -unsigned long lttng_condition_buffer_usage_hash( - const struct lttng_condition *_condition) -{ - unsigned long hash; - unsigned long condition_type; - struct lttng_condition_buffer_usage *condition; - - condition = container_of(_condition, - struct lttng_condition_buffer_usage, parent); - - condition_type = (unsigned long) condition->parent.type; - hash = hash_key_ulong((void *) condition_type, lttng_ht_seed); - if (condition->session_name) { - hash ^= hash_key_str(condition->session_name, lttng_ht_seed); - } - if (condition->channel_name) { - hash ^= hash_key_str(condition->channel_name, lttng_ht_seed); - } - if (condition->domain.set) { - hash ^= hash_key_ulong( - (void *) condition->domain.type, - lttng_ht_seed); - } - if (condition->threshold_ratio.set) { - hash ^= hash_key_u64(&condition->threshold_ratio.value, lttng_ht_seed); - } else if (condition->threshold_bytes.set) { - uint64_t val; - - val = condition->threshold_bytes.value; - hash ^= hash_key_u64(&val, lttng_ht_seed); - } - return hash; -} - -static -unsigned long lttng_condition_session_consumed_size_hash( - const struct lttng_condition *_condition) -{ - unsigned long hash; - unsigned long condition_type = - (unsigned long) LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE; - struct lttng_condition_session_consumed_size *condition; - uint64_t val; - - condition = container_of(_condition, - struct lttng_condition_session_consumed_size, parent); - - hash = hash_key_ulong((void *) condition_type, lttng_ht_seed); - if (condition->session_name) { - hash ^= hash_key_str(condition->session_name, lttng_ht_seed); - } - val = condition->consumed_threshold_bytes.value; - hash ^= hash_key_u64(&val, lttng_ht_seed); - return hash; -} - -static -unsigned long lttng_condition_session_rotation_hash( - const struct lttng_condition *_condition) -{ - unsigned long hash, condition_type; - struct lttng_condition_session_rotation *condition; - - condition = container_of(_condition, - struct lttng_condition_session_rotation, parent); - condition_type = (unsigned long) condition->parent.type; - hash = hash_key_ulong((void *) condition_type, lttng_ht_seed); - LTTNG_ASSERT(condition->session_name); - hash ^= hash_key_str(condition->session_name, lttng_ht_seed); - return hash; -} - -static unsigned long lttng_condition_event_rule_matches_hash( - const struct lttng_condition *condition) -{ - unsigned long hash, condition_type; - enum lttng_condition_status condition_status; - const struct lttng_event_rule *event_rule; - - condition_type = (unsigned long) condition->type; - condition_status = lttng_condition_event_rule_matches_get_rule( - condition, &event_rule); - LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); - - hash = hash_key_ulong((void *) condition_type, lttng_ht_seed); - return hash ^ lttng_event_rule_hash(event_rule); -} - -/* - * 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 - * don't want to link in liblttng-ctl. - */ -unsigned long lttng_condition_hash(const struct lttng_condition *condition) -{ - switch (condition->type) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - return lttng_condition_buffer_usage_hash(condition); - case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: - return lttng_condition_session_consumed_size_hash(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_MATCHES: - return lttng_condition_event_rule_matches_hash(condition); - default: - abort(); - } -} - -struct lttng_condition *lttng_condition_copy(const struct lttng_condition *condition) -{ - int ret; - struct lttng_payload copy_buffer; - struct lttng_condition *copy = NULL; - - lttng_payload_init(©_buffer); - - ret = lttng_condition_serialize(condition, ©_buffer); - if (ret < 0) { - goto end; - } - - { - struct lttng_payload_view view = - lttng_payload_view_from_payload( - ©_buffer, 0, -1); - - ret = lttng_condition_create_from_payload( - &view, ©); - if (ret < 0) { - copy = NULL; - goto end; - } - } - -end: - lttng_payload_reset(©_buffer); - return copy; -} diff --git a/src/bin/lttng-sessiond/condition-internal.cpp b/src/bin/lttng-sessiond/condition-internal.cpp new file mode 100644 index 000000000..dd9c244b3 --- /dev/null +++ b/src/bin/lttng-sessiond/condition-internal.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 Francis Deslauriers + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "condition-internal.h" + +static +unsigned long lttng_condition_buffer_usage_hash( + const struct lttng_condition *_condition) +{ + unsigned long hash; + unsigned long condition_type; + struct lttng_condition_buffer_usage *condition; + + condition = container_of(_condition, + struct lttng_condition_buffer_usage, parent); + + condition_type = (unsigned long) condition->parent.type; + hash = hash_key_ulong((void *) condition_type, lttng_ht_seed); + if (condition->session_name) { + hash ^= hash_key_str(condition->session_name, lttng_ht_seed); + } + if (condition->channel_name) { + hash ^= hash_key_str(condition->channel_name, lttng_ht_seed); + } + if (condition->domain.set) { + hash ^= hash_key_ulong( + (void *) condition->domain.type, + lttng_ht_seed); + } + if (condition->threshold_ratio.set) { + hash ^= hash_key_u64(&condition->threshold_ratio.value, lttng_ht_seed); + } else if (condition->threshold_bytes.set) { + uint64_t val; + + val = condition->threshold_bytes.value; + hash ^= hash_key_u64(&val, lttng_ht_seed); + } + return hash; +} + +static +unsigned long lttng_condition_session_consumed_size_hash( + const struct lttng_condition *_condition) +{ + unsigned long hash; + unsigned long condition_type = + (unsigned long) LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE; + struct lttng_condition_session_consumed_size *condition; + uint64_t val; + + condition = container_of(_condition, + struct lttng_condition_session_consumed_size, parent); + + hash = hash_key_ulong((void *) condition_type, lttng_ht_seed); + if (condition->session_name) { + hash ^= hash_key_str(condition->session_name, lttng_ht_seed); + } + val = condition->consumed_threshold_bytes.value; + hash ^= hash_key_u64(&val, lttng_ht_seed); + return hash; +} + +static +unsigned long lttng_condition_session_rotation_hash( + const struct lttng_condition *_condition) +{ + unsigned long hash, condition_type; + struct lttng_condition_session_rotation *condition; + + condition = container_of(_condition, + struct lttng_condition_session_rotation, parent); + condition_type = (unsigned long) condition->parent.type; + hash = hash_key_ulong((void *) condition_type, lttng_ht_seed); + LTTNG_ASSERT(condition->session_name); + hash ^= hash_key_str(condition->session_name, lttng_ht_seed); + return hash; +} + +static unsigned long lttng_condition_event_rule_matches_hash( + const struct lttng_condition *condition) +{ + unsigned long hash, condition_type; + enum lttng_condition_status condition_status; + const struct lttng_event_rule *event_rule; + + condition_type = (unsigned long) condition->type; + condition_status = lttng_condition_event_rule_matches_get_rule( + condition, &event_rule); + LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); + + hash = hash_key_ulong((void *) condition_type, lttng_ht_seed); + return hash ^ lttng_event_rule_hash(event_rule); +} + +/* + * 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 + * don't want to link in liblttng-ctl. + */ +unsigned long lttng_condition_hash(const struct lttng_condition *condition) +{ + switch (condition->type) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + return lttng_condition_buffer_usage_hash(condition); + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + return lttng_condition_session_consumed_size_hash(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_MATCHES: + return lttng_condition_event_rule_matches_hash(condition); + default: + abort(); + } +} + +struct lttng_condition *lttng_condition_copy(const struct lttng_condition *condition) +{ + int ret; + struct lttng_payload copy_buffer; + struct lttng_condition *copy = NULL; + + lttng_payload_init(©_buffer); + + ret = lttng_condition_serialize(condition, ©_buffer); + if (ret < 0) { + goto end; + } + + { + struct lttng_payload_view view = + lttng_payload_view_from_payload( + ©_buffer, 0, -1); + + ret = lttng_condition_create_from_payload( + &view, ©); + if (ret < 0) { + copy = NULL; + goto end; + } + } + +end: + lttng_payload_reset(©_buffer); + return copy; +} diff --git a/src/bin/lttng-sessiond/condition-internal.h b/src/bin/lttng-sessiond/condition-internal.h index 270a8b1af..fa8f7be04 100644 --- a/src/bin/lttng-sessiond/condition-internal.h +++ b/src/bin/lttng-sessiond/condition-internal.h @@ -10,6 +10,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /* * 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 @@ -19,4 +23,9 @@ unsigned long lttng_condition_hash(const struct lttng_condition *condition); struct lttng_condition *lttng_condition_copy( const struct lttng_condition *condition); + +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_SESSIOND_CONDITION_INTERNAL_H */ diff --git a/src/bin/lttng-sessiond/consumer.c b/src/bin/lttng-sessiond/consumer.c deleted file mode 100644 index 7c36d61be..000000000 --- a/src/bin/lttng-sessiond/consumer.c +++ /dev/null @@ -1,2161 +0,0 @@ -/* - * Copyright (C) 2012 David Goulet - * Copyright (C) 2018 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "consumer.h" -#include "health-sessiond.h" -#include "ust-app.h" -#include "utils.h" -#include "lttng-sessiond.h" - -/* - * Return allocated full pathname of the session using the consumer trace path - * and subdir if available. - * - * The caller can safely free(3) the returned value. On error, NULL is - * returned. - */ -char *setup_channel_trace_path(struct consumer_output *consumer, - const char *session_path, size_t *consumer_path_offset) -{ - int ret; - char *pathname; - - LTTNG_ASSERT(consumer); - LTTNG_ASSERT(session_path); - - health_code_update(); - - /* - * Allocate the string ourself to make sure we never exceed - * LTTNG_PATH_MAX. - */ - pathname = zmalloc(LTTNG_PATH_MAX); - if (!pathname) { - goto error; - } - - /* Get correct path name destination */ - if (consumer->type == CONSUMER_DST_NET && - consumer->relay_major_version == 2 && - consumer->relay_minor_version < 11) { - ret = snprintf(pathname, LTTNG_PATH_MAX, "%s%s/%s/%s", - consumer->dst.net.base_dir, - consumer->chunk_path, consumer->domain_subdir, - session_path); - *consumer_path_offset = 0; - } else { - ret = snprintf(pathname, LTTNG_PATH_MAX, "%s/%s", - consumer->domain_subdir, session_path); - *consumer_path_offset = strlen(consumer->domain_subdir) + 1; - } - DBG3("Consumer trace path relative to current trace chunk: \"%s\"", - pathname); - if (ret < 0) { - PERROR("Failed to format channel path"); - goto error; - } else if (ret >= LTTNG_PATH_MAX) { - ERR("Truncation occurred while formatting channel path"); - goto error; - } - - return pathname; -error: - free(pathname); - return NULL; -} - -/* - * Send a data payload using a given consumer socket of size len. - * - * The consumer socket lock MUST be acquired before calling this since this - * function can change the fd value. - * - * Return 0 on success else a negative value on error. - */ -int consumer_socket_send( - struct consumer_socket *socket, const void *msg, size_t len) -{ - int fd; - ssize_t size; - - LTTNG_ASSERT(socket); - LTTNG_ASSERT(socket->fd_ptr); - LTTNG_ASSERT(msg); - - /* Consumer socket is invalid. Stopping. */ - fd = *socket->fd_ptr; - if (fd < 0) { - goto error; - } - - size = lttcomm_send_unix_sock(fd, msg, len); - if (size < 0) { - /* The above call will print a PERROR on error. */ - DBG("Error when sending data to consumer on sock %d", fd); - /* - * At this point, the socket is not usable anymore thus closing it and - * setting the file descriptor to -1 so it is not reused. - */ - - /* This call will PERROR on error. */ - (void) lttcomm_close_unix_sock(fd); - *socket->fd_ptr = -1; - goto error; - } - - return 0; - -error: - return -1; -} - -/* - * Receive a data payload using a given consumer socket of size len. - * - * The consumer socket lock MUST be acquired before calling this since this - * function can change the fd value. - * - * Return 0 on success else a negative value on error. - */ -int consumer_socket_recv(struct consumer_socket *socket, void *msg, size_t len) -{ - int fd; - ssize_t size; - - LTTNG_ASSERT(socket); - LTTNG_ASSERT(socket->fd_ptr); - LTTNG_ASSERT(msg); - - /* Consumer socket is invalid. Stopping. */ - fd = *socket->fd_ptr; - if (fd < 0) { - goto error; - } - - size = lttcomm_recv_unix_sock(fd, msg, len); - if (size <= 0) { - /* The above call will print a PERROR on error. */ - DBG("Error when receiving data from the consumer socket %d", fd); - /* - * At this point, the socket is not usable anymore thus closing it and - * setting the file descriptor to -1 so it is not reused. - */ - - /* This call will PERROR on error. */ - (void) lttcomm_close_unix_sock(fd); - *socket->fd_ptr = -1; - goto error; - } - - return 0; - -error: - return -1; -} - -/* - * Receive a reply command status message from the consumer. Consumer socket - * lock MUST be acquired before calling this function. - * - * Return 0 on success, -1 on recv error or a negative lttng error code which - * was possibly returned by the consumer. - */ -int consumer_recv_status_reply(struct consumer_socket *sock) -{ - int ret; - struct lttcomm_consumer_status_msg reply; - - LTTNG_ASSERT(sock); - - ret = consumer_socket_recv(sock, &reply, sizeof(reply)); - if (ret < 0) { - goto end; - } - - if (reply.ret_code == LTTCOMM_CONSUMERD_SUCCESS) { - /* All good. */ - ret = 0; - } else { - ret = -reply.ret_code; - DBG("Consumer ret code %d", ret); - } - -end: - return ret; -} - -/* - * Once the ASK_CHANNEL command is sent to the consumer, the channel - * information are sent back. This call receives that data and populates key - * and stream_count. - * - * On success return 0 and both key and stream_count are set. On error, a - * negative value is sent back and both parameters are untouched. - */ -int consumer_recv_status_channel(struct consumer_socket *sock, - uint64_t *key, unsigned int *stream_count) -{ - int ret; - struct lttcomm_consumer_status_channel reply; - - LTTNG_ASSERT(sock); - LTTNG_ASSERT(stream_count); - LTTNG_ASSERT(key); - - ret = consumer_socket_recv(sock, &reply, sizeof(reply)); - if (ret < 0) { - goto end; - } - - /* An error is possible so don't touch the key and stream_count. */ - if (reply.ret_code != LTTCOMM_CONSUMERD_SUCCESS) { - ret = -1; - goto end; - } - - *key = reply.key; - *stream_count = reply.stream_count; - ret = 0; - -end: - return ret; -} - -/* - * Send destroy relayd command to consumer. - * - * On success return positive value. On error, negative value. - */ -int consumer_send_destroy_relayd(struct consumer_socket *sock, - struct consumer_output *consumer) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(consumer); - LTTNG_ASSERT(sock); - - DBG2("Sending destroy relayd command to consumer sock %d", *sock->fd_ptr); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_DESTROY_RELAYD; - msg.u.destroy_relayd.net_seq_idx = consumer->net_seq_index; - - pthread_mutex_lock(sock->lock); - ret = consumer_socket_send(sock, &msg, sizeof(msg)); - if (ret < 0) { - goto error; - } - - /* Don't check the return value. The caller will do it. */ - ret = consumer_recv_status_reply(sock); - - DBG2("Consumer send destroy relayd command done"); - -error: - pthread_mutex_unlock(sock->lock); - return ret; -} - -/* - * For each consumer socket in the consumer output object, send a destroy - * relayd command. - */ -void consumer_output_send_destroy_relayd(struct consumer_output *consumer) -{ - struct lttng_ht_iter iter; - struct consumer_socket *socket; - - LTTNG_ASSERT(consumer); - - /* Destroy any relayd connection */ - if (consumer->type == CONSUMER_DST_NET) { - rcu_read_lock(); - cds_lfht_for_each_entry(consumer->socks->ht, &iter.iter, socket, - node.node) { - int ret; - - /* Send destroy relayd command */ - ret = consumer_send_destroy_relayd(socket, consumer); - if (ret < 0) { - DBG("Unable to send destroy relayd command to consumer"); - /* Continue since we MUST delete everything at this point. */ - } - } - rcu_read_unlock(); - } -} - -/* - * From a consumer_data structure, allocate and add a consumer socket to the - * consumer output. - * - * Return 0 on success, else negative value on error - */ -int consumer_create_socket(struct consumer_data *data, - struct consumer_output *output) -{ - int ret = 0; - struct consumer_socket *socket; - - LTTNG_ASSERT(data); - - if (output == NULL || data->cmd_sock < 0) { - /* - * Not an error. Possible there is simply not spawned consumer or it's - * disabled for the tracing session asking the socket. - */ - goto error; - } - - rcu_read_lock(); - socket = consumer_find_socket(data->cmd_sock, output); - rcu_read_unlock(); - if (socket == NULL) { - socket = consumer_allocate_socket(&data->cmd_sock); - if (socket == NULL) { - ret = -1; - goto error; - } - - socket->registered = 0; - socket->lock = &data->lock; - rcu_read_lock(); - consumer_add_socket(socket, output); - rcu_read_unlock(); - } - - socket->type = data->type; - - DBG3("Consumer socket created (fd: %d) and added to output", - data->cmd_sock); - -error: - return ret; -} - -/* - * Return the consumer socket from the given consumer output with the right - * bitness. On error, returns NULL. - * - * The caller MUST acquire a rcu read side lock and keep it until the socket - * object reference is not needed anymore. - */ -struct consumer_socket *consumer_find_socket_by_bitness(int bits, - const struct consumer_output *consumer) -{ - int consumer_fd; - struct consumer_socket *socket = NULL; - - switch (bits) { - case 64: - consumer_fd = uatomic_read(&the_ust_consumerd64_fd); - break; - case 32: - consumer_fd = uatomic_read(&the_ust_consumerd32_fd); - break; - default: - abort(); - goto end; - } - - socket = consumer_find_socket(consumer_fd, consumer); - if (!socket) { - ERR("Consumer socket fd %d not found in consumer obj %p", - consumer_fd, consumer); - } - -end: - return socket; -} - -/* - * Find a consumer_socket in a consumer_output hashtable. Read side lock must - * be acquired before calling this function and across use of the - * returned consumer_socket. - */ -struct consumer_socket *consumer_find_socket(int key, - const struct consumer_output *consumer) -{ - struct lttng_ht_iter iter; - struct lttng_ht_node_ulong *node; - struct consumer_socket *socket = NULL; - - /* Negative keys are lookup failures */ - if (key < 0 || consumer == NULL) { - return NULL; - } - - lttng_ht_lookup(consumer->socks, (void *)((unsigned long) key), - &iter); - node = lttng_ht_iter_get_node_ulong(&iter); - if (node != NULL) { - socket = caa_container_of(node, struct consumer_socket, node); - } - - return socket; -} - -/* - * Allocate a new consumer_socket and return the pointer. - */ -struct consumer_socket *consumer_allocate_socket(int *fd) -{ - struct consumer_socket *socket = NULL; - - LTTNG_ASSERT(fd); - - socket = zmalloc(sizeof(struct consumer_socket)); - if (socket == NULL) { - PERROR("zmalloc consumer socket"); - goto error; - } - - socket->fd_ptr = fd; - lttng_ht_node_init_ulong(&socket->node, *fd); - -error: - return socket; -} - -/* - * Add consumer socket to consumer output object. Read side lock must be - * acquired before calling this function. - */ -void consumer_add_socket(struct consumer_socket *sock, - struct consumer_output *consumer) -{ - LTTNG_ASSERT(sock); - LTTNG_ASSERT(consumer); - - lttng_ht_add_unique_ulong(consumer->socks, &sock->node); -} - -/* - * Delete consumer socket to consumer output object. Read side lock must be - * acquired before calling this function. - */ -void consumer_del_socket(struct consumer_socket *sock, - struct consumer_output *consumer) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(sock); - LTTNG_ASSERT(consumer); - - iter.iter.node = &sock->node.node; - ret = lttng_ht_del(consumer->socks, &iter); - LTTNG_ASSERT(!ret); -} - -/* - * RCU destroy call function. - */ -static void destroy_socket_rcu(struct rcu_head *head) -{ - struct lttng_ht_node_ulong *node = - caa_container_of(head, struct lttng_ht_node_ulong, head); - struct consumer_socket *socket = - caa_container_of(node, struct consumer_socket, node); - - free(socket); -} - -/* - * Destroy and free socket pointer in a call RCU. Read side lock must be - * acquired before calling this function. - */ -void consumer_destroy_socket(struct consumer_socket *sock) -{ - LTTNG_ASSERT(sock); - - /* - * We DO NOT close the file descriptor here since it is global to the - * session daemon and is closed only if the consumer dies or a custom - * consumer was registered, - */ - if (sock->registered) { - DBG3("Consumer socket was registered. Closing fd %d", *sock->fd_ptr); - lttcomm_close_unix_sock(*sock->fd_ptr); - } - - call_rcu(&sock->node.head, destroy_socket_rcu); -} - -/* - * Allocate and assign data to a consumer_output object. - * - * Return pointer to structure. - */ -struct consumer_output *consumer_create_output(enum consumer_dst_type type) -{ - struct consumer_output *output = NULL; - - output = zmalloc(sizeof(struct consumer_output)); - if (output == NULL) { - PERROR("zmalloc consumer_output"); - goto error; - } - - /* By default, consumer output is enabled */ - output->enabled = 1; - output->type = type; - output->net_seq_index = (uint64_t) -1ULL; - urcu_ref_init(&output->ref); - - output->socks = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); - -error: - return output; -} - -/* - * Iterate over the consumer output socket hash table and destroy them. The - * socket file descriptor are only closed if the consumer output was - * registered meaning it's an external consumer. - */ -void consumer_destroy_output_sockets(struct consumer_output *obj) -{ - struct lttng_ht_iter iter; - struct consumer_socket *socket; - - if (!obj->socks) { - return; - } - - rcu_read_lock(); - cds_lfht_for_each_entry(obj->socks->ht, &iter.iter, socket, node.node) { - consumer_del_socket(socket, obj); - consumer_destroy_socket(socket); - } - rcu_read_unlock(); -} - -/* - * Delete the consumer_output object from the list and free the ptr. - * - * Should *NOT* be called with RCU read-side lock held. - */ -static void consumer_release_output(struct urcu_ref *ref) -{ - struct consumer_output *obj = - caa_container_of(ref, struct consumer_output, ref); - - consumer_destroy_output_sockets(obj); - - if (obj->socks) { - /* Finally destroy HT */ - ht_cleanup_push(obj->socks); - } - - free(obj); -} - -/* - * Get the consumer_output object. - */ -void consumer_output_get(struct consumer_output *obj) -{ - urcu_ref_get(&obj->ref); -} - -/* - * Put the consumer_output object. - * - * Should *NOT* be called with RCU read-side lock held. - */ -void consumer_output_put(struct consumer_output *obj) -{ - if (!obj) { - return; - } - urcu_ref_put(&obj->ref, consumer_release_output); -} - -/* - * Copy consumer output and returned the newly allocated copy. - * - * Should *NOT* be called with RCU read-side lock held. - */ -struct consumer_output *consumer_copy_output(struct consumer_output *src) -{ - int ret; - struct consumer_output *output; - - LTTNG_ASSERT(src); - - output = consumer_create_output(src->type); - if (output == NULL) { - goto end; - } - output->enabled = src->enabled; - output->net_seq_index = src->net_seq_index; - memcpy(output->domain_subdir, src->domain_subdir, - sizeof(output->domain_subdir)); - output->snapshot = src->snapshot; - output->relay_major_version = src->relay_major_version; - output->relay_minor_version = src->relay_minor_version; - output->relay_allows_clear = src->relay_allows_clear; - memcpy(&output->dst, &src->dst, sizeof(output->dst)); - ret = consumer_copy_sockets(output, src); - if (ret < 0) { - goto error_put; - } -end: - return output; - -error_put: - consumer_output_put(output); - return NULL; -} - -/* - * Copy consumer sockets from src to dst. - * - * Return 0 on success or else a negative value. - */ -int consumer_copy_sockets(struct consumer_output *dst, - struct consumer_output *src) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct consumer_socket *socket, *copy_sock; - - LTTNG_ASSERT(dst); - LTTNG_ASSERT(src); - - rcu_read_lock(); - cds_lfht_for_each_entry(src->socks->ht, &iter.iter, socket, node.node) { - /* Ignore socket that are already there. */ - copy_sock = consumer_find_socket(*socket->fd_ptr, dst); - if (copy_sock) { - continue; - } - - /* Create new socket object. */ - copy_sock = consumer_allocate_socket(socket->fd_ptr); - if (copy_sock == NULL) { - rcu_read_unlock(); - ret = -ENOMEM; - goto error; - } - - copy_sock->registered = socket->registered; - /* - * This is valid because this lock is shared accross all consumer - * object being the global lock of the consumer data structure of the - * session daemon. - */ - copy_sock->lock = socket->lock; - consumer_add_socket(copy_sock, dst); - } - rcu_read_unlock(); - -error: - return ret; -} - -/* - * Set network URI to the consumer output. - * - * Return 0 on success. Return 1 if the URI were equal. Else, negative value on - * error. - */ -int consumer_set_network_uri(const struct ltt_session *session, - struct consumer_output *output, - struct lttng_uri *uri) -{ - int ret; - struct lttng_uri *dst_uri = NULL; - - /* Code flow error safety net. */ - LTTNG_ASSERT(output); - LTTNG_ASSERT(uri); - - switch (uri->stype) { - case LTTNG_STREAM_CONTROL: - dst_uri = &output->dst.net.control; - output->dst.net.control_isset = 1; - if (uri->port == 0) { - /* Assign default port. */ - uri->port = DEFAULT_NETWORK_CONTROL_PORT; - } else { - if (output->dst.net.data_isset && uri->port == - output->dst.net.data.port) { - ret = -LTTNG_ERR_INVALID; - goto error; - } - } - DBG3("Consumer control URI set with port %d", uri->port); - break; - case LTTNG_STREAM_DATA: - dst_uri = &output->dst.net.data; - output->dst.net.data_isset = 1; - if (uri->port == 0) { - /* Assign default port. */ - uri->port = DEFAULT_NETWORK_DATA_PORT; - } else { - if (output->dst.net.control_isset && uri->port == - output->dst.net.control.port) { - ret = -LTTNG_ERR_INVALID; - goto error; - } - } - DBG3("Consumer data URI set with port %d", uri->port); - break; - default: - ERR("Set network uri type unknown %d", uri->stype); - ret = -LTTNG_ERR_INVALID; - goto error; - } - - ret = uri_compare(dst_uri, uri); - if (!ret) { - /* Same URI, don't touch it and return success. */ - DBG3("URI network compare are the same"); - goto equal; - } - - /* URIs were not equal, replacing it. */ - memcpy(dst_uri, uri, sizeof(struct lttng_uri)); - output->type = CONSUMER_DST_NET; - if (dst_uri->stype != LTTNG_STREAM_CONTROL) { - /* Only the control uri needs to contain the path. */ - goto end; - } - - /* - * If the user has specified a subdir as part of the control - * URL, the session's base output directory is: - * /RELAYD_OUTPUT_PATH/HOSTNAME/USER_SPECIFIED_DIR - * - * Hence, the "base_dir" from which all stream files and - * session rotation chunks are created takes the form - * /HOSTNAME/USER_SPECIFIED_DIR - * - * If the user has not specified an output directory as part of - * the control URL, the base output directory has the form: - * /RELAYD_OUTPUT_PATH/HOSTNAME/SESSION_NAME-CREATION_TIME - * - * Hence, the "base_dir" from which all stream files and - * session rotation chunks are created takes the form - * /HOSTNAME/SESSION_NAME-CREATION_TIME - * - * Note that automatically generated session names already - * contain the session's creation time. In that case, the - * creation time is omitted to prevent it from being duplicated - * in the final directory hierarchy. - */ - if (*uri->subdir) { - if (strstr(uri->subdir, "../")) { - ERR("Network URI subdirs are not allowed to walk up the path hierarchy"); - ret = -LTTNG_ERR_INVALID; - goto error; - } - ret = snprintf(output->dst.net.base_dir, - sizeof(output->dst.net.base_dir), - "/%s/%s/", session->hostname, uri->subdir); - } else { - if (session->has_auto_generated_name) { - ret = snprintf(output->dst.net.base_dir, - sizeof(output->dst.net.base_dir), - "/%s/%s/", session->hostname, - session->name); - } else { - char session_creation_datetime[16]; - size_t strftime_ret; - struct tm *timeinfo; - - timeinfo = localtime(&session->creation_time); - if (!timeinfo) { - ret = -LTTNG_ERR_FATAL; - goto error; - } - strftime_ret = strftime(session_creation_datetime, - sizeof(session_creation_datetime), - "%Y%m%d-%H%M%S", timeinfo); - if (strftime_ret == 0) { - ERR("Failed to format session creation timestamp while setting network URI"); - ret = -LTTNG_ERR_FATAL; - goto error; - } - ret = snprintf(output->dst.net.base_dir, - sizeof(output->dst.net.base_dir), - "/%s/%s-%s/", session->hostname, - session->name, - session_creation_datetime); - } - } - if (ret >= sizeof(output->dst.net.base_dir)) { - ret = -LTTNG_ERR_INVALID; - ERR("Truncation occurred while setting network output base directory"); - goto error; - } else if (ret == -1) { - ret = -LTTNG_ERR_INVALID; - PERROR("Error occurred while setting network output base directory"); - goto error; - } - - DBG3("Consumer set network uri base_dir path %s", - output->dst.net.base_dir); - -end: - return 0; -equal: - return 1; -error: - return ret; -} - -/* - * Send file descriptor to consumer via sock. - * - * The consumer socket lock must be held by the caller. - */ -int consumer_send_fds(struct consumer_socket *sock, const int *fds, - size_t nb_fd) -{ - int ret; - - LTTNG_ASSERT(fds); - LTTNG_ASSERT(sock); - LTTNG_ASSERT(nb_fd > 0); - LTTNG_ASSERT(pthread_mutex_trylock(sock->lock) == EBUSY); - - ret = lttcomm_send_fds_unix_sock(*sock->fd_ptr, fds, nb_fd); - if (ret < 0) { - /* The above call will print a PERROR on error. */ - DBG("Error when sending consumer fds on sock %d", *sock->fd_ptr); - goto error; - } - - ret = consumer_recv_status_reply(sock); -error: - return ret; -} - -/* - * Consumer send communication message structure to consumer. - * - * The consumer socket lock must be held by the caller. - */ -int consumer_send_msg(struct consumer_socket *sock, - const struct lttcomm_consumer_msg *msg) -{ - int ret; - - LTTNG_ASSERT(msg); - LTTNG_ASSERT(sock); - LTTNG_ASSERT(pthread_mutex_trylock(sock->lock) == EBUSY); - - ret = consumer_socket_send(sock, msg, sizeof(struct lttcomm_consumer_msg)); - if (ret < 0) { - goto error; - } - - ret = consumer_recv_status_reply(sock); - -error: - return ret; -} - -/* - * Consumer send channel communication message structure to consumer. - * - * The consumer socket lock must be held by the caller. - */ -int consumer_send_channel(struct consumer_socket *sock, - struct lttcomm_consumer_msg *msg) -{ - int ret; - - LTTNG_ASSERT(msg); - LTTNG_ASSERT(sock); - - ret = consumer_send_msg(sock, msg); - if (ret < 0) { - goto error; - } - -error: - return ret; -} - -/* - * Populate the given consumer msg structure with the ask_channel command - * information. - */ -void consumer_init_ask_channel_comm_msg(struct lttcomm_consumer_msg *msg, - uint64_t subbuf_size, - uint64_t num_subbuf, - int overwrite, - unsigned int switch_timer_interval, - unsigned int read_timer_interval, - unsigned int live_timer_interval, - bool is_in_live_session, - unsigned int monitor_timer_interval, - int output, - int type, - uint64_t session_id, - const char *pathname, - const char *name, - uint64_t relayd_id, - uint64_t key, - unsigned char *uuid, - uint32_t chan_id, - uint64_t tracefile_size, - uint64_t tracefile_count, - uint64_t session_id_per_pid, - unsigned int monitor, - uint32_t ust_app_uid, - int64_t blocking_timeout, - const char *root_shm_path, - const char *shm_path, - struct lttng_trace_chunk *trace_chunk, - const struct lttng_credentials *buffer_credentials) -{ - LTTNG_ASSERT(msg); - - /* Zeroed structure */ - memset(msg, 0, sizeof(struct lttcomm_consumer_msg)); - msg->u.ask_channel.buffer_credentials.uid = UINT32_MAX; - msg->u.ask_channel.buffer_credentials.gid = UINT32_MAX; - - if (trace_chunk) { - uint64_t chunk_id; - enum lttng_trace_chunk_status chunk_status; - - chunk_status = lttng_trace_chunk_get_id(trace_chunk, &chunk_id); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - LTTNG_OPTIONAL_SET(&msg->u.ask_channel.chunk_id, chunk_id); - } - msg->u.ask_channel.buffer_credentials.uid = - lttng_credentials_get_uid(buffer_credentials); - msg->u.ask_channel.buffer_credentials.gid = - lttng_credentials_get_gid(buffer_credentials); - - msg->cmd_type = LTTNG_CONSUMER_ASK_CHANNEL_CREATION; - msg->u.ask_channel.subbuf_size = subbuf_size; - msg->u.ask_channel.num_subbuf = num_subbuf ; - msg->u.ask_channel.overwrite = overwrite; - msg->u.ask_channel.switch_timer_interval = switch_timer_interval; - msg->u.ask_channel.read_timer_interval = read_timer_interval; - msg->u.ask_channel.live_timer_interval = live_timer_interval; - msg->u.ask_channel.is_live = is_in_live_session; - msg->u.ask_channel.monitor_timer_interval = monitor_timer_interval; - msg->u.ask_channel.output = output; - msg->u.ask_channel.type = type; - msg->u.ask_channel.session_id = session_id; - msg->u.ask_channel.session_id_per_pid = session_id_per_pid; - msg->u.ask_channel.relayd_id = relayd_id; - msg->u.ask_channel.key = key; - msg->u.ask_channel.chan_id = chan_id; - msg->u.ask_channel.tracefile_size = tracefile_size; - msg->u.ask_channel.tracefile_count = tracefile_count; - msg->u.ask_channel.monitor = monitor; - msg->u.ask_channel.ust_app_uid = ust_app_uid; - msg->u.ask_channel.blocking_timeout = blocking_timeout; - - memcpy(msg->u.ask_channel.uuid, uuid, sizeof(msg->u.ask_channel.uuid)); - - if (pathname) { - strncpy(msg->u.ask_channel.pathname, pathname, - sizeof(msg->u.ask_channel.pathname)); - msg->u.ask_channel.pathname[sizeof(msg->u.ask_channel.pathname)-1] = '\0'; - } - - strncpy(msg->u.ask_channel.name, name, sizeof(msg->u.ask_channel.name)); - msg->u.ask_channel.name[sizeof(msg->u.ask_channel.name) - 1] = '\0'; - - if (root_shm_path) { - strncpy(msg->u.ask_channel.root_shm_path, root_shm_path, - sizeof(msg->u.ask_channel.root_shm_path)); - msg->u.ask_channel.root_shm_path[sizeof(msg->u.ask_channel.root_shm_path) - 1] = '\0'; - } - if (shm_path) { - strncpy(msg->u.ask_channel.shm_path, shm_path, - sizeof(msg->u.ask_channel.shm_path)); - msg->u.ask_channel.shm_path[sizeof(msg->u.ask_channel.shm_path) - 1] = '\0'; - } -} - -/* - * Init channel communication message structure. - */ -void consumer_init_add_channel_comm_msg(struct lttcomm_consumer_msg *msg, - uint64_t channel_key, - uint64_t session_id, - const char *pathname, - uid_t uid, - gid_t gid, - uint64_t relayd_id, - const char *name, - unsigned int nb_init_streams, - enum lttng_event_output output, - int type, - uint64_t tracefile_size, - uint64_t tracefile_count, - unsigned int monitor, - unsigned int live_timer_interval, - bool is_in_live_session, - unsigned int monitor_timer_interval, - struct lttng_trace_chunk *trace_chunk) -{ - LTTNG_ASSERT(msg); - - /* Zeroed structure */ - memset(msg, 0, sizeof(struct lttcomm_consumer_msg)); - - if (trace_chunk) { - uint64_t chunk_id; - enum lttng_trace_chunk_status chunk_status; - - chunk_status = lttng_trace_chunk_get_id(trace_chunk, &chunk_id); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - LTTNG_OPTIONAL_SET(&msg->u.channel.chunk_id, chunk_id); - } - - /* Send channel */ - msg->cmd_type = LTTNG_CONSUMER_ADD_CHANNEL; - msg->u.channel.channel_key = channel_key; - msg->u.channel.session_id = session_id; - msg->u.channel.relayd_id = relayd_id; - msg->u.channel.nb_init_streams = nb_init_streams; - msg->u.channel.output = output; - msg->u.channel.type = type; - msg->u.channel.tracefile_size = tracefile_size; - msg->u.channel.tracefile_count = tracefile_count; - msg->u.channel.monitor = monitor; - msg->u.channel.live_timer_interval = live_timer_interval; - msg->u.channel.is_live = is_in_live_session; - msg->u.channel.monitor_timer_interval = monitor_timer_interval; - - strncpy(msg->u.channel.pathname, pathname, - sizeof(msg->u.channel.pathname)); - msg->u.channel.pathname[sizeof(msg->u.channel.pathname) - 1] = '\0'; - - strncpy(msg->u.channel.name, name, sizeof(msg->u.channel.name)); - msg->u.channel.name[sizeof(msg->u.channel.name) - 1] = '\0'; -} - -/* - * Init stream communication message structure. - */ -void consumer_init_add_stream_comm_msg(struct lttcomm_consumer_msg *msg, - uint64_t channel_key, - uint64_t stream_key, - int32_t cpu) -{ - LTTNG_ASSERT(msg); - - memset(msg, 0, sizeof(struct lttcomm_consumer_msg)); - - msg->cmd_type = LTTNG_CONSUMER_ADD_STREAM; - msg->u.stream.channel_key = channel_key; - msg->u.stream.stream_key = stream_key; - msg->u.stream.cpu = cpu; -} - -void consumer_init_streams_sent_comm_msg(struct lttcomm_consumer_msg *msg, - enum lttng_consumer_command cmd, - uint64_t channel_key, uint64_t net_seq_idx) -{ - LTTNG_ASSERT(msg); - - memset(msg, 0, sizeof(struct lttcomm_consumer_msg)); - - msg->cmd_type = cmd; - msg->u.sent_streams.channel_key = channel_key; - msg->u.sent_streams.net_seq_idx = net_seq_idx; -} - -/* - * Send stream communication structure to the consumer. - */ -int consumer_send_stream(struct consumer_socket *sock, - struct consumer_output *dst, struct lttcomm_consumer_msg *msg, - const int *fds, size_t nb_fd) -{ - int ret; - - LTTNG_ASSERT(msg); - LTTNG_ASSERT(dst); - LTTNG_ASSERT(sock); - LTTNG_ASSERT(fds); - - ret = consumer_send_msg(sock, msg); - if (ret < 0) { - goto error; - } - - ret = consumer_send_fds(sock, fds, nb_fd); - if (ret < 0) { - goto error; - } - -error: - return ret; -} - -/* - * Send relayd socket to consumer associated with a session name. - * - * The consumer socket lock must be held by the caller. - * - * On success return positive value. On error, negative value. - */ -int consumer_send_relayd_socket(struct consumer_socket *consumer_sock, - struct lttcomm_relayd_sock *rsock, struct consumer_output *consumer, - enum lttng_stream_type type, uint64_t session_id, - const char *session_name, const char *hostname, - const char *base_path, int session_live_timer, - const uint64_t *current_chunk_id, time_t session_creation_time, - bool session_name_contains_creation_time) -{ - int ret; - struct lttcomm_consumer_msg msg; - - /* Code flow error. Safety net. */ - LTTNG_ASSERT(rsock); - LTTNG_ASSERT(consumer); - LTTNG_ASSERT(consumer_sock); - - memset(&msg, 0, sizeof(msg)); - /* Bail out if consumer is disabled */ - if (!consumer->enabled) { - ret = LTTNG_OK; - goto error; - } - - if (type == LTTNG_STREAM_CONTROL) { - char output_path[LTTNG_PATH_MAX] = {}; - uint64_t relayd_session_id; - - ret = relayd_create_session(rsock, &relayd_session_id, - session_name, hostname, base_path, - session_live_timer, consumer->snapshot, - session_id, the_sessiond_uuid, current_chunk_id, - session_creation_time, - session_name_contains_creation_time, - output_path); - if (ret < 0) { - /* Close the control socket. */ - (void) relayd_close(rsock); - goto error; - } - msg.u.relayd_sock.relayd_session_id = relayd_session_id; - DBG("Created session on relay, output path reply: %s", - output_path); - } - - msg.cmd_type = LTTNG_CONSUMER_ADD_RELAYD_SOCKET; - /* - * Assign network consumer output index using the temporary consumer since - * this call should only be made from within a set_consumer_uri() function - * call in the session daemon. - */ - msg.u.relayd_sock.net_index = consumer->net_seq_index; - msg.u.relayd_sock.type = type; - msg.u.relayd_sock.session_id = session_id; - memcpy(&msg.u.relayd_sock.sock, rsock, sizeof(msg.u.relayd_sock.sock)); - - DBG3("Sending relayd sock info to consumer on %d", *consumer_sock->fd_ptr); - ret = consumer_send_msg(consumer_sock, &msg); - if (ret < 0) { - goto error; - } - - DBG3("Sending relayd socket file descriptor to consumer"); - ret = consumer_send_fds(consumer_sock, ALIGNED_CONST_PTR(rsock->sock.fd), 1); - if (ret < 0) { - goto error; - } - - DBG2("Consumer relayd socket sent"); - -error: - return ret; -} - -static -int consumer_send_pipe(struct consumer_socket *consumer_sock, - enum lttng_consumer_command cmd, int pipe) -{ - int ret; - struct lttcomm_consumer_msg msg; - const char *pipe_name; - const char *command_name; - - switch (cmd) { - case LTTNG_CONSUMER_SET_CHANNEL_MONITOR_PIPE: - pipe_name = "channel monitor"; - command_name = "SET_CHANNEL_MONITOR_PIPE"; - break; - default: - ERR("Unexpected command received in %s (cmd = %d)", __func__, - (int) cmd); - abort(); - } - - /* Code flow error. Safety net. */ - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = cmd; - - pthread_mutex_lock(consumer_sock->lock); - DBG3("Sending %s command to consumer", command_name); - ret = consumer_send_msg(consumer_sock, &msg); - if (ret < 0) { - goto error; - } - - DBG3("Sending %s pipe %d to consumer on socket %d", - pipe_name, - pipe, *consumer_sock->fd_ptr); - ret = consumer_send_fds(consumer_sock, &pipe, 1); - if (ret < 0) { - goto error; - } - - DBG2("%s pipe successfully sent", pipe_name); -error: - pthread_mutex_unlock(consumer_sock->lock); - return ret; -} - -int consumer_send_channel_monitor_pipe(struct consumer_socket *consumer_sock, - int pipe) -{ - return consumer_send_pipe(consumer_sock, - LTTNG_CONSUMER_SET_CHANNEL_MONITOR_PIPE, pipe); -} - -/* - * Ask the consumer if the data is pending for the specific session id. - * Returns 1 if data is pending, 0 otherwise, or < 0 on error. - */ -int consumer_is_data_pending(uint64_t session_id, - struct consumer_output *consumer) -{ - int ret; - int32_t ret_code = 0; /* Default is that the data is NOT pending */ - struct consumer_socket *socket; - struct lttng_ht_iter iter; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(consumer); - - DBG3("Consumer data pending for id %" PRIu64, session_id); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_DATA_PENDING; - msg.u.data_pending.session_id = session_id; - - /* Send command for each consumer */ - rcu_read_lock(); - cds_lfht_for_each_entry(consumer->socks->ht, &iter.iter, socket, - node.node) { - pthread_mutex_lock(socket->lock); - ret = consumer_socket_send(socket, &msg, sizeof(msg)); - if (ret < 0) { - pthread_mutex_unlock(socket->lock); - goto error_unlock; - } - - /* - * No need for a recv reply status because the answer to the command is - * the reply status message. - */ - - ret = consumer_socket_recv(socket, &ret_code, sizeof(ret_code)); - if (ret < 0) { - pthread_mutex_unlock(socket->lock); - goto error_unlock; - } - pthread_mutex_unlock(socket->lock); - - if (ret_code == 1) { - break; - } - } - rcu_read_unlock(); - - DBG("Consumer data is %s pending for session id %" PRIu64, - ret_code == 1 ? "" : "NOT", session_id); - return ret_code; - -error_unlock: - rcu_read_unlock(); - return -1; -} - -/* - * Send a flush command to consumer using the given channel key. - * - * Return 0 on success else a negative value. - */ -int consumer_flush_channel(struct consumer_socket *socket, uint64_t key) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(socket); - - DBG2("Consumer flush channel key %" PRIu64, key); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_FLUSH_CHANNEL; - msg.u.flush_channel.key = key; - - pthread_mutex_lock(socket->lock); - health_code_update(); - - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto end; - } - -end: - health_code_update(); - pthread_mutex_unlock(socket->lock); - return ret; -} - -/* - * Send a clear quiescent command to consumer using the given channel key. - * - * Return 0 on success else a negative value. - */ -int consumer_clear_quiescent_channel(struct consumer_socket *socket, uint64_t key) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(socket); - - DBG2("Consumer clear quiescent channel key %" PRIu64, key); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_CLEAR_QUIESCENT_CHANNEL; - msg.u.clear_quiescent_channel.key = key; - - pthread_mutex_lock(socket->lock); - health_code_update(); - - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto end; - } - -end: - health_code_update(); - pthread_mutex_unlock(socket->lock); - return ret; -} - -/* - * Send a close metadata command to consumer using the given channel key. - * Called with registry lock held. - * - * Return 0 on success else a negative value. - */ -int consumer_close_metadata(struct consumer_socket *socket, - uint64_t metadata_key) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(socket); - - DBG2("Consumer close metadata channel key %" PRIu64, metadata_key); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_CLOSE_METADATA; - msg.u.close_metadata.key = metadata_key; - - pthread_mutex_lock(socket->lock); - health_code_update(); - - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto end; - } - -end: - health_code_update(); - pthread_mutex_unlock(socket->lock); - return ret; -} - -/* - * Send a setup metdata command to consumer using the given channel key. - * - * Return 0 on success else a negative value. - */ -int consumer_setup_metadata(struct consumer_socket *socket, - uint64_t metadata_key) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(socket); - - DBG2("Consumer setup metadata channel key %" PRIu64, metadata_key); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_SETUP_METADATA; - msg.u.setup_metadata.key = metadata_key; - - pthread_mutex_lock(socket->lock); - health_code_update(); - - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto end; - } - -end: - health_code_update(); - pthread_mutex_unlock(socket->lock); - return ret; -} - -/* - * Send metadata string to consumer. - * RCU read-side lock must be held to guarantee existence of socket. - * - * Return 0 on success else a negative value. - */ -int consumer_push_metadata(struct consumer_socket *socket, - uint64_t metadata_key, char *metadata_str, size_t len, - size_t target_offset, uint64_t version) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(socket); - - DBG2("Consumer push metadata to consumer socket %d", *socket->fd_ptr); - - pthread_mutex_lock(socket->lock); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_PUSH_METADATA; - msg.u.push_metadata.key = metadata_key; - msg.u.push_metadata.target_offset = target_offset; - msg.u.push_metadata.len = len; - msg.u.push_metadata.version = version; - - health_code_update(); - ret = consumer_send_msg(socket, &msg); - if (ret < 0 || len == 0) { - goto end; - } - - DBG3("Consumer pushing metadata on sock %d of len %zu", *socket->fd_ptr, - len); - - ret = consumer_socket_send(socket, metadata_str, len); - if (ret < 0) { - goto end; - } - - health_code_update(); - ret = consumer_recv_status_reply(socket); - if (ret < 0) { - goto end; - } - -end: - pthread_mutex_unlock(socket->lock); - health_code_update(); - return ret; -} - -/* - * Ask the consumer to snapshot a specific channel using the key. - * - * Returns LTTNG_OK on success or else an LTTng error code. - */ -enum lttng_error_code consumer_snapshot_channel(struct consumer_socket *socket, - uint64_t key, const struct consumer_output *output, int metadata, - uid_t uid, gid_t gid, const char *channel_path, int wait, - uint64_t nb_packets_per_stream) -{ - int ret; - enum lttng_error_code status = LTTNG_OK; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(socket); - LTTNG_ASSERT(output); - - DBG("Consumer snapshot channel key %" PRIu64, key); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_SNAPSHOT_CHANNEL; - msg.u.snapshot_channel.key = key; - msg.u.snapshot_channel.nb_packets_per_stream = nb_packets_per_stream; - msg.u.snapshot_channel.metadata = metadata; - - if (output->type == CONSUMER_DST_NET) { - msg.u.snapshot_channel.relayd_id = - output->net_seq_index; - msg.u.snapshot_channel.use_relayd = 1; - } else { - msg.u.snapshot_channel.relayd_id = (uint64_t) -1ULL; - } - ret = lttng_strncpy(msg.u.snapshot_channel.pathname, - channel_path, - sizeof(msg.u.snapshot_channel.pathname)); - if (ret < 0) { - ERR("Snapshot path exceeds the maximal allowed length of %zu bytes (%zu bytes required) with path \"%s\"", - sizeof(msg.u.snapshot_channel.pathname), - strlen(channel_path), - channel_path); - status = LTTNG_ERR_SNAPSHOT_FAIL; - goto error; - } - - health_code_update(); - pthread_mutex_lock(socket->lock); - ret = consumer_send_msg(socket, &msg); - pthread_mutex_unlock(socket->lock); - if (ret < 0) { - switch (-ret) { - case LTTCOMM_CONSUMERD_CHAN_NOT_FOUND: - status = LTTNG_ERR_CHAN_NOT_FOUND; - break; - default: - status = LTTNG_ERR_SNAPSHOT_FAIL; - break; - } - goto error; - } - -error: - health_code_update(); - return status; -} - -/* - * Ask the consumer the number of discarded events for a channel. - */ -int consumer_get_discarded_events(uint64_t session_id, uint64_t channel_key, - struct consumer_output *consumer, uint64_t *discarded) -{ - int ret; - struct consumer_socket *socket; - struct lttng_ht_iter iter; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(consumer); - - DBG3("Consumer discarded events id %" PRIu64, session_id); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_DISCARDED_EVENTS; - msg.u.discarded_events.session_id = session_id; - msg.u.discarded_events.channel_key = channel_key; - - *discarded = 0; - - /* Send command for each consumer */ - rcu_read_lock(); - cds_lfht_for_each_entry(consumer->socks->ht, &iter.iter, socket, - node.node) { - uint64_t consumer_discarded = 0; - pthread_mutex_lock(socket->lock); - ret = consumer_socket_send(socket, &msg, sizeof(msg)); - if (ret < 0) { - pthread_mutex_unlock(socket->lock); - goto end; - } - - /* - * No need for a recv reply status because the answer to the - * command is the reply status message. - */ - ret = consumer_socket_recv(socket, &consumer_discarded, - sizeof(consumer_discarded)); - if (ret < 0) { - ERR("get discarded events"); - pthread_mutex_unlock(socket->lock); - goto end; - } - pthread_mutex_unlock(socket->lock); - *discarded += consumer_discarded; - } - ret = 0; - DBG("Consumer discarded %" PRIu64 " events in session id %" PRIu64, - *discarded, session_id); - -end: - rcu_read_unlock(); - return ret; -} - -/* - * Ask the consumer the number of lost packets for a channel. - */ -int consumer_get_lost_packets(uint64_t session_id, uint64_t channel_key, - struct consumer_output *consumer, uint64_t *lost) -{ - int ret; - struct consumer_socket *socket; - struct lttng_ht_iter iter; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(consumer); - - DBG3("Consumer lost packets id %" PRIu64, session_id); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_LOST_PACKETS; - msg.u.lost_packets.session_id = session_id; - msg.u.lost_packets.channel_key = channel_key; - - *lost = 0; - - /* Send command for each consumer */ - rcu_read_lock(); - cds_lfht_for_each_entry(consumer->socks->ht, &iter.iter, socket, - node.node) { - uint64_t consumer_lost = 0; - pthread_mutex_lock(socket->lock); - ret = consumer_socket_send(socket, &msg, sizeof(msg)); - if (ret < 0) { - pthread_mutex_unlock(socket->lock); - goto end; - } - - /* - * No need for a recv reply status because the answer to the - * command is the reply status message. - */ - ret = consumer_socket_recv(socket, &consumer_lost, - sizeof(consumer_lost)); - if (ret < 0) { - ERR("get lost packets"); - pthread_mutex_unlock(socket->lock); - goto end; - } - pthread_mutex_unlock(socket->lock); - *lost += consumer_lost; - } - ret = 0; - DBG("Consumer lost %" PRIu64 " packets in session id %" PRIu64, - *lost, session_id); - -end: - rcu_read_unlock(); - return ret; -} - -/* - * Ask the consumer to rotate a channel. - * - * The new_chunk_id is the session->rotate_count that has been incremented - * when the rotation started. On the relay, this allows to keep track in which - * chunk each stream is currently writing to (for the rotate_pending operation). - */ -int consumer_rotate_channel(struct consumer_socket *socket, uint64_t key, - uid_t uid, gid_t gid, struct consumer_output *output, - bool is_metadata_channel) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(socket); - - DBG("Consumer rotate channel key %" PRIu64, key); - - pthread_mutex_lock(socket->lock); - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_ROTATE_CHANNEL; - msg.u.rotate_channel.key = key; - msg.u.rotate_channel.metadata = !!is_metadata_channel; - - if (output->type == CONSUMER_DST_NET) { - msg.u.rotate_channel.relayd_id = output->net_seq_index; - } else { - msg.u.rotate_channel.relayd_id = (uint64_t) -1ULL; - } - - health_code_update(); - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - switch (-ret) { - case LTTCOMM_CONSUMERD_CHAN_NOT_FOUND: - ret = -LTTNG_ERR_CHAN_NOT_FOUND; - break; - default: - ret = -LTTNG_ERR_ROTATION_FAIL_CONSUMER; - break; - } - goto error; - } -error: - pthread_mutex_unlock(socket->lock); - health_code_update(); - return ret; -} - -int consumer_open_channel_packets(struct consumer_socket *socket, uint64_t key) -{ - int ret; - const struct lttcomm_consumer_msg msg = { - .cmd_type = LTTNG_CONSUMER_OPEN_CHANNEL_PACKETS, - .u.open_channel_packets.key = key, - }; - - LTTNG_ASSERT(socket); - - DBG("Consumer open channel packets: channel key = %" PRIu64, key); - - health_code_update(); - - pthread_mutex_lock(socket->lock); - ret = consumer_send_msg(socket, &msg); - pthread_mutex_unlock(socket->lock); - if (ret < 0) { - goto error_socket; - } - -error_socket: - health_code_update(); - return ret; -} - -int consumer_clear_channel(struct consumer_socket *socket, uint64_t key) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(socket); - - DBG("Consumer clear channel %" PRIu64, key); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_CLEAR_CHANNEL; - msg.u.clear_channel.key = key; - - health_code_update(); - - pthread_mutex_lock(socket->lock); - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto error_socket; - } - -error_socket: - pthread_mutex_unlock(socket->lock); - - health_code_update(); - return ret; -} - -int consumer_init(struct consumer_socket *socket, - const lttng_uuid sessiond_uuid) -{ - int ret; - struct lttcomm_consumer_msg msg = { - .cmd_type = LTTNG_CONSUMER_INIT, - }; - - LTTNG_ASSERT(socket); - - DBG("Sending consumer initialization command"); - lttng_uuid_copy(msg.u.init.sessiond_uuid, sessiond_uuid); - - health_code_update(); - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto error; - } - -error: - health_code_update(); - return ret; -} - -/* - * Ask the consumer to create a new chunk for a given session. - * - * Called with the consumer socket lock held. - */ -int consumer_create_trace_chunk(struct consumer_socket *socket, - uint64_t relayd_id, uint64_t session_id, - struct lttng_trace_chunk *chunk, - const char *domain_subdir) -{ - int ret; - enum lttng_trace_chunk_status chunk_status; - struct lttng_credentials chunk_credentials; - const struct lttng_directory_handle *chunk_directory_handle = NULL; - struct lttng_directory_handle *domain_handle = NULL; - int domain_dirfd; - const char *chunk_name; - bool chunk_name_overridden; - uint64_t chunk_id; - time_t creation_timestamp; - char creation_timestamp_buffer[ISO8601_STR_LEN]; - const char *creation_timestamp_str = "(none)"; - const bool chunk_has_local_output = relayd_id == -1ULL; - enum lttng_trace_chunk_status tc_status; - struct lttcomm_consumer_msg msg = { - .cmd_type = LTTNG_CONSUMER_CREATE_TRACE_CHUNK, - .u.create_trace_chunk.session_id = session_id, - }; - - LTTNG_ASSERT(socket); - LTTNG_ASSERT(chunk); - - if (relayd_id != -1ULL) { - LTTNG_OPTIONAL_SET(&msg.u.create_trace_chunk.relayd_id, - relayd_id); - } - - chunk_status = lttng_trace_chunk_get_name(chunk, &chunk_name, - &chunk_name_overridden); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK && - chunk_status != LTTNG_TRACE_CHUNK_STATUS_NONE) { - ERR("Failed to get name of trace chunk"); - ret = -LTTNG_ERR_FATAL; - goto error; - } - if (chunk_name_overridden) { - ret = lttng_strncpy(msg.u.create_trace_chunk.override_name, - chunk_name, - sizeof(msg.u.create_trace_chunk.override_name)); - if (ret) { - ERR("Trace chunk name \"%s\" exceeds the maximal length allowed by the consumer protocol", - chunk_name); - ret = -LTTNG_ERR_FATAL; - goto error; - } - } - - chunk_status = lttng_trace_chunk_get_creation_timestamp(chunk, - &creation_timestamp); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = -LTTNG_ERR_FATAL; - goto error; - } - msg.u.create_trace_chunk.creation_timestamp = - (uint64_t) creation_timestamp; - /* Only used for logging purposes. */ - ret = time_to_iso8601_str(creation_timestamp, - creation_timestamp_buffer, - sizeof(creation_timestamp_buffer)); - creation_timestamp_str = !ret ? creation_timestamp_buffer : - "(formatting error)"; - - chunk_status = lttng_trace_chunk_get_id(chunk, &chunk_id); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - /* - * Anonymous trace chunks should never be transmitted - * to remote peers (consumerd and relayd). They are used - * internally for backward-compatibility purposes. - */ - ret = -LTTNG_ERR_FATAL; - goto error; - } - msg.u.create_trace_chunk.chunk_id = chunk_id; - - if (chunk_has_local_output) { - chunk_status = lttng_trace_chunk_borrow_chunk_directory_handle( - chunk, &chunk_directory_handle); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = -LTTNG_ERR_FATAL; - goto error; - } - chunk_status = lttng_trace_chunk_get_credentials( - chunk, &chunk_credentials); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - /* - * Not associating credentials to a sessiond chunk is a - * fatal internal error. - */ - ret = -LTTNG_ERR_FATAL; - goto error; - } - tc_status = lttng_trace_chunk_create_subdirectory( - chunk, domain_subdir); - if (tc_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - PERROR("Failed to create chunk domain output directory \"%s\"", - domain_subdir); - ret = -LTTNG_ERR_FATAL; - goto error; - } - domain_handle = lttng_directory_handle_create_from_handle( - domain_subdir, - chunk_directory_handle); - if (!domain_handle) { - ret = -LTTNG_ERR_FATAL; - goto error; - } - - /* - * This will only compile on platforms that support - * dirfd (POSIX.2008). This is fine as the session daemon - * is only built for such platforms. - * - * The ownership of the chunk directory handle's is maintained - * by the trace chunk. - */ - domain_dirfd = lttng_directory_handle_get_dirfd( - domain_handle); - LTTNG_ASSERT(domain_dirfd >= 0); - - msg.u.create_trace_chunk.credentials.value.uid = - lttng_credentials_get_uid(&chunk_credentials); - msg.u.create_trace_chunk.credentials.value.gid = - lttng_credentials_get_gid(&chunk_credentials); - msg.u.create_trace_chunk.credentials.is_set = 1; - } - - DBG("Sending consumer create trace chunk command: relayd_id = %" PRId64 - ", session_id = %" PRIu64 ", chunk_id = %" PRIu64 - ", creation_timestamp = %s", - relayd_id, session_id, chunk_id, - creation_timestamp_str); - health_code_update(); - ret = consumer_send_msg(socket, &msg); - health_code_update(); - if (ret < 0) { - ERR("Trace chunk creation error on consumer"); - ret = -LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; - goto error; - } - - if (chunk_has_local_output) { - DBG("Sending trace chunk domain directory fd to consumer"); - health_code_update(); - ret = consumer_send_fds(socket, &domain_dirfd, 1); - health_code_update(); - if (ret < 0) { - ERR("Trace chunk creation error on consumer"); - ret = -LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; - goto error; - } - } -error: - lttng_directory_handle_put(domain_handle); - return ret; -} - -/* - * Ask the consumer to close a trace chunk for a given session. - * - * Called with the consumer socket lock held. - */ -int consumer_close_trace_chunk(struct consumer_socket *socket, - uint64_t relayd_id, uint64_t session_id, - struct lttng_trace_chunk *chunk, - char *closed_trace_chunk_path) -{ - int ret; - enum lttng_trace_chunk_status chunk_status; - struct lttcomm_consumer_msg msg = { - .cmd_type = LTTNG_CONSUMER_CLOSE_TRACE_CHUNK, - .u.close_trace_chunk.session_id = session_id, - }; - struct lttcomm_consumer_close_trace_chunk_reply reply; - uint64_t chunk_id; - time_t close_timestamp; - enum lttng_trace_chunk_command_type close_command; - const char *close_command_name = "none"; - struct lttng_dynamic_buffer path_reception_buffer; - - LTTNG_ASSERT(socket); - lttng_dynamic_buffer_init(&path_reception_buffer); - - if (relayd_id != -1ULL) { - LTTNG_OPTIONAL_SET( - &msg.u.close_trace_chunk.relayd_id, relayd_id); - } - - chunk_status = lttng_trace_chunk_get_close_command( - chunk, &close_command); - switch (chunk_status) { - case LTTNG_TRACE_CHUNK_STATUS_OK: - LTTNG_OPTIONAL_SET(&msg.u.close_trace_chunk.close_command, - (uint32_t) close_command); - break; - case LTTNG_TRACE_CHUNK_STATUS_NONE: - break; - default: - ERR("Failed to get trace chunk close command"); - ret = -1; - goto error; - } - - chunk_status = lttng_trace_chunk_get_id(chunk, &chunk_id); - /* - * Anonymous trace chunks should never be transmitted to remote peers - * (consumerd and relayd). They are used internally for - * backward-compatibility purposes. - */ - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - msg.u.close_trace_chunk.chunk_id = chunk_id; - - chunk_status = lttng_trace_chunk_get_close_timestamp(chunk, - &close_timestamp); - /* - * A trace chunk should be closed locally before being closed remotely. - * Otherwise, the close timestamp would never be transmitted to the - * peers. - */ - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - msg.u.close_trace_chunk.close_timestamp = (uint64_t) close_timestamp; - - if (msg.u.close_trace_chunk.close_command.is_set) { - close_command_name = lttng_trace_chunk_command_type_get_name( - close_command); - } - DBG("Sending consumer close trace chunk command: relayd_id = %" PRId64 - ", session_id = %" PRIu64 ", chunk_id = %" PRIu64 - ", close command = \"%s\"", - relayd_id, session_id, chunk_id, close_command_name); - - health_code_update(); - ret = consumer_socket_send(socket, &msg, sizeof(struct lttcomm_consumer_msg)); - if (ret < 0) { - ret = -LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; - goto error; - } - ret = consumer_socket_recv(socket, &reply, sizeof(reply)); - if (ret < 0) { - ret = -LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; - goto error; - } - if (reply.path_length >= LTTNG_PATH_MAX) { - ERR("Invalid path returned by relay daemon: %" PRIu32 "bytes exceeds maximal allowed length of %d bytes", - reply.path_length, LTTNG_PATH_MAX); - ret = -LTTNG_ERR_INVALID_PROTOCOL; - goto error; - } - ret = lttng_dynamic_buffer_set_size(&path_reception_buffer, - reply.path_length); - if (ret) { - ERR("Failed to allocate reception buffer of path returned by the \"close trace chunk\" command"); - ret = -LTTNG_ERR_NOMEM; - goto error; - } - ret = consumer_socket_recv(socket, path_reception_buffer.data, - path_reception_buffer.size); - if (ret < 0) { - ERR("Communication error while receiving path of closed trace chunk"); - ret = -LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; - goto error; - } - if (path_reception_buffer.data[path_reception_buffer.size - 1] != '\0') { - ERR("Invalid path returned by relay daemon: not null-terminated"); - ret = -LTTNG_ERR_INVALID_PROTOCOL; - goto error; - } - if (closed_trace_chunk_path) { - /* - * closed_trace_chunk_path is assumed to have a length >= - * LTTNG_PATH_MAX - */ - memcpy(closed_trace_chunk_path, path_reception_buffer.data, - path_reception_buffer.size); - } -error: - lttng_dynamic_buffer_reset(&path_reception_buffer); - health_code_update(); - return ret; -} - -/* - * Ask the consumer if a trace chunk exists. - * - * Called with the consumer socket lock held. - * Returns 0 on success, or a negative value on error. - */ -int consumer_trace_chunk_exists(struct consumer_socket *socket, - uint64_t relayd_id, uint64_t session_id, - struct lttng_trace_chunk *chunk, - enum consumer_trace_chunk_exists_status *result) -{ - int ret; - enum lttng_trace_chunk_status chunk_status; - struct lttcomm_consumer_msg msg = { - .cmd_type = LTTNG_CONSUMER_TRACE_CHUNK_EXISTS, - .u.trace_chunk_exists.session_id = session_id, - }; - uint64_t chunk_id; - const char *consumer_reply_str; - - LTTNG_ASSERT(socket); - - if (relayd_id != -1ULL) { - LTTNG_OPTIONAL_SET(&msg.u.trace_chunk_exists.relayd_id, - relayd_id); - } - - chunk_status = lttng_trace_chunk_get_id(chunk, &chunk_id); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - /* - * Anonymous trace chunks should never be transmitted - * to remote peers (consumerd and relayd). They are used - * internally for backward-compatibility purposes. - */ - ret = -LTTNG_ERR_FATAL; - goto error; - } - msg.u.trace_chunk_exists.chunk_id = chunk_id; - - DBG("Sending consumer trace chunk exists command: relayd_id = %" PRId64 - ", session_id = %" PRIu64 - ", chunk_id = %" PRIu64, relayd_id, session_id, chunk_id); - - health_code_update(); - ret = consumer_send_msg(socket, &msg); - switch (-ret) { - case LTTCOMM_CONSUMERD_UNKNOWN_TRACE_CHUNK: - consumer_reply_str = "unknown trace chunk"; - *result = CONSUMER_TRACE_CHUNK_EXISTS_STATUS_UNKNOWN_CHUNK; - break; - case LTTCOMM_CONSUMERD_TRACE_CHUNK_EXISTS_LOCAL: - consumer_reply_str = "trace chunk exists locally"; - *result = CONSUMER_TRACE_CHUNK_EXISTS_STATUS_EXISTS_LOCAL; - break; - case LTTCOMM_CONSUMERD_TRACE_CHUNK_EXISTS_REMOTE: - consumer_reply_str = "trace chunk exists on remote peer"; - *result = CONSUMER_TRACE_CHUNK_EXISTS_STATUS_EXISTS_REMOTE; - break; - default: - ERR("Consumer returned an error from TRACE_CHUNK_EXISTS command"); - ret = -1; - goto error; - } - DBG("Consumer reply to TRACE_CHUNK_EXISTS command: %s", - consumer_reply_str); - ret = 0; -error: - health_code_update(); - return ret; -} diff --git a/src/bin/lttng-sessiond/consumer.cpp b/src/bin/lttng-sessiond/consumer.cpp new file mode 100644 index 000000000..f7a92cb3d --- /dev/null +++ b/src/bin/lttng-sessiond/consumer.cpp @@ -0,0 +1,2165 @@ +/* + * Copyright (C) 2012 David Goulet + * Copyright (C) 2018 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "consumer.h" +#include "health-sessiond.h" +#include "ust-app.h" +#include "utils.h" +#include "lttng-sessiond.h" + +/* + * Return allocated full pathname of the session using the consumer trace path + * and subdir if available. + * + * The caller can safely free(3) the returned value. On error, NULL is + * returned. + */ +char *setup_channel_trace_path(struct consumer_output *consumer, + const char *session_path, size_t *consumer_path_offset) +{ + int ret; + char *pathname; + + LTTNG_ASSERT(consumer); + LTTNG_ASSERT(session_path); + + health_code_update(); + + /* + * Allocate the string ourself to make sure we never exceed + * LTTNG_PATH_MAX. + */ + pathname = (char *) zmalloc(LTTNG_PATH_MAX); + if (!pathname) { + goto error; + } + + /* Get correct path name destination */ + if (consumer->type == CONSUMER_DST_NET && + consumer->relay_major_version == 2 && + consumer->relay_minor_version < 11) { + ret = snprintf(pathname, LTTNG_PATH_MAX, "%s%s/%s/%s", + consumer->dst.net.base_dir, + consumer->chunk_path, consumer->domain_subdir, + session_path); + *consumer_path_offset = 0; + } else { + ret = snprintf(pathname, LTTNG_PATH_MAX, "%s/%s", + consumer->domain_subdir, session_path); + *consumer_path_offset = strlen(consumer->domain_subdir) + 1; + } + DBG3("Consumer trace path relative to current trace chunk: \"%s\"", + pathname); + if (ret < 0) { + PERROR("Failed to format channel path"); + goto error; + } else if (ret >= LTTNG_PATH_MAX) { + ERR("Truncation occurred while formatting channel path"); + goto error; + } + + return pathname; +error: + free(pathname); + return NULL; +} + +/* + * Send a data payload using a given consumer socket of size len. + * + * The consumer socket lock MUST be acquired before calling this since this + * function can change the fd value. + * + * Return 0 on success else a negative value on error. + */ +int consumer_socket_send( + struct consumer_socket *socket, const void *msg, size_t len) +{ + int fd; + ssize_t size; + + LTTNG_ASSERT(socket); + LTTNG_ASSERT(socket->fd_ptr); + LTTNG_ASSERT(msg); + + /* Consumer socket is invalid. Stopping. */ + fd = *socket->fd_ptr; + if (fd < 0) { + goto error; + } + + size = lttcomm_send_unix_sock(fd, msg, len); + if (size < 0) { + /* The above call will print a PERROR on error. */ + DBG("Error when sending data to consumer on sock %d", fd); + /* + * At this point, the socket is not usable anymore thus closing it and + * setting the file descriptor to -1 so it is not reused. + */ + + /* This call will PERROR on error. */ + (void) lttcomm_close_unix_sock(fd); + *socket->fd_ptr = -1; + goto error; + } + + return 0; + +error: + return -1; +} + +/* + * Receive a data payload using a given consumer socket of size len. + * + * The consumer socket lock MUST be acquired before calling this since this + * function can change the fd value. + * + * Return 0 on success else a negative value on error. + */ +int consumer_socket_recv(struct consumer_socket *socket, void *msg, size_t len) +{ + int fd; + ssize_t size; + + LTTNG_ASSERT(socket); + LTTNG_ASSERT(socket->fd_ptr); + LTTNG_ASSERT(msg); + + /* Consumer socket is invalid. Stopping. */ + fd = *socket->fd_ptr; + if (fd < 0) { + goto error; + } + + size = lttcomm_recv_unix_sock(fd, msg, len); + if (size <= 0) { + /* The above call will print a PERROR on error. */ + DBG("Error when receiving data from the consumer socket %d", fd); + /* + * At this point, the socket is not usable anymore thus closing it and + * setting the file descriptor to -1 so it is not reused. + */ + + /* This call will PERROR on error. */ + (void) lttcomm_close_unix_sock(fd); + *socket->fd_ptr = -1; + goto error; + } + + return 0; + +error: + return -1; +} + +/* + * Receive a reply command status message from the consumer. Consumer socket + * lock MUST be acquired before calling this function. + * + * Return 0 on success, -1 on recv error or a negative lttng error code which + * was possibly returned by the consumer. + */ +int consumer_recv_status_reply(struct consumer_socket *sock) +{ + int ret; + struct lttcomm_consumer_status_msg reply; + + LTTNG_ASSERT(sock); + + ret = consumer_socket_recv(sock, &reply, sizeof(reply)); + if (ret < 0) { + goto end; + } + + if (reply.ret_code == LTTCOMM_CONSUMERD_SUCCESS) { + /* All good. */ + ret = 0; + } else { + ret = -reply.ret_code; + DBG("Consumer ret code %d", ret); + } + +end: + return ret; +} + +/* + * Once the ASK_CHANNEL command is sent to the consumer, the channel + * information are sent back. This call receives that data and populates key + * and stream_count. + * + * On success return 0 and both key and stream_count are set. On error, a + * negative value is sent back and both parameters are untouched. + */ +int consumer_recv_status_channel(struct consumer_socket *sock, + uint64_t *key, unsigned int *stream_count) +{ + int ret; + struct lttcomm_consumer_status_channel reply; + + LTTNG_ASSERT(sock); + LTTNG_ASSERT(stream_count); + LTTNG_ASSERT(key); + + ret = consumer_socket_recv(sock, &reply, sizeof(reply)); + if (ret < 0) { + goto end; + } + + /* An error is possible so don't touch the key and stream_count. */ + if (reply.ret_code != LTTCOMM_CONSUMERD_SUCCESS) { + ret = -1; + goto end; + } + + *key = reply.key; + *stream_count = reply.stream_count; + ret = 0; + +end: + return ret; +} + +/* + * Send destroy relayd command to consumer. + * + * On success return positive value. On error, negative value. + */ +int consumer_send_destroy_relayd(struct consumer_socket *sock, + struct consumer_output *consumer) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(consumer); + LTTNG_ASSERT(sock); + + DBG2("Sending destroy relayd command to consumer sock %d", *sock->fd_ptr); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_DESTROY_RELAYD; + msg.u.destroy_relayd.net_seq_idx = consumer->net_seq_index; + + pthread_mutex_lock(sock->lock); + ret = consumer_socket_send(sock, &msg, sizeof(msg)); + if (ret < 0) { + goto error; + } + + /* Don't check the return value. The caller will do it. */ + ret = consumer_recv_status_reply(sock); + + DBG2("Consumer send destroy relayd command done"); + +error: + pthread_mutex_unlock(sock->lock); + return ret; +} + +/* + * For each consumer socket in the consumer output object, send a destroy + * relayd command. + */ +void consumer_output_send_destroy_relayd(struct consumer_output *consumer) +{ + struct lttng_ht_iter iter; + struct consumer_socket *socket; + + LTTNG_ASSERT(consumer); + + /* Destroy any relayd connection */ + if (consumer->type == CONSUMER_DST_NET) { + rcu_read_lock(); + cds_lfht_for_each_entry(consumer->socks->ht, &iter.iter, socket, + node.node) { + int ret; + + /* Send destroy relayd command */ + ret = consumer_send_destroy_relayd(socket, consumer); + if (ret < 0) { + DBG("Unable to send destroy relayd command to consumer"); + /* Continue since we MUST delete everything at this point. */ + } + } + rcu_read_unlock(); + } +} + +/* + * From a consumer_data structure, allocate and add a consumer socket to the + * consumer output. + * + * Return 0 on success, else negative value on error + */ +int consumer_create_socket(struct consumer_data *data, + struct consumer_output *output) +{ + int ret = 0; + struct consumer_socket *socket; + + LTTNG_ASSERT(data); + + if (output == NULL || data->cmd_sock < 0) { + /* + * Not an error. Possible there is simply not spawned consumer or it's + * disabled for the tracing session asking the socket. + */ + goto error; + } + + rcu_read_lock(); + socket = consumer_find_socket(data->cmd_sock, output); + rcu_read_unlock(); + if (socket == NULL) { + socket = consumer_allocate_socket(&data->cmd_sock); + if (socket == NULL) { + ret = -1; + goto error; + } + + socket->registered = 0; + socket->lock = &data->lock; + rcu_read_lock(); + consumer_add_socket(socket, output); + rcu_read_unlock(); + } + + socket->type = data->type; + + DBG3("Consumer socket created (fd: %d) and added to output", + data->cmd_sock); + +error: + return ret; +} + +/* + * Return the consumer socket from the given consumer output with the right + * bitness. On error, returns NULL. + * + * The caller MUST acquire a rcu read side lock and keep it until the socket + * object reference is not needed anymore. + */ +struct consumer_socket *consumer_find_socket_by_bitness(int bits, + const struct consumer_output *consumer) +{ + int consumer_fd; + struct consumer_socket *socket = NULL; + + switch (bits) { + case 64: + consumer_fd = uatomic_read(&the_ust_consumerd64_fd); + break; + case 32: + consumer_fd = uatomic_read(&the_ust_consumerd32_fd); + break; + default: + abort(); + goto end; + } + + socket = consumer_find_socket(consumer_fd, consumer); + if (!socket) { + ERR("Consumer socket fd %d not found in consumer obj %p", + consumer_fd, consumer); + } + +end: + return socket; +} + +/* + * Find a consumer_socket in a consumer_output hashtable. Read side lock must + * be acquired before calling this function and across use of the + * returned consumer_socket. + */ +struct consumer_socket *consumer_find_socket(int key, + const struct consumer_output *consumer) +{ + struct lttng_ht_iter iter; + struct lttng_ht_node_ulong *node; + struct consumer_socket *socket = NULL; + + /* Negative keys are lookup failures */ + if (key < 0 || consumer == NULL) { + return NULL; + } + + lttng_ht_lookup(consumer->socks, (void *)((unsigned long) key), + &iter); + node = lttng_ht_iter_get_node_ulong(&iter); + if (node != NULL) { + socket = caa_container_of(node, struct consumer_socket, node); + } + + return socket; +} + +/* + * Allocate a new consumer_socket and return the pointer. + */ +struct consumer_socket *consumer_allocate_socket(int *fd) +{ + struct consumer_socket *socket = NULL; + + LTTNG_ASSERT(fd); + + socket = (consumer_socket *) zmalloc(sizeof(struct consumer_socket)); + if (socket == NULL) { + PERROR("zmalloc consumer socket"); + goto error; + } + + socket->fd_ptr = fd; + lttng_ht_node_init_ulong(&socket->node, *fd); + +error: + return socket; +} + +/* + * Add consumer socket to consumer output object. Read side lock must be + * acquired before calling this function. + */ +void consumer_add_socket(struct consumer_socket *sock, + struct consumer_output *consumer) +{ + LTTNG_ASSERT(sock); + LTTNG_ASSERT(consumer); + + lttng_ht_add_unique_ulong(consumer->socks, &sock->node); +} + +/* + * Delete consumer socket to consumer output object. Read side lock must be + * acquired before calling this function. + */ +void consumer_del_socket(struct consumer_socket *sock, + struct consumer_output *consumer) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(sock); + LTTNG_ASSERT(consumer); + + iter.iter.node = &sock->node.node; + ret = lttng_ht_del(consumer->socks, &iter); + LTTNG_ASSERT(!ret); +} + +/* + * RCU destroy call function. + */ +static void destroy_socket_rcu(struct rcu_head *head) +{ + struct lttng_ht_node_ulong *node = + caa_container_of(head, struct lttng_ht_node_ulong, head); + struct consumer_socket *socket = + caa_container_of(node, struct consumer_socket, node); + + free(socket); +} + +/* + * Destroy and free socket pointer in a call RCU. Read side lock must be + * acquired before calling this function. + */ +void consumer_destroy_socket(struct consumer_socket *sock) +{ + LTTNG_ASSERT(sock); + + /* + * We DO NOT close the file descriptor here since it is global to the + * session daemon and is closed only if the consumer dies or a custom + * consumer was registered, + */ + if (sock->registered) { + DBG3("Consumer socket was registered. Closing fd %d", *sock->fd_ptr); + lttcomm_close_unix_sock(*sock->fd_ptr); + } + + call_rcu(&sock->node.head, destroy_socket_rcu); +} + +/* + * Allocate and assign data to a consumer_output object. + * + * Return pointer to structure. + */ +struct consumer_output *consumer_create_output(enum consumer_dst_type type) +{ + struct consumer_output *output = NULL; + + output = (consumer_output *) zmalloc(sizeof(struct consumer_output)); + if (output == NULL) { + PERROR("zmalloc consumer_output"); + goto error; + } + + /* By default, consumer output is enabled */ + output->enabled = 1; + output->type = type; + output->net_seq_index = (uint64_t) -1ULL; + urcu_ref_init(&output->ref); + + output->socks = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); + +error: + return output; +} + +/* + * Iterate over the consumer output socket hash table and destroy them. The + * socket file descriptor are only closed if the consumer output was + * registered meaning it's an external consumer. + */ +void consumer_destroy_output_sockets(struct consumer_output *obj) +{ + struct lttng_ht_iter iter; + struct consumer_socket *socket; + + if (!obj->socks) { + return; + } + + rcu_read_lock(); + cds_lfht_for_each_entry(obj->socks->ht, &iter.iter, socket, node.node) { + consumer_del_socket(socket, obj); + consumer_destroy_socket(socket); + } + rcu_read_unlock(); +} + +/* + * Delete the consumer_output object from the list and free the ptr. + * + * Should *NOT* be called with RCU read-side lock held. + */ +static void consumer_release_output(struct urcu_ref *ref) +{ + struct consumer_output *obj = + caa_container_of(ref, struct consumer_output, ref); + + consumer_destroy_output_sockets(obj); + + if (obj->socks) { + /* Finally destroy HT */ + ht_cleanup_push(obj->socks); + } + + free(obj); +} + +/* + * Get the consumer_output object. + */ +void consumer_output_get(struct consumer_output *obj) +{ + urcu_ref_get(&obj->ref); +} + +/* + * Put the consumer_output object. + * + * Should *NOT* be called with RCU read-side lock held. + */ +void consumer_output_put(struct consumer_output *obj) +{ + if (!obj) { + return; + } + urcu_ref_put(&obj->ref, consumer_release_output); +} + +/* + * Copy consumer output and returned the newly allocated copy. + * + * Should *NOT* be called with RCU read-side lock held. + */ +struct consumer_output *consumer_copy_output(struct consumer_output *src) +{ + int ret; + struct consumer_output *output; + + LTTNG_ASSERT(src); + + output = consumer_create_output(src->type); + if (output == NULL) { + goto end; + } + output->enabled = src->enabled; + output->net_seq_index = src->net_seq_index; + memcpy(output->domain_subdir, src->domain_subdir, + sizeof(output->domain_subdir)); + output->snapshot = src->snapshot; + output->relay_major_version = src->relay_major_version; + output->relay_minor_version = src->relay_minor_version; + output->relay_allows_clear = src->relay_allows_clear; + memcpy(&output->dst, &src->dst, sizeof(output->dst)); + ret = consumer_copy_sockets(output, src); + if (ret < 0) { + goto error_put; + } +end: + return output; + +error_put: + consumer_output_put(output); + return NULL; +} + +/* + * Copy consumer sockets from src to dst. + * + * Return 0 on success or else a negative value. + */ +int consumer_copy_sockets(struct consumer_output *dst, + struct consumer_output *src) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct consumer_socket *socket, *copy_sock; + + LTTNG_ASSERT(dst); + LTTNG_ASSERT(src); + + rcu_read_lock(); + cds_lfht_for_each_entry(src->socks->ht, &iter.iter, socket, node.node) { + /* Ignore socket that are already there. */ + copy_sock = consumer_find_socket(*socket->fd_ptr, dst); + if (copy_sock) { + continue; + } + + /* Create new socket object. */ + copy_sock = consumer_allocate_socket(socket->fd_ptr); + if (copy_sock == NULL) { + rcu_read_unlock(); + ret = -ENOMEM; + goto error; + } + + copy_sock->registered = socket->registered; + /* + * This is valid because this lock is shared accross all consumer + * object being the global lock of the consumer data structure of the + * session daemon. + */ + copy_sock->lock = socket->lock; + consumer_add_socket(copy_sock, dst); + } + rcu_read_unlock(); + +error: + return ret; +} + +/* + * Set network URI to the consumer output. + * + * Return 0 on success. Return 1 if the URI were equal. Else, negative value on + * error. + */ +int consumer_set_network_uri(const struct ltt_session *session, + struct consumer_output *output, + struct lttng_uri *uri) +{ + int ret; + struct lttng_uri *dst_uri = NULL; + + /* Code flow error safety net. */ + LTTNG_ASSERT(output); + LTTNG_ASSERT(uri); + + switch (uri->stype) { + case LTTNG_STREAM_CONTROL: + dst_uri = &output->dst.net.control; + output->dst.net.control_isset = 1; + if (uri->port == 0) { + /* Assign default port. */ + uri->port = DEFAULT_NETWORK_CONTROL_PORT; + } else { + if (output->dst.net.data_isset && uri->port == + output->dst.net.data.port) { + ret = -LTTNG_ERR_INVALID; + goto error; + } + } + DBG3("Consumer control URI set with port %d", uri->port); + break; + case LTTNG_STREAM_DATA: + dst_uri = &output->dst.net.data; + output->dst.net.data_isset = 1; + if (uri->port == 0) { + /* Assign default port. */ + uri->port = DEFAULT_NETWORK_DATA_PORT; + } else { + if (output->dst.net.control_isset && uri->port == + output->dst.net.control.port) { + ret = -LTTNG_ERR_INVALID; + goto error; + } + } + DBG3("Consumer data URI set with port %d", uri->port); + break; + default: + ERR("Set network uri type unknown %d", uri->stype); + ret = -LTTNG_ERR_INVALID; + goto error; + } + + ret = uri_compare(dst_uri, uri); + if (!ret) { + /* Same URI, don't touch it and return success. */ + DBG3("URI network compare are the same"); + goto equal; + } + + /* URIs were not equal, replacing it. */ + memcpy(dst_uri, uri, sizeof(struct lttng_uri)); + output->type = CONSUMER_DST_NET; + if (dst_uri->stype != LTTNG_STREAM_CONTROL) { + /* Only the control uri needs to contain the path. */ + goto end; + } + + /* + * If the user has specified a subdir as part of the control + * URL, the session's base output directory is: + * /RELAYD_OUTPUT_PATH/HOSTNAME/USER_SPECIFIED_DIR + * + * Hence, the "base_dir" from which all stream files and + * session rotation chunks are created takes the form + * /HOSTNAME/USER_SPECIFIED_DIR + * + * If the user has not specified an output directory as part of + * the control URL, the base output directory has the form: + * /RELAYD_OUTPUT_PATH/HOSTNAME/SESSION_NAME-CREATION_TIME + * + * Hence, the "base_dir" from which all stream files and + * session rotation chunks are created takes the form + * /HOSTNAME/SESSION_NAME-CREATION_TIME + * + * Note that automatically generated session names already + * contain the session's creation time. In that case, the + * creation time is omitted to prevent it from being duplicated + * in the final directory hierarchy. + */ + if (*uri->subdir) { + if (strstr(uri->subdir, "../")) { + ERR("Network URI subdirs are not allowed to walk up the path hierarchy"); + ret = -LTTNG_ERR_INVALID; + goto error; + } + ret = snprintf(output->dst.net.base_dir, + sizeof(output->dst.net.base_dir), + "/%s/%s/", session->hostname, uri->subdir); + } else { + if (session->has_auto_generated_name) { + ret = snprintf(output->dst.net.base_dir, + sizeof(output->dst.net.base_dir), + "/%s/%s/", session->hostname, + session->name); + } else { + char session_creation_datetime[16]; + size_t strftime_ret; + struct tm *timeinfo; + + timeinfo = localtime(&session->creation_time); + if (!timeinfo) { + ret = -LTTNG_ERR_FATAL; + goto error; + } + strftime_ret = strftime(session_creation_datetime, + sizeof(session_creation_datetime), + "%Y%m%d-%H%M%S", timeinfo); + if (strftime_ret == 0) { + ERR("Failed to format session creation timestamp while setting network URI"); + ret = -LTTNG_ERR_FATAL; + goto error; + } + ret = snprintf(output->dst.net.base_dir, + sizeof(output->dst.net.base_dir), + "/%s/%s-%s/", session->hostname, + session->name, + session_creation_datetime); + } + } + if (ret >= sizeof(output->dst.net.base_dir)) { + ret = -LTTNG_ERR_INVALID; + ERR("Truncation occurred while setting network output base directory"); + goto error; + } else if (ret == -1) { + ret = -LTTNG_ERR_INVALID; + PERROR("Error occurred while setting network output base directory"); + goto error; + } + + DBG3("Consumer set network uri base_dir path %s", + output->dst.net.base_dir); + +end: + return 0; +equal: + return 1; +error: + return ret; +} + +/* + * Send file descriptor to consumer via sock. + * + * The consumer socket lock must be held by the caller. + */ +int consumer_send_fds(struct consumer_socket *sock, const int *fds, + size_t nb_fd) +{ + int ret; + + LTTNG_ASSERT(fds); + LTTNG_ASSERT(sock); + LTTNG_ASSERT(nb_fd > 0); + LTTNG_ASSERT(pthread_mutex_trylock(sock->lock) == EBUSY); + + ret = lttcomm_send_fds_unix_sock(*sock->fd_ptr, fds, nb_fd); + if (ret < 0) { + /* The above call will print a PERROR on error. */ + DBG("Error when sending consumer fds on sock %d", *sock->fd_ptr); + goto error; + } + + ret = consumer_recv_status_reply(sock); +error: + return ret; +} + +/* + * Consumer send communication message structure to consumer. + * + * The consumer socket lock must be held by the caller. + */ +int consumer_send_msg(struct consumer_socket *sock, + const struct lttcomm_consumer_msg *msg) +{ + int ret; + + LTTNG_ASSERT(msg); + LTTNG_ASSERT(sock); + LTTNG_ASSERT(pthread_mutex_trylock(sock->lock) == EBUSY); + + ret = consumer_socket_send(sock, msg, sizeof(struct lttcomm_consumer_msg)); + if (ret < 0) { + goto error; + } + + ret = consumer_recv_status_reply(sock); + +error: + return ret; +} + +/* + * Consumer send channel communication message structure to consumer. + * + * The consumer socket lock must be held by the caller. + */ +int consumer_send_channel(struct consumer_socket *sock, + struct lttcomm_consumer_msg *msg) +{ + int ret; + + LTTNG_ASSERT(msg); + LTTNG_ASSERT(sock); + + ret = consumer_send_msg(sock, msg); + if (ret < 0) { + goto error; + } + +error: + return ret; +} + +/* + * Populate the given consumer msg structure with the ask_channel command + * information. + */ +void consumer_init_ask_channel_comm_msg(struct lttcomm_consumer_msg *msg, + uint64_t subbuf_size, + uint64_t num_subbuf, + int overwrite, + unsigned int switch_timer_interval, + unsigned int read_timer_interval, + unsigned int live_timer_interval, + bool is_in_live_session, + unsigned int monitor_timer_interval, + int output, + int type, + uint64_t session_id, + const char *pathname, + const char *name, + uint64_t relayd_id, + uint64_t key, + unsigned char *uuid, + uint32_t chan_id, + uint64_t tracefile_size, + uint64_t tracefile_count, + uint64_t session_id_per_pid, + unsigned int monitor, + uint32_t ust_app_uid, + int64_t blocking_timeout, + const char *root_shm_path, + const char *shm_path, + struct lttng_trace_chunk *trace_chunk, + const struct lttng_credentials *buffer_credentials) +{ + LTTNG_ASSERT(msg); + + /* Zeroed structure */ + memset(msg, 0, sizeof(struct lttcomm_consumer_msg)); + msg->u.ask_channel.buffer_credentials.uid = UINT32_MAX; + msg->u.ask_channel.buffer_credentials.gid = UINT32_MAX; + + if (trace_chunk) { + uint64_t chunk_id; + enum lttng_trace_chunk_status chunk_status; + + chunk_status = lttng_trace_chunk_get_id(trace_chunk, &chunk_id); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + LTTNG_OPTIONAL_SET(&msg->u.ask_channel.chunk_id, chunk_id); + } + msg->u.ask_channel.buffer_credentials.uid = + lttng_credentials_get_uid(buffer_credentials); + msg->u.ask_channel.buffer_credentials.gid = + lttng_credentials_get_gid(buffer_credentials); + + msg->cmd_type = LTTNG_CONSUMER_ASK_CHANNEL_CREATION; + msg->u.ask_channel.subbuf_size = subbuf_size; + msg->u.ask_channel.num_subbuf = num_subbuf ; + msg->u.ask_channel.overwrite = overwrite; + msg->u.ask_channel.switch_timer_interval = switch_timer_interval; + msg->u.ask_channel.read_timer_interval = read_timer_interval; + msg->u.ask_channel.live_timer_interval = live_timer_interval; + msg->u.ask_channel.is_live = is_in_live_session; + msg->u.ask_channel.monitor_timer_interval = monitor_timer_interval; + msg->u.ask_channel.output = output; + msg->u.ask_channel.type = type; + msg->u.ask_channel.session_id = session_id; + msg->u.ask_channel.session_id_per_pid = session_id_per_pid; + msg->u.ask_channel.relayd_id = relayd_id; + msg->u.ask_channel.key = key; + msg->u.ask_channel.chan_id = chan_id; + msg->u.ask_channel.tracefile_size = tracefile_size; + msg->u.ask_channel.tracefile_count = tracefile_count; + msg->u.ask_channel.monitor = monitor; + msg->u.ask_channel.ust_app_uid = ust_app_uid; + msg->u.ask_channel.blocking_timeout = blocking_timeout; + + memcpy(msg->u.ask_channel.uuid, uuid, sizeof(msg->u.ask_channel.uuid)); + + if (pathname) { + strncpy(msg->u.ask_channel.pathname, pathname, + sizeof(msg->u.ask_channel.pathname)); + msg->u.ask_channel.pathname[sizeof(msg->u.ask_channel.pathname)-1] = '\0'; + } + + strncpy(msg->u.ask_channel.name, name, sizeof(msg->u.ask_channel.name)); + msg->u.ask_channel.name[sizeof(msg->u.ask_channel.name) - 1] = '\0'; + + if (root_shm_path) { + strncpy(msg->u.ask_channel.root_shm_path, root_shm_path, + sizeof(msg->u.ask_channel.root_shm_path)); + msg->u.ask_channel.root_shm_path[sizeof(msg->u.ask_channel.root_shm_path) - 1] = '\0'; + } + if (shm_path) { + strncpy(msg->u.ask_channel.shm_path, shm_path, + sizeof(msg->u.ask_channel.shm_path)); + msg->u.ask_channel.shm_path[sizeof(msg->u.ask_channel.shm_path) - 1] = '\0'; + } +} + +/* + * Init channel communication message structure. + */ +void consumer_init_add_channel_comm_msg(struct lttcomm_consumer_msg *msg, + uint64_t channel_key, + uint64_t session_id, + const char *pathname, + uid_t uid, + gid_t gid, + uint64_t relayd_id, + const char *name, + unsigned int nb_init_streams, + enum lttng_event_output output, + int type, + uint64_t tracefile_size, + uint64_t tracefile_count, + unsigned int monitor, + unsigned int live_timer_interval, + bool is_in_live_session, + unsigned int monitor_timer_interval, + struct lttng_trace_chunk *trace_chunk) +{ + LTTNG_ASSERT(msg); + + /* Zeroed structure */ + memset(msg, 0, sizeof(struct lttcomm_consumer_msg)); + + if (trace_chunk) { + uint64_t chunk_id; + enum lttng_trace_chunk_status chunk_status; + + chunk_status = lttng_trace_chunk_get_id(trace_chunk, &chunk_id); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + LTTNG_OPTIONAL_SET(&msg->u.channel.chunk_id, chunk_id); + } + + /* Send channel */ + msg->cmd_type = LTTNG_CONSUMER_ADD_CHANNEL; + msg->u.channel.channel_key = channel_key; + msg->u.channel.session_id = session_id; + msg->u.channel.relayd_id = relayd_id; + msg->u.channel.nb_init_streams = nb_init_streams; + msg->u.channel.output = output; + msg->u.channel.type = type; + msg->u.channel.tracefile_size = tracefile_size; + msg->u.channel.tracefile_count = tracefile_count; + msg->u.channel.monitor = monitor; + msg->u.channel.live_timer_interval = live_timer_interval; + msg->u.channel.is_live = is_in_live_session; + msg->u.channel.monitor_timer_interval = monitor_timer_interval; + + strncpy(msg->u.channel.pathname, pathname, + sizeof(msg->u.channel.pathname)); + msg->u.channel.pathname[sizeof(msg->u.channel.pathname) - 1] = '\0'; + + strncpy(msg->u.channel.name, name, sizeof(msg->u.channel.name)); + msg->u.channel.name[sizeof(msg->u.channel.name) - 1] = '\0'; +} + +/* + * Init stream communication message structure. + */ +void consumer_init_add_stream_comm_msg(struct lttcomm_consumer_msg *msg, + uint64_t channel_key, + uint64_t stream_key, + int32_t cpu) +{ + LTTNG_ASSERT(msg); + + memset(msg, 0, sizeof(struct lttcomm_consumer_msg)); + + msg->cmd_type = LTTNG_CONSUMER_ADD_STREAM; + msg->u.stream.channel_key = channel_key; + msg->u.stream.stream_key = stream_key; + msg->u.stream.cpu = cpu; +} + +void consumer_init_streams_sent_comm_msg(struct lttcomm_consumer_msg *msg, + enum lttng_consumer_command cmd, + uint64_t channel_key, uint64_t net_seq_idx) +{ + LTTNG_ASSERT(msg); + + memset(msg, 0, sizeof(struct lttcomm_consumer_msg)); + + msg->cmd_type = cmd; + msg->u.sent_streams.channel_key = channel_key; + msg->u.sent_streams.net_seq_idx = net_seq_idx; +} + +/* + * Send stream communication structure to the consumer. + */ +int consumer_send_stream(struct consumer_socket *sock, + struct consumer_output *dst, struct lttcomm_consumer_msg *msg, + const int *fds, size_t nb_fd) +{ + int ret; + + LTTNG_ASSERT(msg); + LTTNG_ASSERT(dst); + LTTNG_ASSERT(sock); + LTTNG_ASSERT(fds); + + ret = consumer_send_msg(sock, msg); + if (ret < 0) { + goto error; + } + + ret = consumer_send_fds(sock, fds, nb_fd); + if (ret < 0) { + goto error; + } + +error: + return ret; +} + +/* + * Send relayd socket to consumer associated with a session name. + * + * The consumer socket lock must be held by the caller. + * + * On success return positive value. On error, negative value. + */ +int consumer_send_relayd_socket(struct consumer_socket *consumer_sock, + struct lttcomm_relayd_sock *rsock, struct consumer_output *consumer, + enum lttng_stream_type type, uint64_t session_id, + const char *session_name, const char *hostname, + const char *base_path, int session_live_timer, + const uint64_t *current_chunk_id, time_t session_creation_time, + bool session_name_contains_creation_time) +{ + int ret; + int fd; + struct lttcomm_consumer_msg msg; + + /* Code flow error. Safety net. */ + LTTNG_ASSERT(rsock); + LTTNG_ASSERT(consumer); + LTTNG_ASSERT(consumer_sock); + + memset(&msg, 0, sizeof(msg)); + /* Bail out if consumer is disabled */ + if (!consumer->enabled) { + ret = LTTNG_OK; + goto error; + } + + if (type == LTTNG_STREAM_CONTROL) { + char output_path[LTTNG_PATH_MAX] = {}; + uint64_t relayd_session_id; + + ret = relayd_create_session(rsock, &relayd_session_id, + session_name, hostname, base_path, + session_live_timer, consumer->snapshot, + session_id, the_sessiond_uuid, current_chunk_id, + session_creation_time, + session_name_contains_creation_time, + output_path); + if (ret < 0) { + /* Close the control socket. */ + (void) relayd_close(rsock); + goto error; + } + msg.u.relayd_sock.relayd_session_id = relayd_session_id; + DBG("Created session on relay, output path reply: %s", + output_path); + } + + msg.cmd_type = LTTNG_CONSUMER_ADD_RELAYD_SOCKET; + /* + * Assign network consumer output index using the temporary consumer since + * this call should only be made from within a set_consumer_uri() function + * call in the session daemon. + */ + msg.u.relayd_sock.net_index = consumer->net_seq_index; + msg.u.relayd_sock.type = type; + msg.u.relayd_sock.session_id = session_id; + memcpy(&msg.u.relayd_sock.sock, rsock, sizeof(msg.u.relayd_sock.sock)); + + DBG3("Sending relayd sock info to consumer on %d", *consumer_sock->fd_ptr); + ret = consumer_send_msg(consumer_sock, &msg); + if (ret < 0) { + goto error; + } + + DBG3("Sending relayd socket file descriptor to consumer"); + fd = rsock->sock.fd; + ret = consumer_send_fds(consumer_sock, &fd, 1); + if (ret < 0) { + goto error; + } + + DBG2("Consumer relayd socket sent"); + +error: + return ret; +} + +static +int consumer_send_pipe(struct consumer_socket *consumer_sock, + enum lttng_consumer_command cmd, int pipe) +{ + int ret; + struct lttcomm_consumer_msg msg; + const char *pipe_name; + const char *command_name; + + switch (cmd) { + case LTTNG_CONSUMER_SET_CHANNEL_MONITOR_PIPE: + pipe_name = "channel monitor"; + command_name = "SET_CHANNEL_MONITOR_PIPE"; + break; + default: + ERR("Unexpected command received in %s (cmd = %d)", __func__, + (int) cmd); + abort(); + } + + /* Code flow error. Safety net. */ + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = cmd; + + pthread_mutex_lock(consumer_sock->lock); + DBG3("Sending %s command to consumer", command_name); + ret = consumer_send_msg(consumer_sock, &msg); + if (ret < 0) { + goto error; + } + + DBG3("Sending %s pipe %d to consumer on socket %d", + pipe_name, + pipe, *consumer_sock->fd_ptr); + ret = consumer_send_fds(consumer_sock, &pipe, 1); + if (ret < 0) { + goto error; + } + + DBG2("%s pipe successfully sent", pipe_name); +error: + pthread_mutex_unlock(consumer_sock->lock); + return ret; +} + +int consumer_send_channel_monitor_pipe(struct consumer_socket *consumer_sock, + int pipe) +{ + return consumer_send_pipe(consumer_sock, + LTTNG_CONSUMER_SET_CHANNEL_MONITOR_PIPE, pipe); +} + +/* + * Ask the consumer if the data is pending for the specific session id. + * Returns 1 if data is pending, 0 otherwise, or < 0 on error. + */ +int consumer_is_data_pending(uint64_t session_id, + struct consumer_output *consumer) +{ + int ret; + int32_t ret_code = 0; /* Default is that the data is NOT pending */ + struct consumer_socket *socket; + struct lttng_ht_iter iter; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(consumer); + + DBG3("Consumer data pending for id %" PRIu64, session_id); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_DATA_PENDING; + msg.u.data_pending.session_id = session_id; + + /* Send command for each consumer */ + rcu_read_lock(); + cds_lfht_for_each_entry(consumer->socks->ht, &iter.iter, socket, + node.node) { + pthread_mutex_lock(socket->lock); + ret = consumer_socket_send(socket, &msg, sizeof(msg)); + if (ret < 0) { + pthread_mutex_unlock(socket->lock); + goto error_unlock; + } + + /* + * No need for a recv reply status because the answer to the command is + * the reply status message. + */ + + ret = consumer_socket_recv(socket, &ret_code, sizeof(ret_code)); + if (ret < 0) { + pthread_mutex_unlock(socket->lock); + goto error_unlock; + } + pthread_mutex_unlock(socket->lock); + + if (ret_code == 1) { + break; + } + } + rcu_read_unlock(); + + DBG("Consumer data is %s pending for session id %" PRIu64, + ret_code == 1 ? "" : "NOT", session_id); + return ret_code; + +error_unlock: + rcu_read_unlock(); + return -1; +} + +/* + * Send a flush command to consumer using the given channel key. + * + * Return 0 on success else a negative value. + */ +int consumer_flush_channel(struct consumer_socket *socket, uint64_t key) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(socket); + + DBG2("Consumer flush channel key %" PRIu64, key); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_FLUSH_CHANNEL; + msg.u.flush_channel.key = key; + + pthread_mutex_lock(socket->lock); + health_code_update(); + + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto end; + } + +end: + health_code_update(); + pthread_mutex_unlock(socket->lock); + return ret; +} + +/* + * Send a clear quiescent command to consumer using the given channel key. + * + * Return 0 on success else a negative value. + */ +int consumer_clear_quiescent_channel(struct consumer_socket *socket, uint64_t key) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(socket); + + DBG2("Consumer clear quiescent channel key %" PRIu64, key); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_CLEAR_QUIESCENT_CHANNEL; + msg.u.clear_quiescent_channel.key = key; + + pthread_mutex_lock(socket->lock); + health_code_update(); + + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto end; + } + +end: + health_code_update(); + pthread_mutex_unlock(socket->lock); + return ret; +} + +/* + * Send a close metadata command to consumer using the given channel key. + * Called with registry lock held. + * + * Return 0 on success else a negative value. + */ +int consumer_close_metadata(struct consumer_socket *socket, + uint64_t metadata_key) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(socket); + + DBG2("Consumer close metadata channel key %" PRIu64, metadata_key); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_CLOSE_METADATA; + msg.u.close_metadata.key = metadata_key; + + pthread_mutex_lock(socket->lock); + health_code_update(); + + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto end; + } + +end: + health_code_update(); + pthread_mutex_unlock(socket->lock); + return ret; +} + +/* + * Send a setup metdata command to consumer using the given channel key. + * + * Return 0 on success else a negative value. + */ +int consumer_setup_metadata(struct consumer_socket *socket, + uint64_t metadata_key) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(socket); + + DBG2("Consumer setup metadata channel key %" PRIu64, metadata_key); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_SETUP_METADATA; + msg.u.setup_metadata.key = metadata_key; + + pthread_mutex_lock(socket->lock); + health_code_update(); + + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto end; + } + +end: + health_code_update(); + pthread_mutex_unlock(socket->lock); + return ret; +} + +/* + * Send metadata string to consumer. + * RCU read-side lock must be held to guarantee existence of socket. + * + * Return 0 on success else a negative value. + */ +int consumer_push_metadata(struct consumer_socket *socket, + uint64_t metadata_key, char *metadata_str, size_t len, + size_t target_offset, uint64_t version) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(socket); + + DBG2("Consumer push metadata to consumer socket %d", *socket->fd_ptr); + + pthread_mutex_lock(socket->lock); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_PUSH_METADATA; + msg.u.push_metadata.key = metadata_key; + msg.u.push_metadata.target_offset = target_offset; + msg.u.push_metadata.len = len; + msg.u.push_metadata.version = version; + + health_code_update(); + ret = consumer_send_msg(socket, &msg); + if (ret < 0 || len == 0) { + goto end; + } + + DBG3("Consumer pushing metadata on sock %d of len %zu", *socket->fd_ptr, + len); + + ret = consumer_socket_send(socket, metadata_str, len); + if (ret < 0) { + goto end; + } + + health_code_update(); + ret = consumer_recv_status_reply(socket); + if (ret < 0) { + goto end; + } + +end: + pthread_mutex_unlock(socket->lock); + health_code_update(); + return ret; +} + +/* + * Ask the consumer to snapshot a specific channel using the key. + * + * Returns LTTNG_OK on success or else an LTTng error code. + */ +enum lttng_error_code consumer_snapshot_channel(struct consumer_socket *socket, + uint64_t key, const struct consumer_output *output, int metadata, + uid_t uid, gid_t gid, const char *channel_path, int wait, + uint64_t nb_packets_per_stream) +{ + int ret; + enum lttng_error_code status = LTTNG_OK; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(socket); + LTTNG_ASSERT(output); + + DBG("Consumer snapshot channel key %" PRIu64, key); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_SNAPSHOT_CHANNEL; + msg.u.snapshot_channel.key = key; + msg.u.snapshot_channel.nb_packets_per_stream = nb_packets_per_stream; + msg.u.snapshot_channel.metadata = metadata; + + if (output->type == CONSUMER_DST_NET) { + msg.u.snapshot_channel.relayd_id = + output->net_seq_index; + msg.u.snapshot_channel.use_relayd = 1; + } else { + msg.u.snapshot_channel.relayd_id = (uint64_t) -1ULL; + } + ret = lttng_strncpy(msg.u.snapshot_channel.pathname, + channel_path, + sizeof(msg.u.snapshot_channel.pathname)); + if (ret < 0) { + ERR("Snapshot path exceeds the maximal allowed length of %zu bytes (%zu bytes required) with path \"%s\"", + sizeof(msg.u.snapshot_channel.pathname), + strlen(channel_path), + channel_path); + status = LTTNG_ERR_SNAPSHOT_FAIL; + goto error; + } + + health_code_update(); + pthread_mutex_lock(socket->lock); + ret = consumer_send_msg(socket, &msg); + pthread_mutex_unlock(socket->lock); + if (ret < 0) { + switch (-ret) { + case LTTCOMM_CONSUMERD_CHAN_NOT_FOUND: + status = LTTNG_ERR_CHAN_NOT_FOUND; + break; + default: + status = LTTNG_ERR_SNAPSHOT_FAIL; + break; + } + goto error; + } + +error: + health_code_update(); + return status; +} + +/* + * Ask the consumer the number of discarded events for a channel. + */ +int consumer_get_discarded_events(uint64_t session_id, uint64_t channel_key, + struct consumer_output *consumer, uint64_t *discarded) +{ + int ret; + struct consumer_socket *socket; + struct lttng_ht_iter iter; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(consumer); + + DBG3("Consumer discarded events id %" PRIu64, session_id); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_DISCARDED_EVENTS; + msg.u.discarded_events.session_id = session_id; + msg.u.discarded_events.channel_key = channel_key; + + *discarded = 0; + + /* Send command for each consumer */ + rcu_read_lock(); + cds_lfht_for_each_entry(consumer->socks->ht, &iter.iter, socket, + node.node) { + uint64_t consumer_discarded = 0; + pthread_mutex_lock(socket->lock); + ret = consumer_socket_send(socket, &msg, sizeof(msg)); + if (ret < 0) { + pthread_mutex_unlock(socket->lock); + goto end; + } + + /* + * No need for a recv reply status because the answer to the + * command is the reply status message. + */ + ret = consumer_socket_recv(socket, &consumer_discarded, + sizeof(consumer_discarded)); + if (ret < 0) { + ERR("get discarded events"); + pthread_mutex_unlock(socket->lock); + goto end; + } + pthread_mutex_unlock(socket->lock); + *discarded += consumer_discarded; + } + ret = 0; + DBG("Consumer discarded %" PRIu64 " events in session id %" PRIu64, + *discarded, session_id); + +end: + rcu_read_unlock(); + return ret; +} + +/* + * Ask the consumer the number of lost packets for a channel. + */ +int consumer_get_lost_packets(uint64_t session_id, uint64_t channel_key, + struct consumer_output *consumer, uint64_t *lost) +{ + int ret; + struct consumer_socket *socket; + struct lttng_ht_iter iter; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(consumer); + + DBG3("Consumer lost packets id %" PRIu64, session_id); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_LOST_PACKETS; + msg.u.lost_packets.session_id = session_id; + msg.u.lost_packets.channel_key = channel_key; + + *lost = 0; + + /* Send command for each consumer */ + rcu_read_lock(); + cds_lfht_for_each_entry(consumer->socks->ht, &iter.iter, socket, + node.node) { + uint64_t consumer_lost = 0; + pthread_mutex_lock(socket->lock); + ret = consumer_socket_send(socket, &msg, sizeof(msg)); + if (ret < 0) { + pthread_mutex_unlock(socket->lock); + goto end; + } + + /* + * No need for a recv reply status because the answer to the + * command is the reply status message. + */ + ret = consumer_socket_recv(socket, &consumer_lost, + sizeof(consumer_lost)); + if (ret < 0) { + ERR("get lost packets"); + pthread_mutex_unlock(socket->lock); + goto end; + } + pthread_mutex_unlock(socket->lock); + *lost += consumer_lost; + } + ret = 0; + DBG("Consumer lost %" PRIu64 " packets in session id %" PRIu64, + *lost, session_id); + +end: + rcu_read_unlock(); + return ret; +} + +/* + * Ask the consumer to rotate a channel. + * + * The new_chunk_id is the session->rotate_count that has been incremented + * when the rotation started. On the relay, this allows to keep track in which + * chunk each stream is currently writing to (for the rotate_pending operation). + */ +int consumer_rotate_channel(struct consumer_socket *socket, uint64_t key, + uid_t uid, gid_t gid, struct consumer_output *output, + bool is_metadata_channel) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(socket); + + DBG("Consumer rotate channel key %" PRIu64, key); + + pthread_mutex_lock(socket->lock); + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_ROTATE_CHANNEL; + msg.u.rotate_channel.key = key; + msg.u.rotate_channel.metadata = !!is_metadata_channel; + + if (output->type == CONSUMER_DST_NET) { + msg.u.rotate_channel.relayd_id = output->net_seq_index; + } else { + msg.u.rotate_channel.relayd_id = (uint64_t) -1ULL; + } + + health_code_update(); + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + switch (-ret) { + case LTTCOMM_CONSUMERD_CHAN_NOT_FOUND: + ret = -LTTNG_ERR_CHAN_NOT_FOUND; + break; + default: + ret = -LTTNG_ERR_ROTATION_FAIL_CONSUMER; + break; + } + goto error; + } +error: + pthread_mutex_unlock(socket->lock); + health_code_update(); + return ret; +} + +int consumer_open_channel_packets(struct consumer_socket *socket, uint64_t key) +{ + int ret; + lttcomm_consumer_msg msg = { + .cmd_type = LTTNG_CONSUMER_OPEN_CHANNEL_PACKETS, + }; + msg.u.open_channel_packets.key = key; + + LTTNG_ASSERT(socket); + + DBG("Consumer open channel packets: channel key = %" PRIu64, key); + + health_code_update(); + + pthread_mutex_lock(socket->lock); + ret = consumer_send_msg(socket, &msg); + pthread_mutex_unlock(socket->lock); + if (ret < 0) { + goto error_socket; + } + +error_socket: + health_code_update(); + return ret; +} + +int consumer_clear_channel(struct consumer_socket *socket, uint64_t key) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(socket); + + DBG("Consumer clear channel %" PRIu64, key); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_CLEAR_CHANNEL; + msg.u.clear_channel.key = key; + + health_code_update(); + + pthread_mutex_lock(socket->lock); + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto error_socket; + } + +error_socket: + pthread_mutex_unlock(socket->lock); + + health_code_update(); + return ret; +} + +int consumer_init(struct consumer_socket *socket, + const lttng_uuid sessiond_uuid) +{ + int ret; + struct lttcomm_consumer_msg msg = { + .cmd_type = LTTNG_CONSUMER_INIT, + }; + + LTTNG_ASSERT(socket); + + DBG("Sending consumer initialization command"); + lttng_uuid_copy(msg.u.init.sessiond_uuid, sessiond_uuid); + + health_code_update(); + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto error; + } + +error: + health_code_update(); + return ret; +} + +/* + * Ask the consumer to create a new chunk for a given session. + * + * Called with the consumer socket lock held. + */ +int consumer_create_trace_chunk(struct consumer_socket *socket, + uint64_t relayd_id, uint64_t session_id, + struct lttng_trace_chunk *chunk, + const char *domain_subdir) +{ + int ret; + enum lttng_trace_chunk_status chunk_status; + struct lttng_credentials chunk_credentials; + const struct lttng_directory_handle *chunk_directory_handle = NULL; + struct lttng_directory_handle *domain_handle = NULL; + int domain_dirfd; + const char *chunk_name; + bool chunk_name_overridden; + uint64_t chunk_id; + time_t creation_timestamp; + char creation_timestamp_buffer[ISO8601_STR_LEN]; + const char *creation_timestamp_str = "(none)"; + const bool chunk_has_local_output = relayd_id == -1ULL; + enum lttng_trace_chunk_status tc_status; + struct lttcomm_consumer_msg msg = { + .cmd_type = LTTNG_CONSUMER_CREATE_TRACE_CHUNK, + }; + msg.u.create_trace_chunk.session_id = session_id; + + LTTNG_ASSERT(socket); + LTTNG_ASSERT(chunk); + + if (relayd_id != -1ULL) { + LTTNG_OPTIONAL_SET(&msg.u.create_trace_chunk.relayd_id, + relayd_id); + } + + chunk_status = lttng_trace_chunk_get_name(chunk, &chunk_name, + &chunk_name_overridden); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK && + chunk_status != LTTNG_TRACE_CHUNK_STATUS_NONE) { + ERR("Failed to get name of trace chunk"); + ret = -LTTNG_ERR_FATAL; + goto error; + } + if (chunk_name_overridden) { + ret = lttng_strncpy(msg.u.create_trace_chunk.override_name, + chunk_name, + sizeof(msg.u.create_trace_chunk.override_name)); + if (ret) { + ERR("Trace chunk name \"%s\" exceeds the maximal length allowed by the consumer protocol", + chunk_name); + ret = -LTTNG_ERR_FATAL; + goto error; + } + } + + chunk_status = lttng_trace_chunk_get_creation_timestamp(chunk, + &creation_timestamp); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = -LTTNG_ERR_FATAL; + goto error; + } + msg.u.create_trace_chunk.creation_timestamp = + (uint64_t) creation_timestamp; + /* Only used for logging purposes. */ + ret = time_to_iso8601_str(creation_timestamp, + creation_timestamp_buffer, + sizeof(creation_timestamp_buffer)); + creation_timestamp_str = !ret ? creation_timestamp_buffer : + "(formatting error)"; + + chunk_status = lttng_trace_chunk_get_id(chunk, &chunk_id); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + /* + * Anonymous trace chunks should never be transmitted + * to remote peers (consumerd and relayd). They are used + * internally for backward-compatibility purposes. + */ + ret = -LTTNG_ERR_FATAL; + goto error; + } + msg.u.create_trace_chunk.chunk_id = chunk_id; + + if (chunk_has_local_output) { + chunk_status = lttng_trace_chunk_borrow_chunk_directory_handle( + chunk, &chunk_directory_handle); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = -LTTNG_ERR_FATAL; + goto error; + } + chunk_status = lttng_trace_chunk_get_credentials( + chunk, &chunk_credentials); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + /* + * Not associating credentials to a sessiond chunk is a + * fatal internal error. + */ + ret = -LTTNG_ERR_FATAL; + goto error; + } + tc_status = lttng_trace_chunk_create_subdirectory( + chunk, domain_subdir); + if (tc_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + PERROR("Failed to create chunk domain output directory \"%s\"", + domain_subdir); + ret = -LTTNG_ERR_FATAL; + goto error; + } + domain_handle = lttng_directory_handle_create_from_handle( + domain_subdir, + chunk_directory_handle); + if (!domain_handle) { + ret = -LTTNG_ERR_FATAL; + goto error; + } + + /* + * This will only compile on platforms that support + * dirfd (POSIX.2008). This is fine as the session daemon + * is only built for such platforms. + * + * The ownership of the chunk directory handle's is maintained + * by the trace chunk. + */ + domain_dirfd = lttng_directory_handle_get_dirfd( + domain_handle); + LTTNG_ASSERT(domain_dirfd >= 0); + + msg.u.create_trace_chunk.credentials.value.uid = + lttng_credentials_get_uid(&chunk_credentials); + msg.u.create_trace_chunk.credentials.value.gid = + lttng_credentials_get_gid(&chunk_credentials); + msg.u.create_trace_chunk.credentials.is_set = 1; + } + + DBG("Sending consumer create trace chunk command: relayd_id = %" PRId64 + ", session_id = %" PRIu64 ", chunk_id = %" PRIu64 + ", creation_timestamp = %s", + relayd_id, session_id, chunk_id, + creation_timestamp_str); + health_code_update(); + ret = consumer_send_msg(socket, &msg); + health_code_update(); + if (ret < 0) { + ERR("Trace chunk creation error on consumer"); + ret = -LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; + goto error; + } + + if (chunk_has_local_output) { + DBG("Sending trace chunk domain directory fd to consumer"); + health_code_update(); + ret = consumer_send_fds(socket, &domain_dirfd, 1); + health_code_update(); + if (ret < 0) { + ERR("Trace chunk creation error on consumer"); + ret = -LTTNG_ERR_CREATE_TRACE_CHUNK_FAIL_CONSUMER; + goto error; + } + } +error: + lttng_directory_handle_put(domain_handle); + return ret; +} + +/* + * Ask the consumer to close a trace chunk for a given session. + * + * Called with the consumer socket lock held. + */ +int consumer_close_trace_chunk(struct consumer_socket *socket, + uint64_t relayd_id, uint64_t session_id, + struct lttng_trace_chunk *chunk, + char *closed_trace_chunk_path) +{ + int ret; + enum lttng_trace_chunk_status chunk_status; + lttcomm_consumer_msg msg = { + .cmd_type = LTTNG_CONSUMER_CLOSE_TRACE_CHUNK, + }; + msg.u.close_trace_chunk.session_id = session_id; + + struct lttcomm_consumer_close_trace_chunk_reply reply; + uint64_t chunk_id; + time_t close_timestamp; + enum lttng_trace_chunk_command_type close_command; + const char *close_command_name = "none"; + struct lttng_dynamic_buffer path_reception_buffer; + + LTTNG_ASSERT(socket); + lttng_dynamic_buffer_init(&path_reception_buffer); + + if (relayd_id != -1ULL) { + LTTNG_OPTIONAL_SET( + &msg.u.close_trace_chunk.relayd_id, relayd_id); + } + + chunk_status = lttng_trace_chunk_get_close_command( + chunk, &close_command); + switch (chunk_status) { + case LTTNG_TRACE_CHUNK_STATUS_OK: + LTTNG_OPTIONAL_SET(&msg.u.close_trace_chunk.close_command, + (uint32_t) close_command); + break; + case LTTNG_TRACE_CHUNK_STATUS_NONE: + break; + default: + ERR("Failed to get trace chunk close command"); + ret = -1; + goto error; + } + + chunk_status = lttng_trace_chunk_get_id(chunk, &chunk_id); + /* + * Anonymous trace chunks should never be transmitted to remote peers + * (consumerd and relayd). They are used internally for + * backward-compatibility purposes. + */ + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + msg.u.close_trace_chunk.chunk_id = chunk_id; + + chunk_status = lttng_trace_chunk_get_close_timestamp(chunk, + &close_timestamp); + /* + * A trace chunk should be closed locally before being closed remotely. + * Otherwise, the close timestamp would never be transmitted to the + * peers. + */ + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + msg.u.close_trace_chunk.close_timestamp = (uint64_t) close_timestamp; + + if (msg.u.close_trace_chunk.close_command.is_set) { + close_command_name = lttng_trace_chunk_command_type_get_name( + close_command); + } + DBG("Sending consumer close trace chunk command: relayd_id = %" PRId64 + ", session_id = %" PRIu64 ", chunk_id = %" PRIu64 + ", close command = \"%s\"", + relayd_id, session_id, chunk_id, close_command_name); + + health_code_update(); + ret = consumer_socket_send(socket, &msg, sizeof(struct lttcomm_consumer_msg)); + if (ret < 0) { + ret = -LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; + goto error; + } + ret = consumer_socket_recv(socket, &reply, sizeof(reply)); + if (ret < 0) { + ret = -LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; + goto error; + } + if (reply.path_length >= LTTNG_PATH_MAX) { + ERR("Invalid path returned by relay daemon: %" PRIu32 "bytes exceeds maximal allowed length of %d bytes", + reply.path_length, LTTNG_PATH_MAX); + ret = -LTTNG_ERR_INVALID_PROTOCOL; + goto error; + } + ret = lttng_dynamic_buffer_set_size(&path_reception_buffer, + reply.path_length); + if (ret) { + ERR("Failed to allocate reception buffer of path returned by the \"close trace chunk\" command"); + ret = -LTTNG_ERR_NOMEM; + goto error; + } + ret = consumer_socket_recv(socket, path_reception_buffer.data, + path_reception_buffer.size); + if (ret < 0) { + ERR("Communication error while receiving path of closed trace chunk"); + ret = -LTTNG_ERR_CLOSE_TRACE_CHUNK_FAIL_CONSUMER; + goto error; + } + if (path_reception_buffer.data[path_reception_buffer.size - 1] != '\0') { + ERR("Invalid path returned by relay daemon: not null-terminated"); + ret = -LTTNG_ERR_INVALID_PROTOCOL; + goto error; + } + if (closed_trace_chunk_path) { + /* + * closed_trace_chunk_path is assumed to have a length >= + * LTTNG_PATH_MAX + */ + memcpy(closed_trace_chunk_path, path_reception_buffer.data, + path_reception_buffer.size); + } +error: + lttng_dynamic_buffer_reset(&path_reception_buffer); + health_code_update(); + return ret; +} + +/* + * Ask the consumer if a trace chunk exists. + * + * Called with the consumer socket lock held. + * Returns 0 on success, or a negative value on error. + */ +int consumer_trace_chunk_exists(struct consumer_socket *socket, + uint64_t relayd_id, uint64_t session_id, + struct lttng_trace_chunk *chunk, + enum consumer_trace_chunk_exists_status *result) +{ + int ret; + enum lttng_trace_chunk_status chunk_status; + lttcomm_consumer_msg msg = { + .cmd_type = LTTNG_CONSUMER_TRACE_CHUNK_EXISTS, + }; + msg.u.trace_chunk_exists.session_id = session_id; + + uint64_t chunk_id; + const char *consumer_reply_str; + + LTTNG_ASSERT(socket); + + if (relayd_id != -1ULL) { + LTTNG_OPTIONAL_SET(&msg.u.trace_chunk_exists.relayd_id, + relayd_id); + } + + chunk_status = lttng_trace_chunk_get_id(chunk, &chunk_id); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + /* + * Anonymous trace chunks should never be transmitted + * to remote peers (consumerd and relayd). They are used + * internally for backward-compatibility purposes. + */ + ret = -LTTNG_ERR_FATAL; + goto error; + } + msg.u.trace_chunk_exists.chunk_id = chunk_id; + + DBG("Sending consumer trace chunk exists command: relayd_id = %" PRId64 + ", session_id = %" PRIu64 + ", chunk_id = %" PRIu64, relayd_id, session_id, chunk_id); + + health_code_update(); + ret = consumer_send_msg(socket, &msg); + switch (-ret) { + case LTTCOMM_CONSUMERD_UNKNOWN_TRACE_CHUNK: + consumer_reply_str = "unknown trace chunk"; + *result = CONSUMER_TRACE_CHUNK_EXISTS_STATUS_UNKNOWN_CHUNK; + break; + case LTTCOMM_CONSUMERD_TRACE_CHUNK_EXISTS_LOCAL: + consumer_reply_str = "trace chunk exists locally"; + *result = CONSUMER_TRACE_CHUNK_EXISTS_STATUS_EXISTS_LOCAL; + break; + case LTTCOMM_CONSUMERD_TRACE_CHUNK_EXISTS_REMOTE: + consumer_reply_str = "trace chunk exists on remote peer"; + *result = CONSUMER_TRACE_CHUNK_EXISTS_STATUS_EXISTS_REMOTE; + break; + default: + ERR("Consumer returned an error from TRACE_CHUNK_EXISTS command"); + ret = -1; + goto error; + } + DBG("Consumer reply to TRACE_CHUNK_EXISTS command: %s", + consumer_reply_str); + ret = 0; +error: + health_code_update(); + return ret; +} diff --git a/src/bin/lttng-sessiond/consumer.h b/src/bin/lttng-sessiond/consumer.h index eaa04d302..ffba198a8 100644 --- a/src/bin/lttng-sessiond/consumer.h +++ b/src/bin/lttng-sessiond/consumer.h @@ -12,6 +12,7 @@ #include #include #include +#include #include "snapshot.h" @@ -19,6 +20,19 @@ struct snapshot; struct snapshot_output; struct ltt_session; +/* + * Needed until we use C++14, where std::max is constexpr. + * + * Use a static_assert so we remember to remove it when we upgrade to a newer + * C++. + */ +static_assert(__cplusplus == 201103L, ""); +template +constexpr T max_constexpr(T l, T r) +{ + return l > r ? l : r; +} + enum consumer_dst_type { CONSUMER_DST_LOCAL, CONSUMER_DST_NET, @@ -61,39 +75,43 @@ struct consumer_socket { }; struct consumer_data { + consumer_data (lttng_consumer_type type_) + : type(type_) + {} + enum lttng_consumer_type type; /* Mutex to control consumerd pid assignation */ - pthread_mutex_t pid_mutex; - pid_t pid; + pthread_mutex_t pid_mutex = PTHREAD_MUTEX_INITIALIZER; + pid_t pid = 0; - int err_sock; + int err_sock = -1; /* These two sockets uses the cmd_unix_sock_path. */ - int cmd_sock; + int cmd_sock = -1; /* * Write-end of the channel monitoring pipe to be passed to the * consumer. */ - int channel_monitor_pipe; + int channel_monitor_pipe = -1; /* * The metadata socket object is handled differently and only created * locally in this object thus it's the only reference available in the * session daemon. For that reason, a variable for the fd is required and * the metadata socket fd points to it. */ - int metadata_fd; - struct consumer_socket metadata_sock; + int metadata_fd = 0; + struct consumer_socket metadata_sock {}; /* consumer error and command Unix socket path */ - const char *err_unix_sock_path; - const char *cmd_unix_sock_path; + const char *err_unix_sock_path = nullptr; + const char *cmd_unix_sock_path = nullptr; /* * This lock has two purposes. It protects any change to the consumer * socket and make sure only one thread uses this object for read/write * operations. */ - pthread_mutex_t lock; + pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; }; /* @@ -149,7 +167,8 @@ struct consumer_output { * Subdirectory path name used for both local and network * consumer ("kernel", "ust", or empty). */ - char domain_subdir[max(sizeof(DEFAULT_KERNEL_TRACE_DIR), + char domain_subdir[ + max_constexpr(sizeof(DEFAULT_KERNEL_TRACE_DIR), sizeof(DEFAULT_UST_TRACE_DIR))]; /* diff --git a/src/bin/lttng-sessiond/context.c b/src/bin/lttng-sessiond/context.c deleted file mode 100644 index c8c892438..000000000 --- a/src/bin/lttng-sessiond/context.c +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2016 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include - -#include -#include - -#include "context.h" -#include "kernel.h" -#include "ust-app.h" -#include "trace-ust.h" -#include "agent.h" - -/* - * Add kernel context to all channel. - * - * Assumes the ownership of kctx. - */ -static int add_kctx_all_channels(struct ltt_kernel_session *ksession, - struct ltt_kernel_context *kctx) -{ - int ret; - struct ltt_kernel_channel *kchan; - - LTTNG_ASSERT(ksession); - LTTNG_ASSERT(kctx); - - DBG("Adding kernel context to all channels"); - - /* Go over all channels */ - cds_list_for_each_entry(kchan, &ksession->channel_list.head, list) { - struct ltt_kernel_context *kctx_copy; - - kctx_copy = trace_kernel_copy_context(kctx); - if (!kctx_copy) { - PERROR("zmalloc ltt_kernel_context"); - ret = -LTTNG_ERR_NOMEM; - goto error; - } - - /* Ownership of kctx_copy is transferred to the callee. */ - ret = kernel_add_channel_context(kchan, kctx_copy); - kctx_copy = NULL; - if (ret != 0) { - goto error; - } - } - - ret = LTTNG_OK; - -error: - trace_kernel_destroy_context(kctx); - return ret; -} - -/* - * Add kernel context to a specific channel. - * - * Assumes the ownership of kctx. - */ -static int add_kctx_to_channel(struct ltt_kernel_context *kctx, - struct ltt_kernel_channel *kchan) -{ - int ret; - - LTTNG_ASSERT(kchan); - LTTNG_ASSERT(kctx); - - DBG("Add kernel context to channel '%s'", kchan->channel->name); - - /* Ownership of kctx is transferred to the callee. */ - ret = kernel_add_channel_context(kchan, kctx); - kctx = NULL; - if (ret != 0) { - goto error; - } - - ret = LTTNG_OK; - -error: - return ret; -} - -/* - * Add UST context to channel. - */ -static int add_uctx_to_channel(struct ltt_ust_session *usess, - enum lttng_domain_type domain, - struct ltt_ust_channel *uchan, - const struct lttng_event_context *ctx) -{ - int ret; - struct ltt_ust_context *uctx = NULL; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(uchan); - LTTNG_ASSERT(ctx); - - /* Check if context is duplicate */ - cds_list_for_each_entry(uctx, &uchan->ctx_list, list) { - if (trace_ust_match_context(uctx, ctx)) { - ret = LTTNG_ERR_UST_CONTEXT_EXIST; - goto duplicate; - } - } - uctx = NULL; - - switch (domain) { - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - { - struct agent *agt; - - if (ctx->ctx != LTTNG_EVENT_CONTEXT_APP_CONTEXT) { - /* Other contexts are not needed by the agent. */ - break; - } - agt = trace_ust_find_agent(usess, domain); - - if (!agt) { - agt = agent_create(domain); - if (!agt) { - ret = -LTTNG_ERR_NOMEM; - goto error; - } - agent_add(agt, usess->agents); - } - ret = agent_add_context(ctx, agt); - if (ret != LTTNG_OK) { - goto error; - } - - ret = agent_enable_context(ctx, domain); - if (ret != LTTNG_OK) { - goto error; - } - break; - } - case LTTNG_DOMAIN_UST: - break; - default: - abort(); - } - - /* Create ltt UST context */ - uctx = trace_ust_create_context(ctx); - if (uctx == NULL) { - ret = LTTNG_ERR_UST_CONTEXT_INVAL; - goto error; - } - - /* Add ltt UST context node to ltt UST channel */ - lttng_ht_add_ulong(uchan->ctx, &uctx->node); - cds_list_add_tail(&uctx->list, &uchan->ctx_list); - - if (!usess->active) { - goto end; - } - - ret = ust_app_add_ctx_channel_glb(usess, uchan, uctx); - if (ret < 0) { - goto error; - } -end: - DBG("Context UST %d added to channel %s", uctx->ctx.ctx, uchan->name); - - return 0; - -error: - free(uctx); -duplicate: - return ret; -} - -/* - * Add kernel context to tracer. - */ -int context_kernel_add(struct ltt_kernel_session *ksession, - const struct lttng_event_context *ctx, char *channel_name) -{ - int ret; - struct ltt_kernel_channel *kchan; - struct ltt_kernel_context *kctx; - - LTTNG_ASSERT(ksession); - LTTNG_ASSERT(ctx); - LTTNG_ASSERT(channel_name); - - kctx = trace_kernel_create_context(NULL); - if (!kctx) { - ret = -LTTNG_ERR_NOMEM; - goto error; - } - - /* Setup kernel context structure */ - switch (ctx->ctx) { - case LTTNG_EVENT_CONTEXT_PID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PID; - break; - case LTTNG_EVENT_CONTEXT_PROCNAME: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PROCNAME; - break; - case LTTNG_EVENT_CONTEXT_PRIO: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PRIO; - break; - case LTTNG_EVENT_CONTEXT_NICE: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_NICE; - break; - case LTTNG_EVENT_CONTEXT_VPID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VPID; - break; - case LTTNG_EVENT_CONTEXT_TID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_TID; - break; - case LTTNG_EVENT_CONTEXT_VTID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VTID; - break; - case LTTNG_EVENT_CONTEXT_PPID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PPID; - break; - case LTTNG_EVENT_CONTEXT_VPPID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VPPID; - break; - case LTTNG_EVENT_CONTEXT_HOSTNAME: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_HOSTNAME; - break; - case LTTNG_EVENT_CONTEXT_PERF_CPU_COUNTER: - case LTTNG_EVENT_CONTEXT_PERF_COUNTER: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PERF_CPU_COUNTER; - break; - case LTTNG_EVENT_CONTEXT_INTERRUPTIBLE: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_INTERRUPTIBLE; - break; - case LTTNG_EVENT_CONTEXT_PREEMPTIBLE: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PREEMPTIBLE; - break; - case LTTNG_EVENT_CONTEXT_NEED_RESCHEDULE: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_NEED_RESCHEDULE; - break; - case LTTNG_EVENT_CONTEXT_MIGRATABLE: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_MIGRATABLE; - break; - case LTTNG_EVENT_CONTEXT_CALLSTACK_KERNEL: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_CALLSTACK_KERNEL; - break; - case LTTNG_EVENT_CONTEXT_CALLSTACK_USER: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_CALLSTACK_USER; - break; - case LTTNG_EVENT_CONTEXT_CGROUP_NS: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_CGROUP_NS; - break; - case LTTNG_EVENT_CONTEXT_IPC_NS: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_IPC_NS; - break; - case LTTNG_EVENT_CONTEXT_MNT_NS: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_MNT_NS; - break; - case LTTNG_EVENT_CONTEXT_NET_NS: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_NET_NS; - break; - case LTTNG_EVENT_CONTEXT_PID_NS: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PID_NS; - break; - case LTTNG_EVENT_CONTEXT_TIME_NS: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_TIME_NS; - break; - case LTTNG_EVENT_CONTEXT_USER_NS: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_USER_NS; - break; - case LTTNG_EVENT_CONTEXT_UTS_NS: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_UTS_NS; - break; - case LTTNG_EVENT_CONTEXT_UID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_UID; - break; - case LTTNG_EVENT_CONTEXT_EUID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_EUID; - break; - case LTTNG_EVENT_CONTEXT_SUID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_SUID; - break; - case LTTNG_EVENT_CONTEXT_GID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_GID; - break; - case LTTNG_EVENT_CONTEXT_EGID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_EGID; - break; - case LTTNG_EVENT_CONTEXT_SGID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_SGID; - break; - case LTTNG_EVENT_CONTEXT_VUID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VUID; - break; - case LTTNG_EVENT_CONTEXT_VEUID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VEUID; - break; - case LTTNG_EVENT_CONTEXT_VSUID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VSUID; - break; - case LTTNG_EVENT_CONTEXT_VGID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VGID; - break; - case LTTNG_EVENT_CONTEXT_VEGID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VEGID; - break; - case LTTNG_EVENT_CONTEXT_VSGID: - kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VSGID; - break; - default: - ret = LTTNG_ERR_KERN_CONTEXT_FAIL; - goto error; - } - - kctx->ctx.u.perf_counter.type = ctx->u.perf_counter.type; - kctx->ctx.u.perf_counter.config = ctx->u.perf_counter.config; - strncpy(kctx->ctx.u.perf_counter.name, ctx->u.perf_counter.name, - LTTNG_SYMBOL_NAME_LEN); - kctx->ctx.u.perf_counter.name[LTTNG_SYMBOL_NAME_LEN - 1] = '\0'; - - if (*channel_name == '\0') { - ret = add_kctx_all_channels(ksession, kctx); - /* Ownership of kctx is transferred to the callee. */ - kctx = NULL; - if (ret != LTTNG_OK) { - goto error; - } - } else { - /* Get kernel channel */ - kchan = trace_kernel_get_channel_by_name(channel_name, ksession); - if (kchan == NULL) { - ret = LTTNG_ERR_KERN_CHAN_NOT_FOUND; - goto error; - } - - ret = add_kctx_to_channel(kctx, kchan); - /* Ownership of kctx is transferred to the callee. */ - kctx = NULL; - if (ret != LTTNG_OK) { - goto error; - } - } - - ret = LTTNG_OK; - -error: - if (kctx) { - trace_kernel_destroy_context(kctx); - } - return ret; -} - -/* - * Add UST context to tracer. - */ -int context_ust_add(struct ltt_ust_session *usess, - enum lttng_domain_type domain, - const struct lttng_event_context *ctx, - char *channel_name) -{ - int ret = LTTNG_OK; - struct lttng_ht_iter iter; - struct lttng_ht *chan_ht; - struct ltt_ust_channel *uchan = NULL; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(ctx); - LTTNG_ASSERT(channel_name); - - rcu_read_lock(); - - chan_ht = usess->domain_global.channels; - - /* Get UST channel if defined */ - if (channel_name[0] != '\0') { - uchan = trace_ust_find_channel_by_name(chan_ht, channel_name); - if (uchan == NULL) { - ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; - goto error; - } - } - - if (uchan) { - /* Add ctx to channel */ - ret = add_uctx_to_channel(usess, domain, uchan, ctx); - } else { - rcu_read_lock(); - /* Add ctx all events, all channels */ - cds_lfht_for_each_entry(chan_ht->ht, &iter.iter, uchan, node.node) { - ret = add_uctx_to_channel(usess, domain, uchan, ctx); - if (ret) { - ERR("Failed to add context to channel %s", - uchan->name); - continue; - } - } - rcu_read_unlock(); - } - - switch (ret) { - case LTTNG_ERR_UST_CONTEXT_EXIST: - break; - case -ENOMEM: - case -LTTNG_ERR_NOMEM: - ret = LTTNG_ERR_FATAL; - break; - case -EINVAL: - ret = LTTNG_ERR_UST_CONTEXT_INVAL; - break; - case -ENOSYS: - ret = LTTNG_ERR_UNKNOWN_DOMAIN; - break; - default: - if (ret != 0 && ret != LTTNG_OK) { - ret = ret > 0 ? ret : LTTNG_ERR_UNK; - } else { - ret = LTTNG_OK; - } - break; - } - -error: - rcu_read_unlock(); - return ret; -} diff --git a/src/bin/lttng-sessiond/context.cpp b/src/bin/lttng-sessiond/context.cpp new file mode 100644 index 000000000..c8c892438 --- /dev/null +++ b/src/bin/lttng-sessiond/context.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2016 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include + +#include +#include + +#include "context.h" +#include "kernel.h" +#include "ust-app.h" +#include "trace-ust.h" +#include "agent.h" + +/* + * Add kernel context to all channel. + * + * Assumes the ownership of kctx. + */ +static int add_kctx_all_channels(struct ltt_kernel_session *ksession, + struct ltt_kernel_context *kctx) +{ + int ret; + struct ltt_kernel_channel *kchan; + + LTTNG_ASSERT(ksession); + LTTNG_ASSERT(kctx); + + DBG("Adding kernel context to all channels"); + + /* Go over all channels */ + cds_list_for_each_entry(kchan, &ksession->channel_list.head, list) { + struct ltt_kernel_context *kctx_copy; + + kctx_copy = trace_kernel_copy_context(kctx); + if (!kctx_copy) { + PERROR("zmalloc ltt_kernel_context"); + ret = -LTTNG_ERR_NOMEM; + goto error; + } + + /* Ownership of kctx_copy is transferred to the callee. */ + ret = kernel_add_channel_context(kchan, kctx_copy); + kctx_copy = NULL; + if (ret != 0) { + goto error; + } + } + + ret = LTTNG_OK; + +error: + trace_kernel_destroy_context(kctx); + return ret; +} + +/* + * Add kernel context to a specific channel. + * + * Assumes the ownership of kctx. + */ +static int add_kctx_to_channel(struct ltt_kernel_context *kctx, + struct ltt_kernel_channel *kchan) +{ + int ret; + + LTTNG_ASSERT(kchan); + LTTNG_ASSERT(kctx); + + DBG("Add kernel context to channel '%s'", kchan->channel->name); + + /* Ownership of kctx is transferred to the callee. */ + ret = kernel_add_channel_context(kchan, kctx); + kctx = NULL; + if (ret != 0) { + goto error; + } + + ret = LTTNG_OK; + +error: + return ret; +} + +/* + * Add UST context to channel. + */ +static int add_uctx_to_channel(struct ltt_ust_session *usess, + enum lttng_domain_type domain, + struct ltt_ust_channel *uchan, + const struct lttng_event_context *ctx) +{ + int ret; + struct ltt_ust_context *uctx = NULL; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(uchan); + LTTNG_ASSERT(ctx); + + /* Check if context is duplicate */ + cds_list_for_each_entry(uctx, &uchan->ctx_list, list) { + if (trace_ust_match_context(uctx, ctx)) { + ret = LTTNG_ERR_UST_CONTEXT_EXIST; + goto duplicate; + } + } + uctx = NULL; + + switch (domain) { + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + { + struct agent *agt; + + if (ctx->ctx != LTTNG_EVENT_CONTEXT_APP_CONTEXT) { + /* Other contexts are not needed by the agent. */ + break; + } + agt = trace_ust_find_agent(usess, domain); + + if (!agt) { + agt = agent_create(domain); + if (!agt) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + agent_add(agt, usess->agents); + } + ret = agent_add_context(ctx, agt); + if (ret != LTTNG_OK) { + goto error; + } + + ret = agent_enable_context(ctx, domain); + if (ret != LTTNG_OK) { + goto error; + } + break; + } + case LTTNG_DOMAIN_UST: + break; + default: + abort(); + } + + /* Create ltt UST context */ + uctx = trace_ust_create_context(ctx); + if (uctx == NULL) { + ret = LTTNG_ERR_UST_CONTEXT_INVAL; + goto error; + } + + /* Add ltt UST context node to ltt UST channel */ + lttng_ht_add_ulong(uchan->ctx, &uctx->node); + cds_list_add_tail(&uctx->list, &uchan->ctx_list); + + if (!usess->active) { + goto end; + } + + ret = ust_app_add_ctx_channel_glb(usess, uchan, uctx); + if (ret < 0) { + goto error; + } +end: + DBG("Context UST %d added to channel %s", uctx->ctx.ctx, uchan->name); + + return 0; + +error: + free(uctx); +duplicate: + return ret; +} + +/* + * Add kernel context to tracer. + */ +int context_kernel_add(struct ltt_kernel_session *ksession, + const struct lttng_event_context *ctx, char *channel_name) +{ + int ret; + struct ltt_kernel_channel *kchan; + struct ltt_kernel_context *kctx; + + LTTNG_ASSERT(ksession); + LTTNG_ASSERT(ctx); + LTTNG_ASSERT(channel_name); + + kctx = trace_kernel_create_context(NULL); + if (!kctx) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + + /* Setup kernel context structure */ + switch (ctx->ctx) { + case LTTNG_EVENT_CONTEXT_PID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PID; + break; + case LTTNG_EVENT_CONTEXT_PROCNAME: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PROCNAME; + break; + case LTTNG_EVENT_CONTEXT_PRIO: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PRIO; + break; + case LTTNG_EVENT_CONTEXT_NICE: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_NICE; + break; + case LTTNG_EVENT_CONTEXT_VPID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VPID; + break; + case LTTNG_EVENT_CONTEXT_TID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_TID; + break; + case LTTNG_EVENT_CONTEXT_VTID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VTID; + break; + case LTTNG_EVENT_CONTEXT_PPID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PPID; + break; + case LTTNG_EVENT_CONTEXT_VPPID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VPPID; + break; + case LTTNG_EVENT_CONTEXT_HOSTNAME: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_HOSTNAME; + break; + case LTTNG_EVENT_CONTEXT_PERF_CPU_COUNTER: + case LTTNG_EVENT_CONTEXT_PERF_COUNTER: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PERF_CPU_COUNTER; + break; + case LTTNG_EVENT_CONTEXT_INTERRUPTIBLE: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_INTERRUPTIBLE; + break; + case LTTNG_EVENT_CONTEXT_PREEMPTIBLE: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PREEMPTIBLE; + break; + case LTTNG_EVENT_CONTEXT_NEED_RESCHEDULE: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_NEED_RESCHEDULE; + break; + case LTTNG_EVENT_CONTEXT_MIGRATABLE: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_MIGRATABLE; + break; + case LTTNG_EVENT_CONTEXT_CALLSTACK_KERNEL: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_CALLSTACK_KERNEL; + break; + case LTTNG_EVENT_CONTEXT_CALLSTACK_USER: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_CALLSTACK_USER; + break; + case LTTNG_EVENT_CONTEXT_CGROUP_NS: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_CGROUP_NS; + break; + case LTTNG_EVENT_CONTEXT_IPC_NS: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_IPC_NS; + break; + case LTTNG_EVENT_CONTEXT_MNT_NS: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_MNT_NS; + break; + case LTTNG_EVENT_CONTEXT_NET_NS: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_NET_NS; + break; + case LTTNG_EVENT_CONTEXT_PID_NS: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_PID_NS; + break; + case LTTNG_EVENT_CONTEXT_TIME_NS: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_TIME_NS; + break; + case LTTNG_EVENT_CONTEXT_USER_NS: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_USER_NS; + break; + case LTTNG_EVENT_CONTEXT_UTS_NS: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_UTS_NS; + break; + case LTTNG_EVENT_CONTEXT_UID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_UID; + break; + case LTTNG_EVENT_CONTEXT_EUID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_EUID; + break; + case LTTNG_EVENT_CONTEXT_SUID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_SUID; + break; + case LTTNG_EVENT_CONTEXT_GID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_GID; + break; + case LTTNG_EVENT_CONTEXT_EGID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_EGID; + break; + case LTTNG_EVENT_CONTEXT_SGID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_SGID; + break; + case LTTNG_EVENT_CONTEXT_VUID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VUID; + break; + case LTTNG_EVENT_CONTEXT_VEUID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VEUID; + break; + case LTTNG_EVENT_CONTEXT_VSUID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VSUID; + break; + case LTTNG_EVENT_CONTEXT_VGID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VGID; + break; + case LTTNG_EVENT_CONTEXT_VEGID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VEGID; + break; + case LTTNG_EVENT_CONTEXT_VSGID: + kctx->ctx.ctx = LTTNG_KERNEL_ABI_CONTEXT_VSGID; + break; + default: + ret = LTTNG_ERR_KERN_CONTEXT_FAIL; + goto error; + } + + kctx->ctx.u.perf_counter.type = ctx->u.perf_counter.type; + kctx->ctx.u.perf_counter.config = ctx->u.perf_counter.config; + strncpy(kctx->ctx.u.perf_counter.name, ctx->u.perf_counter.name, + LTTNG_SYMBOL_NAME_LEN); + kctx->ctx.u.perf_counter.name[LTTNG_SYMBOL_NAME_LEN - 1] = '\0'; + + if (*channel_name == '\0') { + ret = add_kctx_all_channels(ksession, kctx); + /* Ownership of kctx is transferred to the callee. */ + kctx = NULL; + if (ret != LTTNG_OK) { + goto error; + } + } else { + /* Get kernel channel */ + kchan = trace_kernel_get_channel_by_name(channel_name, ksession); + if (kchan == NULL) { + ret = LTTNG_ERR_KERN_CHAN_NOT_FOUND; + goto error; + } + + ret = add_kctx_to_channel(kctx, kchan); + /* Ownership of kctx is transferred to the callee. */ + kctx = NULL; + if (ret != LTTNG_OK) { + goto error; + } + } + + ret = LTTNG_OK; + +error: + if (kctx) { + trace_kernel_destroy_context(kctx); + } + return ret; +} + +/* + * Add UST context to tracer. + */ +int context_ust_add(struct ltt_ust_session *usess, + enum lttng_domain_type domain, + const struct lttng_event_context *ctx, + char *channel_name) +{ + int ret = LTTNG_OK; + struct lttng_ht_iter iter; + struct lttng_ht *chan_ht; + struct ltt_ust_channel *uchan = NULL; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(ctx); + LTTNG_ASSERT(channel_name); + + rcu_read_lock(); + + chan_ht = usess->domain_global.channels; + + /* Get UST channel if defined */ + if (channel_name[0] != '\0') { + uchan = trace_ust_find_channel_by_name(chan_ht, channel_name); + if (uchan == NULL) { + ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; + goto error; + } + } + + if (uchan) { + /* Add ctx to channel */ + ret = add_uctx_to_channel(usess, domain, uchan, ctx); + } else { + rcu_read_lock(); + /* Add ctx all events, all channels */ + cds_lfht_for_each_entry(chan_ht->ht, &iter.iter, uchan, node.node) { + ret = add_uctx_to_channel(usess, domain, uchan, ctx); + if (ret) { + ERR("Failed to add context to channel %s", + uchan->name); + continue; + } + } + rcu_read_unlock(); + } + + switch (ret) { + case LTTNG_ERR_UST_CONTEXT_EXIST: + break; + case -ENOMEM: + case -LTTNG_ERR_NOMEM: + ret = LTTNG_ERR_FATAL; + break; + case -EINVAL: + ret = LTTNG_ERR_UST_CONTEXT_INVAL; + break; + case -ENOSYS: + ret = LTTNG_ERR_UNKNOWN_DOMAIN; + break; + default: + if (ret != 0 && ret != LTTNG_OK) { + ret = ret > 0 ? ret : LTTNG_ERR_UNK; + } else { + ret = LTTNG_OK; + } + break; + } + +error: + rcu_read_unlock(); + return ret; +} diff --git a/src/bin/lttng-sessiond/dispatch.c b/src/bin/lttng-sessiond/dispatch.c deleted file mode 100644 index a767ccbdd..000000000 --- a/src/bin/lttng-sessiond/dispatch.c +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include -#include -#include - -#include "dispatch.h" -#include "ust-app.h" -#include "testpoint.h" -#include "fd-limit.h" -#include "health-sessiond.h" -#include "lttng-sessiond.h" -#include "thread.h" - -struct thread_notifiers { - struct ust_cmd_queue *ust_cmd_queue; - int apps_cmd_pipe_write_fd; - int apps_cmd_notify_pipe_write_fd; - int dispatch_thread_exit; -}; - -/* - * For each tracing session, update newly registered apps. The session list - * lock MUST be acquired before calling this. - */ -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(&the_ust_consumerd_state) == CONSUMER_ERROR) { - /* Stop the update process since the consumer is dead. */ - return; - } - - rcu_read_lock(); - LTTNG_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 event notifiers for the app. */ - ust_app_global_update_event_notifier_rules(app); - - /* For all tracing session(s) */ - cds_list_for_each_entry_safe(sess, stmp, &session_list->head, list) { - if (!session_get(sess)) { - continue; - } - session_lock(sess); - if (!sess->active || !sess->ust_session || - !sess->ust_session->active) { - goto unlock_session; - } - - ust_app_global_update(sess->ust_session, app); - unlock_session: - session_unlock(sess); - session_put(sess); - } - -unlock_rcu: - rcu_read_unlock(); -} - -/* - * Sanitize the wait queue of the dispatch registration thread meaning removing - * invalid nodes from it. This is to avoid memory leaks for the case the UST - * notify socket is never received. - */ -static void sanitize_wait_queue(struct ust_reg_wait_queue *wait_queue) -{ - int ret, nb_fd = 0, i; - unsigned int fd_added = 0; - struct lttng_poll_event events; - struct ust_reg_wait_node *wait_node = NULL, *tmp_wait_node; - - LTTNG_ASSERT(wait_queue); - - lttng_poll_init(&events); - - /* Just skip everything for an empty queue. */ - if (!wait_queue->count) { - goto end; - } - - ret = lttng_poll_create(&events, wait_queue->count, LTTNG_CLOEXEC); - if (ret < 0) { - goto error_create; - } - - cds_list_for_each_entry_safe(wait_node, tmp_wait_node, - &wait_queue->head, head) { - LTTNG_ASSERT(wait_node->app); - ret = lttng_poll_add(&events, wait_node->app->sock, - LPOLLHUP | LPOLLERR); - if (ret < 0) { - goto error; - } - - fd_added = 1; - } - - if (!fd_added) { - goto end; - } - - /* - * Poll but don't block so we can quickly identify the faulty events and - * clean them afterwards from the wait queue. - */ - ret = lttng_poll_wait(&events, 0); - if (ret < 0) { - goto error; - } - nb_fd = ret; - - for (i = 0; i < nb_fd; i++) { - /* Get faulty FD. */ - uint32_t revents = LTTNG_POLL_GETEV(&events, i); - int pollfd = LTTNG_POLL_GETFD(&events, i); - - cds_list_for_each_entry_safe(wait_node, tmp_wait_node, - &wait_queue->head, head) { - if (pollfd == wait_node->app->sock && - (revents & (LPOLLHUP | LPOLLERR))) { - cds_list_del(&wait_node->head); - wait_queue->count--; - ust_app_destroy(wait_node->app); - free(wait_node); - /* - * Silence warning of use-after-free in - * cds_list_for_each_entry_safe which uses - * __typeof__(*wait_node). - */ - wait_node = NULL; - break; - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - } - } - - if (nb_fd > 0) { - DBG("Wait queue sanitized, %d node were cleaned up", nb_fd); - } - -end: - lttng_poll_clean(&events); - return; - -error: - lttng_poll_clean(&events); -error_create: - ERR("Unable to sanitize wait queue"); - return; -} - -/* - * Send a socket to a thread This is called from the dispatch UST registration - * thread once all sockets are set for the application. - * - * The sock value can be invalid, we don't really care, the thread will handle - * it and make the necessary cleanup if so. - * - * On success, return 0 else a negative value being the errno message of the - * write(). - */ -static int send_socket_to_thread(int fd, int sock) -{ - ssize_t ret; - - /* - * It's possible that the FD is set as invalid with -1 concurrently just - * before calling this function being a shutdown state of the thread. - */ - if (fd < 0) { - ret = -EBADF; - goto error; - } - - ret = lttng_write(fd, &sock, sizeof(sock)); - if (ret < sizeof(sock)) { - PERROR("write apps pipe %d", fd); - if (ret < 0) { - ret = -errno; - } - goto error; - } - - /* All good. Don't send back the write positive ret value. */ - ret = 0; -error: - return (int) ret; -} - -static void cleanup_ust_dispatch_thread(void *data) -{ - free(data); -} - -/* - * Dispatch request from the registration threads to the application - * communication thread. - */ -static void *thread_dispatch_ust_registration(void *data) -{ - int ret, err = -1; - struct cds_wfcq_node *node; - struct ust_command *ust_cmd = NULL; - struct ust_reg_wait_node *wait_node = NULL, *tmp_wait_node; - struct ust_reg_wait_queue wait_queue = { - .count = 0, - }; - struct thread_notifiers *notifiers = data; - - rcu_register_thread(); - - health_register(the_health_sessiond, - HEALTH_SESSIOND_TYPE_APP_REG_DISPATCH); - - if (testpoint(sessiond_thread_app_reg_dispatch)) { - goto error_testpoint; - } - - health_code_update(); - - CDS_INIT_LIST_HEAD(&wait_queue.head); - - DBG("[thread] Dispatch UST command started"); - - for (;;) { - health_code_update(); - - /* Atomically prepare the queue futex */ - futex_nto1_prepare(¬ifiers->ust_cmd_queue->futex); - - if (CMM_LOAD_SHARED(notifiers->dispatch_thread_exit)) { - break; - } - - do { - struct ust_app *app = NULL; - ust_cmd = NULL; - - /* - * Make sure we don't have node(s) that have hung up before receiving - * the notify socket. This is to clean the list in order to avoid - * memory leaks from notify socket that are never seen. - */ - sanitize_wait_queue(&wait_queue); - - health_code_update(); - /* Dequeue command for registration */ - node = cds_wfcq_dequeue_blocking( - ¬ifiers->ust_cmd_queue->head, - ¬ifiers->ust_cmd_queue->tail); - if (node == NULL) { - DBG("Woken up but nothing in the UST command queue"); - /* Continue thread execution */ - break; - } - - ust_cmd = caa_container_of(node, struct ust_command, node); - - DBG("Dispatching UST registration pid:%d ppid:%d uid:%d" - " gid:%d sock:%d name:%s (version %d.%d)", - ust_cmd->reg_msg.pid, ust_cmd->reg_msg.ppid, - ust_cmd->reg_msg.uid, ust_cmd->reg_msg.gid, - ust_cmd->sock, ust_cmd->reg_msg.name, - ust_cmd->reg_msg.major, ust_cmd->reg_msg.minor); - - if (ust_cmd->reg_msg.type == LTTNG_UST_CTL_SOCKET_CMD) { - wait_node = zmalloc(sizeof(*wait_node)); - if (!wait_node) { - PERROR("zmalloc wait_node dispatch"); - ret = close(ust_cmd->sock); - if (ret < 0) { - PERROR("close ust sock dispatch %d", ust_cmd->sock); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - free(ust_cmd); - ust_cmd = NULL; - goto error; - } - CDS_INIT_LIST_HEAD(&wait_node->head); - - /* Create application object if socket is CMD. */ - wait_node->app = ust_app_create(&ust_cmd->reg_msg, - ust_cmd->sock); - if (!wait_node->app) { - ret = close(ust_cmd->sock); - if (ret < 0) { - PERROR("close ust sock dispatch %d", ust_cmd->sock); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - free(wait_node); - wait_node = NULL; - free(ust_cmd); - ust_cmd = NULL; - continue; - } - /* - * Add application to the wait queue so we can set the notify - * socket before putting this object in the global ht. - */ - cds_list_add(&wait_node->head, &wait_queue.head); - wait_queue.count++; - - free(ust_cmd); - ust_cmd = NULL; - /* - * We have to continue here since we don't have the notify - * socket and the application MUST be added to the hash table - * only at that moment. - */ - continue; - } else { - /* - * Look for the application in the local wait queue and set the - * notify socket if found. - */ - cds_list_for_each_entry_safe(wait_node, tmp_wait_node, - &wait_queue.head, head) { - health_code_update(); - if (wait_node->app->pid == ust_cmd->reg_msg.pid) { - wait_node->app->notify_sock = ust_cmd->sock; - cds_list_del(&wait_node->head); - wait_queue.count--; - app = wait_node->app; - free(wait_node); - wait_node = NULL; - DBG3("UST app notify socket %d is set", ust_cmd->sock); - break; - } - } - - /* - * With no application at this stage the received socket is - * basically useless so close it before we free the cmd data - * structure for good. - */ - if (!app) { - ret = close(ust_cmd->sock); - if (ret < 0) { - PERROR("close ust sock dispatch %d", ust_cmd->sock); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - } - free(ust_cmd); - ust_cmd = NULL; - } - - if (app) { - /* - * @session_lock_list - * - * Lock the global session list so from the register up to the - * registration done message, no thread can see the application - * and change its state. - */ - session_lock_list(); - rcu_read_lock(); - - /* - * Add application to the global hash table. This needs to be - * done before the update to the UST registry can locate the - * application. - */ - ust_app_add(app); - - /* Set app version. This call will print an error if needed. */ - (void) ust_app_version(app); - - (void) ust_app_setup_event_notifier_group(app); - - /* Send notify socket through the notify pipe. */ - ret = send_socket_to_thread( - notifiers->apps_cmd_notify_pipe_write_fd, - app->notify_sock); - if (ret < 0) { - rcu_read_unlock(); - session_unlock_list(); - /* - * No notify thread, stop the UST tracing. However, this is - * not an internal error of the this thread thus setting - * the health error code to a normal exit. - */ - err = 0; - goto error; - } - - /* - * Update newly registered application with the tracing - * registry info already enabled information. - */ - update_ust_app(app->sock); - - /* - * Don't care about return value. Let the manage apps threads - * handle app unregistration upon socket close. - */ - (void) ust_app_register_done(app); - - /* - * Even if the application socket has been closed, send the app - * to the thread and unregistration will take place at that - * place. - */ - ret = send_socket_to_thread( - notifiers->apps_cmd_pipe_write_fd, - app->sock); - if (ret < 0) { - rcu_read_unlock(); - session_unlock_list(); - /* - * No apps. thread, stop the UST tracing. However, this is - * not an internal error of the this thread thus setting - * the health error code to a normal exit. - */ - err = 0; - goto error; - } - - rcu_read_unlock(); - session_unlock_list(); - } - } while (node != NULL); - - health_poll_entry(); - /* Futex wait on queue. Blocking call on futex() */ - futex_nto1_wait(¬ifiers->ust_cmd_queue->futex); - health_poll_exit(); - } - /* Normal exit, no error */ - err = 0; - -error: - /* Clean up wait queue. */ - cds_list_for_each_entry_safe(wait_node, tmp_wait_node, - &wait_queue.head, head) { - cds_list_del(&wait_node->head); - wait_queue.count--; - free(wait_node); - } - - /* Empty command queue. */ - for (;;) { - /* Dequeue command for registration */ - node = cds_wfcq_dequeue_blocking( - ¬ifiers->ust_cmd_queue->head, - ¬ifiers->ust_cmd_queue->tail); - if (node == NULL) { - break; - } - ust_cmd = caa_container_of(node, struct ust_command, node); - ret = close(ust_cmd->sock); - if (ret < 0) { - PERROR("close ust sock exit dispatch %d", ust_cmd->sock); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - free(ust_cmd); - } - -error_testpoint: - DBG("Dispatch thread dying"); - if (err) { - health_error(); - ERR("Health error occurred in %s", __func__); - } - health_unregister(the_health_sessiond); - rcu_unregister_thread(); - return NULL; -} - -static bool shutdown_ust_dispatch_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - - CMM_STORE_SHARED(notifiers->dispatch_thread_exit, 1); - futex_nto1_wake(¬ifiers->ust_cmd_queue->futex); - return true; -} - -bool launch_ust_dispatch_thread(struct ust_cmd_queue *cmd_queue, - int apps_cmd_pipe_write_fd, - int apps_cmd_notify_pipe_write_fd) -{ - struct lttng_thread *thread; - struct thread_notifiers *notifiers; - - notifiers = zmalloc(sizeof(*notifiers)); - if (!notifiers) { - goto error; - } - notifiers->ust_cmd_queue = cmd_queue; - notifiers->apps_cmd_pipe_write_fd = apps_cmd_pipe_write_fd; - notifiers->apps_cmd_notify_pipe_write_fd = apps_cmd_notify_pipe_write_fd; - - thread = lttng_thread_create("UST registration dispatch", - thread_dispatch_ust_registration, - shutdown_ust_dispatch_thread, - cleanup_ust_dispatch_thread, - notifiers); - if (!thread) { - goto error; - } - lttng_thread_put(thread); - return true; -error: - free(notifiers); - return false; -} diff --git a/src/bin/lttng-sessiond/dispatch.cpp b/src/bin/lttng-sessiond/dispatch.cpp new file mode 100644 index 000000000..b4092eb44 --- /dev/null +++ b/src/bin/lttng-sessiond/dispatch.cpp @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include + +#include "dispatch.h" +#include "ust-app.h" +#include "testpoint.h" +#include "fd-limit.h" +#include "health-sessiond.h" +#include "lttng-sessiond.h" +#include "thread.h" + +struct thread_notifiers { + struct ust_cmd_queue *ust_cmd_queue; + int apps_cmd_pipe_write_fd; + int apps_cmd_notify_pipe_write_fd; + int dispatch_thread_exit; +}; + +/* + * For each tracing session, update newly registered apps. The session list + * lock MUST be acquired before calling this. + */ +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(&the_ust_consumerd_state) == CONSUMER_ERROR) { + /* Stop the update process since the consumer is dead. */ + return; + } + + rcu_read_lock(); + LTTNG_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 event notifiers for the app. */ + ust_app_global_update_event_notifier_rules(app); + + /* For all tracing session(s) */ + cds_list_for_each_entry_safe(sess, stmp, &session_list->head, list) { + if (!session_get(sess)) { + continue; + } + session_lock(sess); + if (!sess->active || !sess->ust_session || + !sess->ust_session->active) { + goto unlock_session; + } + + ust_app_global_update(sess->ust_session, app); + unlock_session: + session_unlock(sess); + session_put(sess); + } + +unlock_rcu: + rcu_read_unlock(); +} + +/* + * Sanitize the wait queue of the dispatch registration thread meaning removing + * invalid nodes from it. This is to avoid memory leaks for the case the UST + * notify socket is never received. + */ +static void sanitize_wait_queue(struct ust_reg_wait_queue *wait_queue) +{ + int ret, nb_fd = 0, i; + unsigned int fd_added = 0; + struct lttng_poll_event events; + struct ust_reg_wait_node *wait_node = NULL, *tmp_wait_node; + + LTTNG_ASSERT(wait_queue); + + lttng_poll_init(&events); + + /* Just skip everything for an empty queue. */ + if (!wait_queue->count) { + goto end; + } + + ret = lttng_poll_create(&events, wait_queue->count, LTTNG_CLOEXEC); + if (ret < 0) { + goto error_create; + } + + cds_list_for_each_entry_safe(wait_node, tmp_wait_node, + &wait_queue->head, head) { + LTTNG_ASSERT(wait_node->app); + ret = lttng_poll_add(&events, wait_node->app->sock, + LPOLLHUP | LPOLLERR); + if (ret < 0) { + goto error; + } + + fd_added = 1; + } + + if (!fd_added) { + goto end; + } + + /* + * Poll but don't block so we can quickly identify the faulty events and + * clean them afterwards from the wait queue. + */ + ret = lttng_poll_wait(&events, 0); + if (ret < 0) { + goto error; + } + nb_fd = ret; + + for (i = 0; i < nb_fd; i++) { + /* Get faulty FD. */ + uint32_t revents = LTTNG_POLL_GETEV(&events, i); + int pollfd = LTTNG_POLL_GETFD(&events, i); + + cds_list_for_each_entry_safe(wait_node, tmp_wait_node, + &wait_queue->head, head) { + if (pollfd == wait_node->app->sock && + (revents & (LPOLLHUP | LPOLLERR))) { + cds_list_del(&wait_node->head); + wait_queue->count--; + ust_app_destroy(wait_node->app); + free(wait_node); + /* + * Silence warning of use-after-free in + * cds_list_for_each_entry_safe which uses + * __typeof__(*wait_node). + */ + wait_node = NULL; + break; + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + } + } + + if (nb_fd > 0) { + DBG("Wait queue sanitized, %d node were cleaned up", nb_fd); + } + +end: + lttng_poll_clean(&events); + return; + +error: + lttng_poll_clean(&events); +error_create: + ERR("Unable to sanitize wait queue"); + return; +} + +/* + * Send a socket to a thread This is called from the dispatch UST registration + * thread once all sockets are set for the application. + * + * The sock value can be invalid, we don't really care, the thread will handle + * it and make the necessary cleanup if so. + * + * On success, return 0 else a negative value being the errno message of the + * write(). + */ +static int send_socket_to_thread(int fd, int sock) +{ + ssize_t ret; + + /* + * It's possible that the FD is set as invalid with -1 concurrently just + * before calling this function being a shutdown state of the thread. + */ + if (fd < 0) { + ret = -EBADF; + goto error; + } + + ret = lttng_write(fd, &sock, sizeof(sock)); + if (ret < sizeof(sock)) { + PERROR("write apps pipe %d", fd); + if (ret < 0) { + ret = -errno; + } + goto error; + } + + /* All good. Don't send back the write positive ret value. */ + ret = 0; +error: + return (int) ret; +} + +static void cleanup_ust_dispatch_thread(void *data) +{ + free(data); +} + +/* + * Dispatch request from the registration threads to the application + * communication thread. + */ +static void *thread_dispatch_ust_registration(void *data) +{ + int ret, err = -1; + struct cds_wfcq_node *node; + struct ust_command *ust_cmd = NULL; + struct ust_reg_wait_node *wait_node = NULL, *tmp_wait_node; + struct ust_reg_wait_queue wait_queue = { + .count = 0, + }; + struct thread_notifiers *notifiers = (thread_notifiers *) data; + + rcu_register_thread(); + + health_register(the_health_sessiond, + HEALTH_SESSIOND_TYPE_APP_REG_DISPATCH); + + if (testpoint(sessiond_thread_app_reg_dispatch)) { + goto error_testpoint; + } + + health_code_update(); + + CDS_INIT_LIST_HEAD(&wait_queue.head); + + DBG("[thread] Dispatch UST command started"); + + for (;;) { + health_code_update(); + + /* Atomically prepare the queue futex */ + futex_nto1_prepare(¬ifiers->ust_cmd_queue->futex); + + if (CMM_LOAD_SHARED(notifiers->dispatch_thread_exit)) { + break; + } + + do { + struct ust_app *app = NULL; + ust_cmd = NULL; + + /* + * Make sure we don't have node(s) that have hung up before receiving + * the notify socket. This is to clean the list in order to avoid + * memory leaks from notify socket that are never seen. + */ + sanitize_wait_queue(&wait_queue); + + health_code_update(); + /* Dequeue command for registration */ + node = cds_wfcq_dequeue_blocking( + ¬ifiers->ust_cmd_queue->head, + ¬ifiers->ust_cmd_queue->tail); + if (node == NULL) { + DBG("Woken up but nothing in the UST command queue"); + /* Continue thread execution */ + break; + } + + ust_cmd = caa_container_of(node, struct ust_command, node); + + DBG("Dispatching UST registration pid:%d ppid:%d uid:%d" + " gid:%d sock:%d name:%s (version %d.%d)", + ust_cmd->reg_msg.pid, ust_cmd->reg_msg.ppid, + ust_cmd->reg_msg.uid, ust_cmd->reg_msg.gid, + ust_cmd->sock, ust_cmd->reg_msg.name, + ust_cmd->reg_msg.major, ust_cmd->reg_msg.minor); + + if (ust_cmd->reg_msg.type == LTTNG_UST_CTL_SOCKET_CMD) { + wait_node = (ust_reg_wait_node *) zmalloc(sizeof(*wait_node)); + if (!wait_node) { + PERROR("zmalloc wait_node dispatch"); + ret = close(ust_cmd->sock); + if (ret < 0) { + PERROR("close ust sock dispatch %d", ust_cmd->sock); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + free(ust_cmd); + ust_cmd = NULL; + goto error; + } + CDS_INIT_LIST_HEAD(&wait_node->head); + + /* Create application object if socket is CMD. */ + wait_node->app = ust_app_create(&ust_cmd->reg_msg, + ust_cmd->sock); + if (!wait_node->app) { + ret = close(ust_cmd->sock); + if (ret < 0) { + PERROR("close ust sock dispatch %d", ust_cmd->sock); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + free(wait_node); + wait_node = NULL; + free(ust_cmd); + ust_cmd = NULL; + continue; + } + /* + * Add application to the wait queue so we can set the notify + * socket before putting this object in the global ht. + */ + cds_list_add(&wait_node->head, &wait_queue.head); + wait_queue.count++; + + free(ust_cmd); + ust_cmd = NULL; + /* + * We have to continue here since we don't have the notify + * socket and the application MUST be added to the hash table + * only at that moment. + */ + continue; + } else { + /* + * Look for the application in the local wait queue and set the + * notify socket if found. + */ + cds_list_for_each_entry_safe(wait_node, tmp_wait_node, + &wait_queue.head, head) { + health_code_update(); + if (wait_node->app->pid == ust_cmd->reg_msg.pid) { + wait_node->app->notify_sock = ust_cmd->sock; + cds_list_del(&wait_node->head); + wait_queue.count--; + app = wait_node->app; + free(wait_node); + wait_node = NULL; + DBG3("UST app notify socket %d is set", ust_cmd->sock); + break; + } + } + + /* + * With no application at this stage the received socket is + * basically useless so close it before we free the cmd data + * structure for good. + */ + if (!app) { + ret = close(ust_cmd->sock); + if (ret < 0) { + PERROR("close ust sock dispatch %d", ust_cmd->sock); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + } + free(ust_cmd); + ust_cmd = NULL; + } + + if (app) { + /* + * @session_lock_list + * + * Lock the global session list so from the register up to the + * registration done message, no thread can see the application + * and change its state. + */ + session_lock_list(); + rcu_read_lock(); + + /* + * Add application to the global hash table. This needs to be + * done before the update to the UST registry can locate the + * application. + */ + ust_app_add(app); + + /* Set app version. This call will print an error if needed. */ + (void) ust_app_version(app); + + (void) ust_app_setup_event_notifier_group(app); + + /* Send notify socket through the notify pipe. */ + ret = send_socket_to_thread( + notifiers->apps_cmd_notify_pipe_write_fd, + app->notify_sock); + if (ret < 0) { + rcu_read_unlock(); + session_unlock_list(); + /* + * No notify thread, stop the UST tracing. However, this is + * not an internal error of the this thread thus setting + * the health error code to a normal exit. + */ + err = 0; + goto error; + } + + /* + * Update newly registered application with the tracing + * registry info already enabled information. + */ + update_ust_app(app->sock); + + /* + * Don't care about return value. Let the manage apps threads + * handle app unregistration upon socket close. + */ + (void) ust_app_register_done(app); + + /* + * Even if the application socket has been closed, send the app + * to the thread and unregistration will take place at that + * place. + */ + ret = send_socket_to_thread( + notifiers->apps_cmd_pipe_write_fd, + app->sock); + if (ret < 0) { + rcu_read_unlock(); + session_unlock_list(); + /* + * No apps. thread, stop the UST tracing. However, this is + * not an internal error of the this thread thus setting + * the health error code to a normal exit. + */ + err = 0; + goto error; + } + + rcu_read_unlock(); + session_unlock_list(); + } + } while (node != NULL); + + health_poll_entry(); + /* Futex wait on queue. Blocking call on futex() */ + futex_nto1_wait(¬ifiers->ust_cmd_queue->futex); + health_poll_exit(); + } + /* Normal exit, no error */ + err = 0; + +error: + /* Clean up wait queue. */ + cds_list_for_each_entry_safe(wait_node, tmp_wait_node, + &wait_queue.head, head) { + cds_list_del(&wait_node->head); + wait_queue.count--; + free(wait_node); + } + + /* Empty command queue. */ + for (;;) { + /* Dequeue command for registration */ + node = cds_wfcq_dequeue_blocking( + ¬ifiers->ust_cmd_queue->head, + ¬ifiers->ust_cmd_queue->tail); + if (node == NULL) { + break; + } + ust_cmd = caa_container_of(node, struct ust_command, node); + ret = close(ust_cmd->sock); + if (ret < 0) { + PERROR("close ust sock exit dispatch %d", ust_cmd->sock); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + free(ust_cmd); + } + +error_testpoint: + DBG("Dispatch thread dying"); + if (err) { + health_error(); + ERR("Health error occurred in %s", __func__); + } + health_unregister(the_health_sessiond); + rcu_unregister_thread(); + return NULL; +} + +static bool shutdown_ust_dispatch_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + + CMM_STORE_SHARED(notifiers->dispatch_thread_exit, 1); + futex_nto1_wake(¬ifiers->ust_cmd_queue->futex); + return true; +} + +bool launch_ust_dispatch_thread(struct ust_cmd_queue *cmd_queue, + int apps_cmd_pipe_write_fd, + int apps_cmd_notify_pipe_write_fd) +{ + struct lttng_thread *thread; + struct thread_notifiers *notifiers; + + notifiers = (thread_notifiers *) zmalloc(sizeof(*notifiers)); + if (!notifiers) { + goto error; + } + notifiers->ust_cmd_queue = cmd_queue; + notifiers->apps_cmd_pipe_write_fd = apps_cmd_pipe_write_fd; + notifiers->apps_cmd_notify_pipe_write_fd = apps_cmd_notify_pipe_write_fd; + + thread = lttng_thread_create("UST registration dispatch", + thread_dispatch_ust_registration, + shutdown_ust_dispatch_thread, + cleanup_ust_dispatch_thread, + notifiers); + if (!thread) { + goto error; + } + lttng_thread_put(thread); + return true; +error: + free(notifiers); + return false; +} diff --git a/src/bin/lttng-sessiond/event-notifier-error-accounting.c b/src/bin/lttng-sessiond/event-notifier-error-accounting.c deleted file mode 100644 index 1488d801c..000000000 --- a/src/bin/lttng-sessiond/event-notifier-error-accounting.c +++ /dev/null @@ -1,1368 +0,0 @@ -/* - * Copyright (C) 2020 Francis Deslauriers - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "event-notifier-error-accounting.h" -#include "lttng-ust-error.h" -#include "ust-app.h" - -#define ERROR_COUNTER_INDEX_HT_INITIAL_SIZE 16 - -struct index_ht_entry { - struct lttng_ht_node_u64 node; - uint64_t error_counter_index; - struct rcu_head rcu_head; -}; - -struct ust_error_accounting_entry { - uid_t uid; - struct urcu_ref ref; - struct lttng_ht_node_u64 node; - struct rcu_head rcu_head; - struct lttng_ust_ctl_daemon_counter *daemon_counter; - /* - * Those `lttng_ust_abi_object_data` are anonymous handles to the - * counters objects. - * They are only used to be duplicated for each new applications of the - * user. To destroy them, call with the `sock` parameter set to -1. - * e.g. `lttng_ust_ctl_release_object(-1, data)`; - */ - struct lttng_ust_abi_object_data *counter; - struct lttng_ust_abi_object_data **cpu_counters; - int nr_counter_cpu_fds; -}; - -struct kernel_error_accounting_entry { - int error_counter_fd; -}; - -static struct kernel_error_accounting_entry kernel_error_accounting_entry; - -/* Hashtable mapping uid to error_account_entry. */ -static struct lttng_ht *error_counter_uid_ht; - -struct error_accounting_state { - struct lttng_index_allocator *index_allocator; - /* Hashtable mapping event notifier token to index_ht_entry. */ - struct lttng_ht *indices_ht; - uint64_t number_indices; -}; - -static struct error_accounting_state ust_state; -static struct error_accounting_state kernel_state; - -static inline void get_trigger_info_for_log(const struct lttng_trigger *trigger, - const char **trigger_name, - uid_t *trigger_owner_uid) -{ - enum lttng_trigger_status trigger_status; - - trigger_status = lttng_trigger_get_name(trigger, trigger_name); - switch (trigger_status) { - case LTTNG_TRIGGER_STATUS_OK: - break; - case LTTNG_TRIGGER_STATUS_UNSET: - *trigger_name = "(anonymous)"; - break; - default: - abort(); - } - - trigger_status = lttng_trigger_get_owner_uid(trigger, - trigger_owner_uid); - LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); -} - -static inline -const char *error_accounting_status_str( - enum event_notifier_error_accounting_status status) -{ - switch (status) { - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK: - return "OK"; - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR: - return "ERROR"; - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOT_FOUND: - return "NOT_FOUND"; - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM: - return "NOMEM"; - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NO_INDEX_AVAILABLE: - return "NO_INDEX_AVAILABLE"; - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD: - return "APP_DEAD"; - default: - abort(); - } -} - -#ifdef HAVE_LIBLTTNG_UST_CTL -struct event_notifier_counter { - pthread_mutex_t lock; - long count; -}; - -static struct event_notifier_counter the_event_notifier_counter; - -static void free_ust_error_accounting_entry(struct rcu_head *head) -{ - int i; - struct ust_error_accounting_entry *entry = - caa_container_of(head, typeof(*entry), rcu_head); - - for (i = 0; i < entry->nr_counter_cpu_fds; i++) { - lttng_ust_ctl_release_object(-1, entry->cpu_counters[i]); - free(entry->cpu_counters[i]); - } - - free(entry->cpu_counters); - - lttng_ust_ctl_release_object(-1, entry->counter); - free(entry->counter); - - lttng_ust_ctl_destroy_counter(entry->daemon_counter); - - free(entry); -} - -static -bool ust_error_accounting_entry_get(struct ust_error_accounting_entry *entry) -{ - return urcu_ref_get_unless_zero(&entry->ref); -} - -static -void ust_error_accounting_entry_release(struct urcu_ref *entry_ref) -{ - struct ust_error_accounting_entry *entry = - container_of(entry_ref, typeof(*entry), ref); - - rcu_read_lock(); - cds_lfht_del(error_counter_uid_ht->ht, &entry->node.node); - call_rcu(&entry->rcu_head, free_ust_error_accounting_entry); - rcu_read_unlock(); -} - - -static -void ust_error_accounting_entry_put(struct ust_error_accounting_entry *entry) -{ - if (!entry) { - return; - } - - urcu_ref_put(&entry->ref, ust_error_accounting_entry_release); -} - -/* - * Put one reference to every UID entries. - */ -static -void put_ref_all_ust_error_accounting_entry(void) -{ - struct lttng_ht_iter iter; - struct ust_error_accounting_entry *uid_entry; - - ASSERT_LOCKED(the_event_notifier_counter.lock); - - rcu_read_lock(); - cds_lfht_for_each_entry(error_counter_uid_ht->ht, &iter.iter, - uid_entry, node.node) { - ust_error_accounting_entry_put(uid_entry); - } - - rcu_read_unlock(); -} - -/* - * Get one reference to every UID entries. - */ -static -void get_ref_all_ust_error_accounting_entry(void) -{ - struct lttng_ht_iter iter; - struct ust_error_accounting_entry *uid_entry; - - ASSERT_LOCKED(the_event_notifier_counter.lock); - - rcu_read_lock(); - cds_lfht_for_each_entry(error_counter_uid_ht->ht, &iter.iter, - uid_entry, node.node) { - ust_error_accounting_entry_get(uid_entry); - } - - rcu_read_unlock(); -} - -#endif /* HAVE_LIBLTTNG_UST_CTL */ - -static -enum event_notifier_error_accounting_status -init_error_accounting_state(struct error_accounting_state *state, - uint64_t index_count) -{ - enum event_notifier_error_accounting_status status; - - LTTNG_ASSERT(state); - - state->number_indices = index_count; - - state->index_allocator = lttng_index_allocator_create(index_count); - if (!state->index_allocator) { - ERR("Failed to allocate event notifier error counter index allocator"); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; - goto end; - } - - state->indices_ht = lttng_ht_new(ERROR_COUNTER_INDEX_HT_INITIAL_SIZE, - LTTNG_HT_TYPE_U64); - if (!state->indices_ht) { - ERR("Failed to allocate error counter indices hash table"); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; - goto error_indices_ht; - } - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; - goto end; - -error_indices_ht: - lttng_index_allocator_destroy(state->index_allocator); - state->index_allocator = NULL; -end: - return status; -} - -static -void fini_error_accounting_state(struct error_accounting_state *state) -{ - LTTNG_ASSERT(state); - - /* - * Will assert if some error counter indices were not released (an - * internal error). - */ - lttng_ht_destroy(state->indices_ht); - lttng_index_allocator_destroy(state->index_allocator); -} - -enum event_notifier_error_accounting_status -event_notifier_error_accounting_init(uint64_t buffer_size_kernel, - uint64_t buffer_size_ust) -{ - enum event_notifier_error_accounting_status status; - - status = init_error_accounting_state(&kernel_state, buffer_size_kernel); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - ERR("Failed to initialize kernel event notifier accounting state: status = %s", - error_accounting_status_str(status)); - goto end; - } - - status = init_error_accounting_state(&ust_state, buffer_size_ust); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - ERR("Failed to initialize UST event notifier accounting state: status = %s", - error_accounting_status_str(status)); - goto error_ust_state; - } - - error_counter_uid_ht = lttng_ht_new( - ERROR_COUNTER_INDEX_HT_INITIAL_SIZE, LTTNG_HT_TYPE_U64); - if (!error_counter_uid_ht) { - ERR("Failed to allocate UID to error counter accountant hash table"); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; - goto error_uid_ht; - } - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; - goto end; - -error_uid_ht: - fini_error_accounting_state(&ust_state); -error_ust_state: - fini_error_accounting_state(&kernel_state); -end: - return status; -} - -/* - * Return the error counteur index associated to this event notifier tracer - * token. Returns _STATUS_OK if found and _STATUS_NOT_FOUND otherwise. - */ -static -enum event_notifier_error_accounting_status get_error_counter_index_for_token( - struct error_accounting_state *state, uint64_t tracer_token, - uint64_t *error_counter_index) -{ - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - const struct index_ht_entry *index_entry; - enum event_notifier_error_accounting_status status; - - rcu_read_lock(); - lttng_ht_lookup(state->indices_ht, &tracer_token, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (node) { - index_entry = caa_container_of( - node, const struct index_ht_entry, node); - *error_counter_index = index_entry->error_counter_index; - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; - } else { - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOT_FOUND; - } - - rcu_read_unlock(); - return status; -} - -#ifdef HAVE_LIBLTTNG_UST_CTL -/* - * Find the entry for this app's UID, the caller acquires a reference if the - * entry is found. - */ -static -struct ust_error_accounting_entry *ust_error_accounting_entry_find( - struct lttng_ht *uid_ht, const struct ust_app *app) -{ - struct ust_error_accounting_entry *entry; - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - uint64_t key = app->uid; - - lttng_ht_lookup(uid_ht, &key, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if(node == NULL) { - entry = NULL; - } else { - bool got_ref; - - entry = caa_container_of(node, - struct ust_error_accounting_entry, node); - - got_ref = ust_error_accounting_entry_get(entry); - if (!got_ref) { - entry = NULL; - } - } - - return entry; -} - -/* - * Create the entry for this app's UID, the caller acquires a reference to the - * entry, - */ -static -struct ust_error_accounting_entry *ust_error_accounting_entry_create( - struct lttng_ht *uid_ht, const struct ust_app *app) -{ - int i, ret, *cpu_counter_fds = NULL; - struct lttng_ust_ctl_daemon_counter *daemon_counter; - struct lttng_ust_abi_object_data *counter, **cpu_counters; - struct ust_error_accounting_entry *entry = NULL; - const struct lttng_ust_ctl_counter_dimension dimension = { - .size = ust_state.number_indices, - .has_underflow = false, - .has_overflow = false, - }; - - if (!ust_app_supports_counters(app)) { - DBG("Refusing to create accounting entry for application (unsupported feature): app name = '%s', app ppid = %d", - app->name, (int) app->ppid); - goto error; - } - - entry = zmalloc(sizeof(struct ust_error_accounting_entry)); - if (!entry) { - PERROR("Failed to allocate event notifier error acounting entry") - goto error; - } - - urcu_ref_init(&entry->ref); - entry->uid = app->uid; - entry->nr_counter_cpu_fds = lttng_ust_ctl_get_nr_cpu_per_counter(); - - cpu_counter_fds = zmalloc(entry->nr_counter_cpu_fds * sizeof(*cpu_counter_fds)); - if (!cpu_counter_fds) { - PERROR("Failed to allocate event notifier error counter file descriptors array: application uid = %d, application name = '%s', pid = %d, allocation size = %zu", - (int) app->uid, app->name, (int) app->pid, - entry->nr_counter_cpu_fds * sizeof(*cpu_counter_fds)); - goto error_counter_cpu_fds_alloc; - } - - /* Initialize to an invalid fd value to closes fds in case of error. */ - for (i = 0; i < entry->nr_counter_cpu_fds; i++) { - cpu_counter_fds[i] = -1; - } - - cpu_counters = zmalloc(entry->nr_counter_cpu_fds * sizeof(struct lttng_ust_abi_object_data *)); - if (!cpu_counters) { - PERROR("Failed to allocate event notifier error counter lttng_ust_abi_object_data array: application uid = %d, application name = '%s', pid = %d, allocation size = %zu", - (int) app->uid, app->name, (int) app->pid, - entry->nr_counter_cpu_fds * sizeof(struct lttng_ust_abi_object_data *)); - goto error_counter_cpus_alloc; - } - - for (i = 0; i < entry->nr_counter_cpu_fds; i++) { - cpu_counter_fds[i] = shm_create_anonymous("event-notifier-error-accounting"); - if (cpu_counter_fds[i] == -1) { - ERR("Failed to create event notifier error accounting shared memory for application user: application uid = %d, pid = %d, application name = '%s'", - (int) app->uid, (int) app->pid, app->name); - goto error_shm_alloc; - } - } - - /* - * Ownership of the file descriptors transferred to the ustctl object. - */ - daemon_counter = lttng_ust_ctl_create_counter(1, &dimension, 0, -1, - entry->nr_counter_cpu_fds, cpu_counter_fds, - LTTNG_UST_CTL_COUNTER_BITNESS_32, - LTTNG_UST_CTL_COUNTER_ARITHMETIC_MODULAR, - LTTNG_UST_CTL_COUNTER_ALLOC_PER_CPU, - false); - if (!daemon_counter) { - goto error_create_daemon_counter; - } - - ret = lttng_ust_ctl_create_counter_data(daemon_counter, &counter); - if (ret) { - ERR("Failed to create userspace tracer counter data for application user: uid = %d, pid = %d, application name = '%s'", - (int) app->uid, (int) app->pid, app->name); - goto error_create_counter_data; - } - - for (i = 0; i < entry->nr_counter_cpu_fds; i++) { - ret = lttng_ust_ctl_create_counter_cpu_data(daemon_counter, i, - &cpu_counters[i]); - if (ret) { - ERR("Failed to create userspace tracer counter cpu data for application user: uid = %d, pid = %d, application name = '%s'", - (int) app->uid, (int) app->pid, - app->name); - goto error_create_counter_cpu_data; - } - } - - entry->daemon_counter = daemon_counter; - entry->counter = counter; - entry->cpu_counters = cpu_counters; - - lttng_ht_node_init_u64(&entry->node, entry->uid); - lttng_ht_add_unique_u64(error_counter_uid_ht, &entry->node); - - goto end; - -error_create_counter_cpu_data: - /* Teardown any allocated cpu counters. */ - for (i = 0; i < entry->nr_counter_cpu_fds; i++) { - if (!cpu_counters[i]) { - /* - * Early-exit when error occurred before all cpu - * counters could be initialized. - */ - break; - } - - lttng_ust_ctl_release_object(-1, cpu_counters[i]); - free(cpu_counters[i]); - } - - lttng_ust_ctl_release_object(-1, entry->counter); - free(entry->counter); -error_create_counter_data: - lttng_ust_ctl_destroy_counter(daemon_counter); -error_create_daemon_counter: -error_shm_alloc: - /* Error occurred before per-cpu SHMs were handed-off to ustctl. */ - if (cpu_counter_fds) { - for (i = 0; i < entry->nr_counter_cpu_fds; i++) { - if (cpu_counter_fds[i] < 0) { - /* - * Early-exit when error occurred before all cpu - * counter shm fds could be initialized. - */ - break; - } - - ret = close(cpu_counter_fds[i]); - if (ret) { - PERROR("Failed to close error counter per-CPU shm file descriptor: fd = %d", - cpu_counter_fds[i]); - } - } - } - - free(cpu_counters); -error_counter_cpus_alloc: -error_counter_cpu_fds_alloc: - free(entry); -error: - entry = NULL; -end: - free(cpu_counter_fds); - return entry; -} - -static -enum event_notifier_error_accounting_status send_counter_data_to_ust( - struct ust_app *app, - struct lttng_ust_abi_object_data *new_counter) -{ - int ret; - enum event_notifier_error_accounting_status status; - - /* Attach counter to trigger group. */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_send_counter_data_to_ust(app->sock, - app->event_notifier_group.object->handle, new_counter); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { - ERR("Failed to send counter data to application: application name = '%s', pid = %d, ret = %d", - app->name, app->pid, ret); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - } else { - DBG3("Failed to send counter data to application (application is dead): application name = '%s', pid = %d, ret = %d", - app->name, app->pid, ret); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD; - } - - goto end; - } - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; -end: - return status; -} - -static -enum event_notifier_error_accounting_status send_counter_cpu_data_to_ust( - struct ust_app *app, - struct lttng_ust_abi_object_data *counter, - struct lttng_ust_abi_object_data *counter_cpu) -{ - int ret; - enum event_notifier_error_accounting_status status; - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_send_counter_cpu_data_to_ust(app->sock, - counter, counter_cpu); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { - ERR("Failed to send counter CPU data to application: application name = '%s', pid = %d, ret = %d", - app->name, app->pid, ret); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - } else { - DBG3("Failed to send counter CPU data to application: application name = '%s', pid = %d, ret = %d", - app->name, app->pid, ret); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD; - } - - goto end; - } - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; -end: - return status; -} - -enum event_notifier_error_accounting_status -event_notifier_error_accounting_register_app(struct ust_app *app) -{ - int ret; - uint64_t i; - struct lttng_ust_abi_object_data *new_counter; - struct ust_error_accounting_entry *entry; - enum event_notifier_error_accounting_status status; - struct lttng_ust_abi_object_data **cpu_counters; - - if (!ust_app_supports_counters(app)) { - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_UNSUPPORTED; - goto end; - } - - /* - * Check if we already have a error counter for the user id of this - * app. If not, create one. - */ - rcu_read_lock(); - entry = ust_error_accounting_entry_find(error_counter_uid_ht, app); - if (entry == NULL) { - /* - * Take the event notifier counter lock before creating the new - * entry to ensure that no event notifier is registered between - * the the entry creation and event notifier count check. - */ - pthread_mutex_lock(&the_event_notifier_counter.lock); - - entry = ust_error_accounting_entry_create(error_counter_uid_ht, - app); - if (!entry) { - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - pthread_mutex_unlock(&the_event_notifier_counter.lock); - goto error_creating_entry; - } - - /* - * We just created a new UID entry, If there are event - * notifiers already registered, take one reference on their - * behalf. - */ - if (the_event_notifier_counter.count > 0) { - ust_error_accounting_entry_get(entry); - } - - pthread_mutex_unlock(&the_event_notifier_counter.lock); - } - - /* Duplicate counter object data. */ - ret = lttng_ust_ctl_duplicate_ust_object_data(&new_counter, - entry->counter); - if (ret) { - ERR("Failed to duplicate event notifier error accounting counter for application user: application uid = %d, pid = %d, application name = '%s'", - (int) app->uid, (int) app->pid, app->name); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto error_duplicate_counter; - } - - status = send_counter_data_to_ust(app, new_counter); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - if (status == EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD) { - goto error_send_counter_data; - } - - ERR("Failed to send counter data to application tracer: status = %s, application uid = %d, pid = %d, application name = '%s'", - error_accounting_status_str(status), - (int) app->uid, (int) app->pid, app->name); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto error_send_counter_data; - } - - cpu_counters = zmalloc(entry->nr_counter_cpu_fds * sizeof(struct lttng_ust_abi_object_data *)); - if (!cpu_counters) { - PERROR("Failed to allocate event notifier error counter lttng_ust_abi_object_data array: application uid = %d, application name = '%s', pid = %d, allocation size = %zu", - (int) app->uid, app->name, (int) app->pid, - entry->nr_counter_cpu_fds * sizeof(**cpu_counters)); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; - goto error_allocate_cpu_counters; - } - - for (i = 0; i < entry->nr_counter_cpu_fds; i++) { - struct lttng_ust_abi_object_data *new_counter_cpu = NULL; - - ret = lttng_ust_ctl_duplicate_ust_object_data(&new_counter_cpu, - entry->cpu_counters[i]); - if (ret) { - ERR("Failed to duplicate userspace tracer counter cpu data for application user: uid = %d, pid = %d, application name = '%s'", - (int) app->uid, (int) app->pid, - app->name); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; - goto error_duplicate_cpu_counter; - } - - cpu_counters[i] = new_counter_cpu; - - status = send_counter_cpu_data_to_ust(app, new_counter, - new_counter_cpu); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - if (status == EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD) { - goto error_send_cpu_counter_data; - } - - ERR("Failed to send counter cpu data to application tracer: status = %s, application uid = %d, pid = %d, application name = '%s'", - error_accounting_status_str(status), - (int) app->uid, (int) app->pid, - app->name); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto error_send_cpu_counter_data; - } - } - - app->event_notifier_group.counter = new_counter; - new_counter = NULL; - app->event_notifier_group.nr_counter_cpu = entry->nr_counter_cpu_fds; - app->event_notifier_group.counter_cpu = cpu_counters; - cpu_counters = NULL; - goto end_unlock; - -error_send_cpu_counter_data: -error_duplicate_cpu_counter: - /* Teardown any duplicated cpu counters. */ - for (i = 0; i < entry->nr_counter_cpu_fds; i++) { - if (!cpu_counters[i]) { - /* - * Early-exit when error occurred before all cpu - * counters could be initialized. - */ - break; - } - - lttng_ust_ctl_release_object(-1, cpu_counters[i]); - free(cpu_counters[i]); - } - - free(cpu_counters); - -error_allocate_cpu_counters: -error_send_counter_data: - lttng_ust_ctl_release_object(-1, new_counter); - free(new_counter); -error_duplicate_counter: - ust_error_accounting_entry_put(entry); -error_creating_entry: - app->event_notifier_group.counter = NULL; -end_unlock: - rcu_read_unlock(); -end: - return status; -} - -enum event_notifier_error_accounting_status -event_notifier_error_accounting_unregister_app(struct ust_app *app) -{ - enum event_notifier_error_accounting_status status; - struct ust_error_accounting_entry *entry; - int i; - - rcu_read_lock(); - - /* If an error occurred during app registration no entry was created. */ - if (!app->event_notifier_group.counter) { - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; - goto end; - } - - entry = ust_error_accounting_entry_find(error_counter_uid_ht, app); - if (entry == NULL) { - ERR("Failed to find event notitifier error accounting entry on application teardown: pid = %d, application name = '%s'", - app->pid, app->name); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto end; - } else { - /* - * Put the entry twice as we acquired a reference from the - * `ust_error_accounting_entry_find()` above. - */ - ust_error_accounting_entry_put(entry); - ust_error_accounting_entry_put(entry); - } - - for (i = 0; i < app->event_notifier_group.nr_counter_cpu; i++) { - lttng_ust_ctl_release_object(app->sock, - app->event_notifier_group.counter_cpu[i]); - free(app->event_notifier_group.counter_cpu[i]); - } - - free(app->event_notifier_group.counter_cpu); - - lttng_ust_ctl_release_object(app->sock, app->event_notifier_group.counter); - free(app->event_notifier_group.counter); - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; -end: - rcu_read_unlock(); - return status; -} - -static -enum event_notifier_error_accounting_status -event_notifier_error_accounting_ust_get_count( - const struct lttng_trigger *trigger, uint64_t *count) -{ - struct lttng_ht_iter iter; - struct ust_error_accounting_entry *uid_entry; - uint64_t error_counter_index, global_sum = 0; - enum event_notifier_error_accounting_status status; - size_t dimension_indexes[1]; - const uint64_t tracer_token = lttng_trigger_get_tracer_token(trigger); - uid_t trigger_owner_uid; - const char *trigger_name; - - - rcu_read_lock(); - - get_trigger_info_for_log(trigger, &trigger_name, &trigger_owner_uid); - - status = get_error_counter_index_for_token(&ust_state, tracer_token, - &error_counter_index); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - - ERR("Failed to retrieve index for tracer token: token = %" PRIu64 ", trigger name = '%s', trigger owner uid = %d, status = %s", - tracer_token, trigger_name, - (int) trigger_owner_uid, - error_accounting_status_str(status)); - goto end; - } - - dimension_indexes[0] = error_counter_index; - - /* - * Iterate over all the UID entries. - * We aggregate the value of all uid entries regardless of if the uid - * matches the trigger's uid because a user that is allowed to register - * a trigger to a given sessiond is also allowed to create an event - * notifier on all apps that this sessiond is aware of. - */ - cds_lfht_for_each_entry(error_counter_uid_ht->ht, &iter.iter, - uid_entry, node.node) { - int ret; - int64_t local_value = 0; - bool overflow = false, underflow = false; - - ret = lttng_ust_ctl_counter_aggregate(uid_entry->daemon_counter, - dimension_indexes, &local_value, &overflow, - &underflow); - if (ret || local_value < 0) { - if (ret) { - ERR("Failed to aggregate event notifier error counter values of trigger: trigger name = '%s', trigger owner uid = %d", - trigger_name, - (int) trigger_owner_uid); - } else if (local_value < 0) { - ERR("Negative event notifier error counter value encountered during aggregation: trigger name = '%s', trigger owner uid = %d, value = %" PRId64, - trigger_name, - (int) trigger_owner_uid, - local_value); - } else { - abort(); - } - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto end; - } - - /* Cast is safe as negative values are checked-for above. */ - global_sum += (uint64_t) local_value; - } - - *count = global_sum; - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; - -end: - rcu_read_unlock(); - return status; -} - -static -enum event_notifier_error_accounting_status event_notifier_error_accounting_ust_clear( - const struct lttng_trigger *trigger) -{ - struct lttng_ht_iter iter; - struct ust_error_accounting_entry *uid_entry; - uint64_t error_counter_index; - enum event_notifier_error_accounting_status status; - size_t dimension_index; - const uint64_t tracer_token = lttng_trigger_get_tracer_token(trigger); - - rcu_read_lock(); - status = get_error_counter_index_for_token(&ust_state, tracer_token, - &error_counter_index); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - uid_t trigger_owner_uid; - const char *trigger_name; - - get_trigger_info_for_log(trigger, &trigger_name, - &trigger_owner_uid); - - ERR("Failed to retrieve index for tracer token: token = %" PRIu64 ", trigger name = '%s', trigger owner uid = %d, status = %s", - tracer_token, trigger_name, - (int) trigger_owner_uid, - error_accounting_status_str(status)); - goto end; - } - - dimension_index = error_counter_index; - - /* - * Go over all error counters (ignoring uid) as a trigger (and trigger - * errors) can be generated from any applications that this session - * daemon is managing. - */ - cds_lfht_for_each_entry(error_counter_uid_ht->ht, &iter.iter, - uid_entry, node.node) { - const int ret = lttng_ust_ctl_counter_clear(uid_entry->daemon_counter, - &dimension_index); - - if (ret) { - uid_t trigger_owner_uid; - const char *trigger_name; - - get_trigger_info_for_log(trigger, &trigger_name, - &trigger_owner_uid); - ERR("Failed to clear event notifier counter value for trigger: counter uid = %d, trigger name = '%s', trigger owner uid = %d", - (int) uid_entry->node.key, trigger_name, - (int) trigger_owner_uid); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto end; - } - } - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; -end: - rcu_read_unlock(); - return status; -} -#endif /* HAVE_LIBLTTNG_UST_CTL */ - -static -enum event_notifier_error_accounting_status -event_notifier_error_accounting_kernel_clear( - const struct lttng_trigger *trigger) -{ - int ret; - uint64_t error_counter_index; - enum event_notifier_error_accounting_status status; - struct lttng_kernel_abi_counter_clear counter_clear = {}; - - status = get_error_counter_index_for_token(&kernel_state, - lttng_trigger_get_tracer_token(trigger), - &error_counter_index); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - uid_t trigger_owner_uid; - const char *trigger_name; - - get_trigger_info_for_log( - trigger, &trigger_name, &trigger_owner_uid); - - ERR("Failed to get event notifier error counter index: trigger owner uid = %d, trigger name = '%s', status = '%s'", - trigger_owner_uid, trigger_name, - error_accounting_status_str(status)); - goto end; - } - - counter_clear.index.number_dimensions = 1; - counter_clear.index.dimension_indexes[0] = error_counter_index; - - ret = kernctl_counter_clear( - kernel_error_accounting_entry.error_counter_fd, - &counter_clear); - if (ret) { - uid_t trigger_owner_uid; - const char *trigger_name; - - get_trigger_info_for_log( - trigger, &trigger_name, &trigger_owner_uid); - - ERR("Failed to clear kernel event notifier error counter: trigger owner uid = %d, trigger name = '%s'", - trigger_owner_uid, trigger_name); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto end; - } - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; -end: - return status; -} - -enum event_notifier_error_accounting_status -event_notifier_error_accounting_register_kernel( - int kernel_event_notifier_group_fd) -{ - int error_counter_fd = -1, ret; - enum event_notifier_error_accounting_status status; - const struct lttng_kernel_abi_counter_conf error_counter_conf = { - .arithmetic = LTTNG_KERNEL_ABI_COUNTER_ARITHMETIC_MODULAR, - .bitness = sizeof(void *) == sizeof(uint32_t) ? - LTTNG_KERNEL_ABI_COUNTER_BITNESS_32 : - LTTNG_KERNEL_ABI_COUNTER_BITNESS_64, - .global_sum_step = 0, - .number_dimensions = 1, - .dimensions[0].size = kernel_state.number_indices, - .dimensions[0].has_underflow = false, - .dimensions[0].has_overflow = false, - }; - - ret = kernctl_create_event_notifier_group_error_counter( - kernel_event_notifier_group_fd, &error_counter_conf); - if (ret < 0) { - PERROR("Failed to create event notifier group error counter through kernel ioctl: kernel_event_notifier_group_fd = %d", - kernel_event_notifier_group_fd); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto error; - } - - error_counter_fd = ret; - - /* Prevent fd duplication after execlp(). */ - ret = fcntl(error_counter_fd, F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("Failed to set FD_CLOEXEC flag on event notifier error counter file descriptor: error_counter_fd = %d", - error_counter_fd); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto error; - } - - DBG("Created kernel event notifier group error counter: fd = %d", - error_counter_fd); - - kernel_error_accounting_entry.error_counter_fd = - error_counter_fd; - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; - -error: - return status; -} - -static -enum event_notifier_error_accounting_status create_error_counter_index_for_token( - struct error_accounting_state *state, uint64_t tracer_token, - uint64_t *error_counter_index) -{ - struct index_ht_entry *index_entry; - enum lttng_index_allocator_status index_alloc_status; - uint64_t local_error_counter_index; - enum event_notifier_error_accounting_status status; - - LTTNG_ASSERT(state); - - /* Allocate a new index for that counter. */ - index_alloc_status = lttng_index_allocator_alloc(state->index_allocator, - &local_error_counter_index); - switch (index_alloc_status) { - case LTTNG_INDEX_ALLOCATOR_STATUS_EMPTY: - DBG("No indices left in the configured event notifier error counter: " - "number-of-indices = %"PRIu64, - lttng_index_allocator_get_index_count( - state->index_allocator)); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NO_INDEX_AVAILABLE; - goto end; - case LTTNG_INDEX_ALLOCATOR_STATUS_OK: - break; - default: - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto end; - } - - index_entry = zmalloc(sizeof(*index_entry)); - if (index_entry == NULL) { - PERROR("Failed to allocate event notifier error counter hash table entry"); - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; - goto end; - } - - index_entry->error_counter_index = local_error_counter_index; - lttng_ht_node_init_u64(&index_entry->node, tracer_token); - lttng_ht_add_unique_u64(state->indices_ht, &index_entry->node); - - DBG("Allocated error counter index for tracer token: tracer token = %" PRIu64 ", index = %" PRIu64, - tracer_token, local_error_counter_index); - *error_counter_index = local_error_counter_index; - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; -end: - return status; -} - -enum event_notifier_error_accounting_status -event_notifier_error_accounting_register_event_notifier( - const struct lttng_trigger *trigger, - uint64_t *error_counter_index) -{ - enum event_notifier_error_accounting_status status; - uint64_t local_error_counter_index; - struct error_accounting_state *state; - - switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { - case LTTNG_DOMAIN_KERNEL: - state = &kernel_state; - break; - case LTTNG_DOMAIN_UST: - case LTTNG_DOMAIN_PYTHON: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - state = &ust_state; - break; - default: - abort(); - } - - /* - * Check if this event notifier already has a error counter index - * assigned. - */ - status = get_error_counter_index_for_token(state, - lttng_trigger_get_tracer_token(trigger), - &local_error_counter_index); - switch (status) { - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOT_FOUND: - { - uid_t trigger_owner_uid; - const char *trigger_name; - - get_trigger_info_for_log( - trigger, &trigger_name, &trigger_owner_uid); - - DBG("Event notifier error counter index not found for tracer token (allocating a new one): trigger name = '%s', trigger owner uid = %d, tracer token = %" PRIu64, - trigger_name, trigger_owner_uid, - lttng_trigger_get_tracer_token(trigger)); - - status = create_error_counter_index_for_token(state, - lttng_trigger_get_tracer_token(trigger), - &local_error_counter_index); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - ERR("Error creating index for token: status = %s, trigger name = '%s', trigger owner uid = %d", - error_accounting_status_str(status), - trigger_name, trigger_owner_uid); - goto end; - } - /* fall-through. */ - } - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK: - *error_counter_index = local_error_counter_index; - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; - break; - default: - break; - } - -#ifdef HAVE_LIBLTTNG_UST_CTL - switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { - case LTTNG_DOMAIN_UST: - case LTTNG_DOMAIN_PYTHON: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - pthread_mutex_lock(&the_event_notifier_counter.lock); - the_event_notifier_counter.count++; - if (the_event_notifier_counter.count == 1) { - /* - * On the first event notifier, we get a reference to - * every existing UID entries. This ensures that the - * entries are kept around if there are still - * registered event notifiers but no apps. - */ - get_ref_all_ust_error_accounting_entry(); - } - pthread_mutex_unlock(&the_event_notifier_counter.lock); - break; - default: - break; - } -#endif /* HAVE_LIBLTTNG_UST_CTL */ - - -end: - return status; -} - -static -enum event_notifier_error_accounting_status -event_notifier_error_accounting_kernel_get_count( - const struct lttng_trigger *trigger, uint64_t *count) -{ - struct lttng_kernel_abi_counter_aggregate counter_aggregate = {}; - enum event_notifier_error_accounting_status status; - uint64_t error_counter_index; - int ret; - - status = get_error_counter_index_for_token(&kernel_state, - lttng_trigger_get_tracer_token(trigger), - &error_counter_index); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - ERR("Error getting index for token: status=%s", - error_accounting_status_str(status)); - goto end; - } - - counter_aggregate.index.number_dimensions = 1; - counter_aggregate.index.dimension_indexes[0] = error_counter_index; - - LTTNG_ASSERT(kernel_error_accounting_entry.error_counter_fd); - - ret = kernctl_counter_get_aggregate_value( - kernel_error_accounting_entry.error_counter_fd, - &counter_aggregate); - if (ret || counter_aggregate.value.value < 0) { - uid_t trigger_owner_uid; - const char *trigger_name; - - get_trigger_info_for_log(trigger, &trigger_name, - &trigger_owner_uid); - - if (counter_aggregate.value.value < 0) { - ERR("Invalid negative event notifier error counter value: trigger owner = %d, trigger name = '%s', value = %" PRId64, - trigger_owner_uid, trigger_name, - counter_aggregate.value.value); - } else { - ERR("Failed to getting event notifier error count: trigger owner = %d, trigger name = '%s', ret = %d", - trigger_owner_uid, trigger_name, ret); - } - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; - goto end; - } - - /* Error count can't be negative. */ - LTTNG_ASSERT(counter_aggregate.value.value >= 0); - *count = (uint64_t) counter_aggregate.value.value; - - status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; - -end: - return status; -} - -enum event_notifier_error_accounting_status -event_notifier_error_accounting_get_count( - const struct lttng_trigger *trigger, uint64_t *count) -{ - switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { - case LTTNG_DOMAIN_KERNEL: - return event_notifier_error_accounting_kernel_get_count( - trigger, count); - case LTTNG_DOMAIN_UST: - case LTTNG_DOMAIN_PYTHON: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: -#ifdef HAVE_LIBLTTNG_UST_CTL - return event_notifier_error_accounting_ust_get_count(trigger, - count); -#else - *count = 0; - return EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; -#endif /* HAVE_LIBLTTNG_UST_CTL */ - default: - abort(); - } -} - -static -enum event_notifier_error_accounting_status -event_notifier_error_accounting_clear(const struct lttng_trigger *trigger) -{ - switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { - case LTTNG_DOMAIN_KERNEL: - return event_notifier_error_accounting_kernel_clear(trigger); - case LTTNG_DOMAIN_UST: - case LTTNG_DOMAIN_PYTHON: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: -#ifdef HAVE_LIBLTTNG_UST_CTL - return event_notifier_error_accounting_ust_clear(trigger); -#else - return EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; -#endif /* HAVE_LIBLTTNG_UST_CTL */ - default: - abort(); - } -} - -static void free_index_ht_entry(struct rcu_head *head) -{ - struct index_ht_entry *entry = caa_container_of(head, - struct index_ht_entry, rcu_head); - - free(entry); -} - -void event_notifier_error_accounting_unregister_event_notifier( - const struct lttng_trigger *trigger) -{ - struct lttng_ht_iter iter; - struct lttng_ht_node_u64 *node; - const uint64_t tracer_token = lttng_trigger_get_tracer_token(trigger); - enum event_notifier_error_accounting_status status; - struct error_accounting_state *state; - - status = event_notifier_error_accounting_clear(trigger); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - /* Trigger details already logged by callee on error. */ - ERR("Failed to clear event notifier error counter during unregistration of event notifier: status = '%s'", - error_accounting_status_str(status)); - goto end; - } - - rcu_read_lock(); - - switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { - case LTTNG_DOMAIN_KERNEL: - state = &kernel_state; - break; -#ifdef HAVE_LIBLTTNG_UST_CTL - case LTTNG_DOMAIN_UST: - case LTTNG_DOMAIN_PYTHON: - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - state = &ust_state; - - pthread_mutex_lock(&the_event_notifier_counter.lock); - the_event_notifier_counter.count--; - if (the_event_notifier_counter.count == 0) { - - /* - * When unregistering the last event notifier, put one - * reference to every uid entries on the behalf of all - * event notifiers. - */ - put_ref_all_ust_error_accounting_entry(); - } - - pthread_mutex_unlock(&the_event_notifier_counter.lock); - - break; -#endif /* HAVE_LIBLTTNG_UST_CTL */ - default: - abort(); - } - - lttng_ht_lookup(state->indices_ht, &tracer_token, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (node) { - int del_ret; - struct index_ht_entry *index_entry = caa_container_of( - node, typeof(*index_entry), node); - enum lttng_index_allocator_status index_alloc_status; - - index_alloc_status = lttng_index_allocator_release( - state->index_allocator, - index_entry->error_counter_index); - if (index_alloc_status != LTTNG_INDEX_ALLOCATOR_STATUS_OK) { - uid_t trigger_owner_uid; - const char *trigger_name; - - get_trigger_info_for_log(trigger, &trigger_name, - &trigger_owner_uid); - - ERR("Failed to release event notifier error counter index: index = %" PRIu64 ", trigger name = '%s', trigger owner uid = %d", - index_entry->error_counter_index, - trigger_name, (int) trigger_owner_uid); - /* Don't exit, perform the rest of the clean-up. */ - } - - del_ret = lttng_ht_del(state->indices_ht, &iter); - LTTNG_ASSERT(!del_ret); - call_rcu(&index_entry->rcu_head, free_index_ht_entry); - } - -end: - rcu_read_unlock(); -} - -void event_notifier_error_accounting_fini(void) -{ - if (kernel_error_accounting_entry.error_counter_fd) { - const int ret = close(kernel_error_accounting_entry.error_counter_fd); - - if (ret) { - PERROR("Failed to close kernel event notifier error counter"); - } - } - - lttng_ht_destroy(error_counter_uid_ht); - - fini_error_accounting_state(&kernel_state); - fini_error_accounting_state(&ust_state); -} diff --git a/src/bin/lttng-sessiond/event-notifier-error-accounting.cpp b/src/bin/lttng-sessiond/event-notifier-error-accounting.cpp new file mode 100644 index 000000000..c99c39338 --- /dev/null +++ b/src/bin/lttng-sessiond/event-notifier-error-accounting.cpp @@ -0,0 +1,1368 @@ +/* + * Copyright (C) 2020 Francis Deslauriers + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "event-notifier-error-accounting.h" +#include "lttng-ust-error.h" +#include "ust-app.h" + +#define ERROR_COUNTER_INDEX_HT_INITIAL_SIZE 16 + +struct index_ht_entry { + struct lttng_ht_node_u64 node; + uint64_t error_counter_index; + struct rcu_head rcu_head; +}; + +struct ust_error_accounting_entry { + uid_t uid; + struct urcu_ref ref; + struct lttng_ht_node_u64 node; + struct rcu_head rcu_head; + struct lttng_ust_ctl_daemon_counter *daemon_counter; + /* + * Those `lttng_ust_abi_object_data` are anonymous handles to the + * counters objects. + * They are only used to be duplicated for each new applications of the + * user. To destroy them, call with the `sock` parameter set to -1. + * e.g. `lttng_ust_ctl_release_object(-1, data)`; + */ + struct lttng_ust_abi_object_data *counter; + struct lttng_ust_abi_object_data **cpu_counters; + int nr_counter_cpu_fds; +}; + +struct kernel_error_accounting_entry { + int error_counter_fd; +}; + +static struct kernel_error_accounting_entry kernel_error_accounting_entry; + +/* Hashtable mapping uid to error_account_entry. */ +static struct lttng_ht *error_counter_uid_ht; + +struct error_accounting_state { + struct lttng_index_allocator *index_allocator; + /* Hashtable mapping event notifier token to index_ht_entry. */ + struct lttng_ht *indices_ht; + uint64_t number_indices; +}; + +static struct error_accounting_state ust_state; +static struct error_accounting_state kernel_state; + +static inline void get_trigger_info_for_log(const struct lttng_trigger *trigger, + const char **trigger_name, + uid_t *trigger_owner_uid) +{ + enum lttng_trigger_status trigger_status; + + trigger_status = lttng_trigger_get_name(trigger, trigger_name); + switch (trigger_status) { + case LTTNG_TRIGGER_STATUS_OK: + break; + case LTTNG_TRIGGER_STATUS_UNSET: + *trigger_name = "(anonymous)"; + break; + default: + abort(); + } + + trigger_status = lttng_trigger_get_owner_uid(trigger, + trigger_owner_uid); + LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); +} + +static inline +const char *error_accounting_status_str( + enum event_notifier_error_accounting_status status) +{ + switch (status) { + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK: + return "OK"; + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR: + return "ERROR"; + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOT_FOUND: + return "NOT_FOUND"; + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM: + return "NOMEM"; + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NO_INDEX_AVAILABLE: + return "NO_INDEX_AVAILABLE"; + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD: + return "APP_DEAD"; + default: + abort(); + } +} + +#ifdef HAVE_LIBLTTNG_UST_CTL +struct event_notifier_counter { + pthread_mutex_t lock; + long count; +}; + +static struct event_notifier_counter the_event_notifier_counter; + +static void free_ust_error_accounting_entry(struct rcu_head *head) +{ + int i; + struct ust_error_accounting_entry *entry = + caa_container_of(head, typeof(*entry), rcu_head); + + for (i = 0; i < entry->nr_counter_cpu_fds; i++) { + lttng_ust_ctl_release_object(-1, entry->cpu_counters[i]); + free(entry->cpu_counters[i]); + } + + free(entry->cpu_counters); + + lttng_ust_ctl_release_object(-1, entry->counter); + free(entry->counter); + + lttng_ust_ctl_destroy_counter(entry->daemon_counter); + + free(entry); +} + +static +bool ust_error_accounting_entry_get(struct ust_error_accounting_entry *entry) +{ + return urcu_ref_get_unless_zero(&entry->ref); +} + +static +void ust_error_accounting_entry_release(struct urcu_ref *entry_ref) +{ + struct ust_error_accounting_entry *entry = + container_of(entry_ref, typeof(*entry), ref); + + rcu_read_lock(); + cds_lfht_del(error_counter_uid_ht->ht, &entry->node.node); + call_rcu(&entry->rcu_head, free_ust_error_accounting_entry); + rcu_read_unlock(); +} + + +static +void ust_error_accounting_entry_put(struct ust_error_accounting_entry *entry) +{ + if (!entry) { + return; + } + + urcu_ref_put(&entry->ref, ust_error_accounting_entry_release); +} + +/* + * Put one reference to every UID entries. + */ +static +void put_ref_all_ust_error_accounting_entry(void) +{ + struct lttng_ht_iter iter; + struct ust_error_accounting_entry *uid_entry; + + ASSERT_LOCKED(the_event_notifier_counter.lock); + + rcu_read_lock(); + cds_lfht_for_each_entry(error_counter_uid_ht->ht, &iter.iter, + uid_entry, node.node) { + ust_error_accounting_entry_put(uid_entry); + } + + rcu_read_unlock(); +} + +/* + * Get one reference to every UID entries. + */ +static +void get_ref_all_ust_error_accounting_entry(void) +{ + struct lttng_ht_iter iter; + struct ust_error_accounting_entry *uid_entry; + + ASSERT_LOCKED(the_event_notifier_counter.lock); + + rcu_read_lock(); + cds_lfht_for_each_entry(error_counter_uid_ht->ht, &iter.iter, + uid_entry, node.node) { + ust_error_accounting_entry_get(uid_entry); + } + + rcu_read_unlock(); +} + +#endif /* HAVE_LIBLTTNG_UST_CTL */ + +static +enum event_notifier_error_accounting_status +init_error_accounting_state(struct error_accounting_state *state, + uint64_t index_count) +{ + enum event_notifier_error_accounting_status status; + + LTTNG_ASSERT(state); + + state->number_indices = index_count; + + state->index_allocator = lttng_index_allocator_create(index_count); + if (!state->index_allocator) { + ERR("Failed to allocate event notifier error counter index allocator"); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; + goto end; + } + + state->indices_ht = lttng_ht_new(ERROR_COUNTER_INDEX_HT_INITIAL_SIZE, + LTTNG_HT_TYPE_U64); + if (!state->indices_ht) { + ERR("Failed to allocate error counter indices hash table"); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; + goto error_indices_ht; + } + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; + goto end; + +error_indices_ht: + lttng_index_allocator_destroy(state->index_allocator); + state->index_allocator = NULL; +end: + return status; +} + +static +void fini_error_accounting_state(struct error_accounting_state *state) +{ + LTTNG_ASSERT(state); + + /* + * Will assert if some error counter indices were not released (an + * internal error). + */ + lttng_ht_destroy(state->indices_ht); + lttng_index_allocator_destroy(state->index_allocator); +} + +enum event_notifier_error_accounting_status +event_notifier_error_accounting_init(uint64_t buffer_size_kernel, + uint64_t buffer_size_ust) +{ + enum event_notifier_error_accounting_status status; + + status = init_error_accounting_state(&kernel_state, buffer_size_kernel); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + ERR("Failed to initialize kernel event notifier accounting state: status = %s", + error_accounting_status_str(status)); + goto end; + } + + status = init_error_accounting_state(&ust_state, buffer_size_ust); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + ERR("Failed to initialize UST event notifier accounting state: status = %s", + error_accounting_status_str(status)); + goto error_ust_state; + } + + error_counter_uid_ht = lttng_ht_new( + ERROR_COUNTER_INDEX_HT_INITIAL_SIZE, LTTNG_HT_TYPE_U64); + if (!error_counter_uid_ht) { + ERR("Failed to allocate UID to error counter accountant hash table"); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; + goto error_uid_ht; + } + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; + goto end; + +error_uid_ht: + fini_error_accounting_state(&ust_state); +error_ust_state: + fini_error_accounting_state(&kernel_state); +end: + return status; +} + +/* + * Return the error counteur index associated to this event notifier tracer + * token. Returns _STATUS_OK if found and _STATUS_NOT_FOUND otherwise. + */ +static +enum event_notifier_error_accounting_status get_error_counter_index_for_token( + struct error_accounting_state *state, uint64_t tracer_token, + uint64_t *error_counter_index) +{ + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + const struct index_ht_entry *index_entry; + enum event_notifier_error_accounting_status status; + + rcu_read_lock(); + lttng_ht_lookup(state->indices_ht, &tracer_token, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (node) { + index_entry = caa_container_of( + node, const struct index_ht_entry, node); + *error_counter_index = index_entry->error_counter_index; + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; + } else { + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOT_FOUND; + } + + rcu_read_unlock(); + return status; +} + +#ifdef HAVE_LIBLTTNG_UST_CTL +/* + * Find the entry for this app's UID, the caller acquires a reference if the + * entry is found. + */ +static +struct ust_error_accounting_entry *ust_error_accounting_entry_find( + struct lttng_ht *uid_ht, const struct ust_app *app) +{ + struct ust_error_accounting_entry *entry; + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + uint64_t key = app->uid; + + lttng_ht_lookup(uid_ht, &key, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if(node == NULL) { + entry = NULL; + } else { + bool got_ref; + + entry = caa_container_of(node, + struct ust_error_accounting_entry, node); + + got_ref = ust_error_accounting_entry_get(entry); + if (!got_ref) { + entry = NULL; + } + } + + return entry; +} + +/* + * Create the entry for this app's UID, the caller acquires a reference to the + * entry, + */ +static +struct ust_error_accounting_entry *ust_error_accounting_entry_create( + struct lttng_ht *uid_ht, const struct ust_app *app) +{ + int i, ret, *cpu_counter_fds = NULL; + struct lttng_ust_ctl_daemon_counter *daemon_counter; + struct lttng_ust_abi_object_data *counter, **cpu_counters; + struct ust_error_accounting_entry *entry = NULL; + lttng_ust_ctl_counter_dimension dimension; + + dimension.size = ust_state.number_indices; + dimension.has_underflow = false; + dimension.has_overflow = false; + + if (!ust_app_supports_counters(app)) { + DBG("Refusing to create accounting entry for application (unsupported feature): app name = '%s', app ppid = %d", + app->name, (int) app->ppid); + goto error; + } + + entry = (ust_error_accounting_entry *) zmalloc(sizeof(struct ust_error_accounting_entry)); + if (!entry) { + PERROR("Failed to allocate event notifier error acounting entry") + goto error; + } + + urcu_ref_init(&entry->ref); + entry->uid = app->uid; + entry->nr_counter_cpu_fds = lttng_ust_ctl_get_nr_cpu_per_counter(); + + cpu_counter_fds = (int *) zmalloc(entry->nr_counter_cpu_fds * sizeof(*cpu_counter_fds)); + if (!cpu_counter_fds) { + PERROR("Failed to allocate event notifier error counter file descriptors array: application uid = %d, application name = '%s', pid = %d, allocation size = %zu", + (int) app->uid, app->name, (int) app->pid, + entry->nr_counter_cpu_fds * sizeof(*cpu_counter_fds)); + goto error_counter_cpu_fds_alloc; + } + + /* Initialize to an invalid fd value to closes fds in case of error. */ + for (i = 0; i < entry->nr_counter_cpu_fds; i++) { + cpu_counter_fds[i] = -1; + } + + cpu_counters = (lttng_ust_abi_object_data **) zmalloc(entry->nr_counter_cpu_fds * sizeof(struct lttng_ust_abi_object_data *)); + if (!cpu_counters) { + PERROR("Failed to allocate event notifier error counter lttng_ust_abi_object_data array: application uid = %d, application name = '%s', pid = %d, allocation size = %zu", + (int) app->uid, app->name, (int) app->pid, + entry->nr_counter_cpu_fds * sizeof(struct lttng_ust_abi_object_data *)); + goto error_counter_cpus_alloc; + } + + for (i = 0; i < entry->nr_counter_cpu_fds; i++) { + cpu_counter_fds[i] = shm_create_anonymous("event-notifier-error-accounting"); + if (cpu_counter_fds[i] == -1) { + ERR("Failed to create event notifier error accounting shared memory for application user: application uid = %d, pid = %d, application name = '%s'", + (int) app->uid, (int) app->pid, app->name); + goto error_shm_alloc; + } + } + + /* + * Ownership of the file descriptors transferred to the ustctl object. + */ + daemon_counter = lttng_ust_ctl_create_counter(1, &dimension, 0, -1, + entry->nr_counter_cpu_fds, cpu_counter_fds, + LTTNG_UST_CTL_COUNTER_BITNESS_32, + LTTNG_UST_CTL_COUNTER_ARITHMETIC_MODULAR, + LTTNG_UST_CTL_COUNTER_ALLOC_PER_CPU, + false); + if (!daemon_counter) { + goto error_create_daemon_counter; + } + + ret = lttng_ust_ctl_create_counter_data(daemon_counter, &counter); + if (ret) { + ERR("Failed to create userspace tracer counter data for application user: uid = %d, pid = %d, application name = '%s'", + (int) app->uid, (int) app->pid, app->name); + goto error_create_counter_data; + } + + for (i = 0; i < entry->nr_counter_cpu_fds; i++) { + ret = lttng_ust_ctl_create_counter_cpu_data(daemon_counter, i, + &cpu_counters[i]); + if (ret) { + ERR("Failed to create userspace tracer counter cpu data for application user: uid = %d, pid = %d, application name = '%s'", + (int) app->uid, (int) app->pid, + app->name); + goto error_create_counter_cpu_data; + } + } + + entry->daemon_counter = daemon_counter; + entry->counter = counter; + entry->cpu_counters = cpu_counters; + + lttng_ht_node_init_u64(&entry->node, entry->uid); + lttng_ht_add_unique_u64(error_counter_uid_ht, &entry->node); + + goto end; + +error_create_counter_cpu_data: + /* Teardown any allocated cpu counters. */ + for (i = 0; i < entry->nr_counter_cpu_fds; i++) { + if (!cpu_counters[i]) { + /* + * Early-exit when error occurred before all cpu + * counters could be initialized. + */ + break; + } + + lttng_ust_ctl_release_object(-1, cpu_counters[i]); + free(cpu_counters[i]); + } + + lttng_ust_ctl_release_object(-1, entry->counter); + free(entry->counter); +error_create_counter_data: + lttng_ust_ctl_destroy_counter(daemon_counter); +error_create_daemon_counter: +error_shm_alloc: + /* Error occurred before per-cpu SHMs were handed-off to ustctl. */ + if (cpu_counter_fds) { + for (i = 0; i < entry->nr_counter_cpu_fds; i++) { + if (cpu_counter_fds[i] < 0) { + /* + * Early-exit when error occurred before all cpu + * counter shm fds could be initialized. + */ + break; + } + + ret = close(cpu_counter_fds[i]); + if (ret) { + PERROR("Failed to close error counter per-CPU shm file descriptor: fd = %d", + cpu_counter_fds[i]); + } + } + } + + free(cpu_counters); +error_counter_cpus_alloc: +error_counter_cpu_fds_alloc: + free(entry); +error: + entry = NULL; +end: + free(cpu_counter_fds); + return entry; +} + +static +enum event_notifier_error_accounting_status send_counter_data_to_ust( + struct ust_app *app, + struct lttng_ust_abi_object_data *new_counter) +{ + int ret; + enum event_notifier_error_accounting_status status; + + /* Attach counter to trigger group. */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_send_counter_data_to_ust(app->sock, + app->event_notifier_group.object->handle, new_counter); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { + ERR("Failed to send counter data to application: application name = '%s', pid = %d, ret = %d", + app->name, app->pid, ret); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + } else { + DBG3("Failed to send counter data to application (application is dead): application name = '%s', pid = %d, ret = %d", + app->name, app->pid, ret); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD; + } + + goto end; + } + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; +end: + return status; +} + +static +enum event_notifier_error_accounting_status send_counter_cpu_data_to_ust( + struct ust_app *app, + struct lttng_ust_abi_object_data *counter, + struct lttng_ust_abi_object_data *counter_cpu) +{ + int ret; + enum event_notifier_error_accounting_status status; + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_send_counter_cpu_data_to_ust(app->sock, + counter, counter_cpu); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret != -EPIPE && ret != -LTTNG_UST_ERR_EXITING) { + ERR("Failed to send counter CPU data to application: application name = '%s', pid = %d, ret = %d", + app->name, app->pid, ret); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + } else { + DBG3("Failed to send counter CPU data to application: application name = '%s', pid = %d, ret = %d", + app->name, app->pid, ret); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD; + } + + goto end; + } + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; +end: + return status; +} + +enum event_notifier_error_accounting_status +event_notifier_error_accounting_register_app(struct ust_app *app) +{ + int ret; + uint64_t i; + struct lttng_ust_abi_object_data *new_counter; + struct ust_error_accounting_entry *entry; + enum event_notifier_error_accounting_status status; + struct lttng_ust_abi_object_data **cpu_counters; + + if (!ust_app_supports_counters(app)) { + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_UNSUPPORTED; + goto end; + } + + /* + * Check if we already have a error counter for the user id of this + * app. If not, create one. + */ + rcu_read_lock(); + entry = ust_error_accounting_entry_find(error_counter_uid_ht, app); + if (entry == NULL) { + /* + * Take the event notifier counter lock before creating the new + * entry to ensure that no event notifier is registered between + * the the entry creation and event notifier count check. + */ + pthread_mutex_lock(&the_event_notifier_counter.lock); + + entry = ust_error_accounting_entry_create(error_counter_uid_ht, + app); + if (!entry) { + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + pthread_mutex_unlock(&the_event_notifier_counter.lock); + goto error_creating_entry; + } + + /* + * We just created a new UID entry, If there are event + * notifiers already registered, take one reference on their + * behalf. + */ + if (the_event_notifier_counter.count > 0) { + ust_error_accounting_entry_get(entry); + } + + pthread_mutex_unlock(&the_event_notifier_counter.lock); + } + + /* Duplicate counter object data. */ + ret = lttng_ust_ctl_duplicate_ust_object_data(&new_counter, + entry->counter); + if (ret) { + ERR("Failed to duplicate event notifier error accounting counter for application user: application uid = %d, pid = %d, application name = '%s'", + (int) app->uid, (int) app->pid, app->name); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto error_duplicate_counter; + } + + status = send_counter_data_to_ust(app, new_counter); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + if (status == EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD) { + goto error_send_counter_data; + } + + ERR("Failed to send counter data to application tracer: status = %s, application uid = %d, pid = %d, application name = '%s'", + error_accounting_status_str(status), + (int) app->uid, (int) app->pid, app->name); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto error_send_counter_data; + } + + cpu_counters = (lttng_ust_abi_object_data **) zmalloc(entry->nr_counter_cpu_fds * sizeof(struct lttng_ust_abi_object_data *)); + if (!cpu_counters) { + PERROR("Failed to allocate event notifier error counter lttng_ust_abi_object_data array: application uid = %d, application name = '%s', pid = %d, allocation size = %zu", + (int) app->uid, app->name, (int) app->pid, + entry->nr_counter_cpu_fds * sizeof(**cpu_counters)); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; + goto error_allocate_cpu_counters; + } + + for (i = 0; i < entry->nr_counter_cpu_fds; i++) { + struct lttng_ust_abi_object_data *new_counter_cpu = NULL; + + ret = lttng_ust_ctl_duplicate_ust_object_data(&new_counter_cpu, + entry->cpu_counters[i]); + if (ret) { + ERR("Failed to duplicate userspace tracer counter cpu data for application user: uid = %d, pid = %d, application name = '%s'", + (int) app->uid, (int) app->pid, + app->name); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; + goto error_duplicate_cpu_counter; + } + + cpu_counters[i] = new_counter_cpu; + + status = send_counter_cpu_data_to_ust(app, new_counter, + new_counter_cpu); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + if (status == EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD) { + goto error_send_cpu_counter_data; + } + + ERR("Failed to send counter cpu data to application tracer: status = %s, application uid = %d, pid = %d, application name = '%s'", + error_accounting_status_str(status), + (int) app->uid, (int) app->pid, + app->name); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto error_send_cpu_counter_data; + } + } + + app->event_notifier_group.counter = new_counter; + new_counter = NULL; + app->event_notifier_group.nr_counter_cpu = entry->nr_counter_cpu_fds; + app->event_notifier_group.counter_cpu = cpu_counters; + cpu_counters = NULL; + goto end_unlock; + +error_send_cpu_counter_data: +error_duplicate_cpu_counter: + /* Teardown any duplicated cpu counters. */ + for (i = 0; i < entry->nr_counter_cpu_fds; i++) { + if (!cpu_counters[i]) { + /* + * Early-exit when error occurred before all cpu + * counters could be initialized. + */ + break; + } + + lttng_ust_ctl_release_object(-1, cpu_counters[i]); + free(cpu_counters[i]); + } + + free(cpu_counters); + +error_allocate_cpu_counters: +error_send_counter_data: + lttng_ust_ctl_release_object(-1, new_counter); + free(new_counter); +error_duplicate_counter: + ust_error_accounting_entry_put(entry); +error_creating_entry: + app->event_notifier_group.counter = NULL; +end_unlock: + rcu_read_unlock(); +end: + return status; +} + +enum event_notifier_error_accounting_status +event_notifier_error_accounting_unregister_app(struct ust_app *app) +{ + enum event_notifier_error_accounting_status status; + struct ust_error_accounting_entry *entry; + int i; + + rcu_read_lock(); + + /* If an error occurred during app registration no entry was created. */ + if (!app->event_notifier_group.counter) { + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; + goto end; + } + + entry = ust_error_accounting_entry_find(error_counter_uid_ht, app); + if (entry == NULL) { + ERR("Failed to find event notitifier error accounting entry on application teardown: pid = %d, application name = '%s'", + app->pid, app->name); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto end; + } else { + /* + * Put the entry twice as we acquired a reference from the + * `ust_error_accounting_entry_find()` above. + */ + ust_error_accounting_entry_put(entry); + ust_error_accounting_entry_put(entry); + } + + for (i = 0; i < app->event_notifier_group.nr_counter_cpu; i++) { + lttng_ust_ctl_release_object(app->sock, + app->event_notifier_group.counter_cpu[i]); + free(app->event_notifier_group.counter_cpu[i]); + } + + free(app->event_notifier_group.counter_cpu); + + lttng_ust_ctl_release_object(app->sock, app->event_notifier_group.counter); + free(app->event_notifier_group.counter); + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; +end: + rcu_read_unlock(); + return status; +} + +static +enum event_notifier_error_accounting_status +event_notifier_error_accounting_ust_get_count( + const struct lttng_trigger *trigger, uint64_t *count) +{ + struct lttng_ht_iter iter; + struct ust_error_accounting_entry *uid_entry; + uint64_t error_counter_index, global_sum = 0; + enum event_notifier_error_accounting_status status; + size_t dimension_indexes[1]; + const uint64_t tracer_token = lttng_trigger_get_tracer_token(trigger); + uid_t trigger_owner_uid; + const char *trigger_name; + + + rcu_read_lock(); + + get_trigger_info_for_log(trigger, &trigger_name, &trigger_owner_uid); + + status = get_error_counter_index_for_token(&ust_state, tracer_token, + &error_counter_index); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + + ERR("Failed to retrieve index for tracer token: token = %" PRIu64 ", trigger name = '%s', trigger owner uid = %d, status = %s", + tracer_token, trigger_name, + (int) trigger_owner_uid, + error_accounting_status_str(status)); + goto end; + } + + dimension_indexes[0] = error_counter_index; + + /* + * Iterate over all the UID entries. + * We aggregate the value of all uid entries regardless of if the uid + * matches the trigger's uid because a user that is allowed to register + * a trigger to a given sessiond is also allowed to create an event + * notifier on all apps that this sessiond is aware of. + */ + cds_lfht_for_each_entry(error_counter_uid_ht->ht, &iter.iter, + uid_entry, node.node) { + int ret; + int64_t local_value = 0; + bool overflow = false, underflow = false; + + ret = lttng_ust_ctl_counter_aggregate(uid_entry->daemon_counter, + dimension_indexes, &local_value, &overflow, + &underflow); + if (ret || local_value < 0) { + if (ret) { + ERR("Failed to aggregate event notifier error counter values of trigger: trigger name = '%s', trigger owner uid = %d", + trigger_name, + (int) trigger_owner_uid); + } else if (local_value < 0) { + ERR("Negative event notifier error counter value encountered during aggregation: trigger name = '%s', trigger owner uid = %d, value = %" PRId64, + trigger_name, + (int) trigger_owner_uid, + local_value); + } else { + abort(); + } + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto end; + } + + /* Cast is safe as negative values are checked-for above. */ + global_sum += (uint64_t) local_value; + } + + *count = global_sum; + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; + +end: + rcu_read_unlock(); + return status; +} + +static +enum event_notifier_error_accounting_status event_notifier_error_accounting_ust_clear( + const struct lttng_trigger *trigger) +{ + struct lttng_ht_iter iter; + struct ust_error_accounting_entry *uid_entry; + uint64_t error_counter_index; + enum event_notifier_error_accounting_status status; + size_t dimension_index; + const uint64_t tracer_token = lttng_trigger_get_tracer_token(trigger); + + rcu_read_lock(); + status = get_error_counter_index_for_token(&ust_state, tracer_token, + &error_counter_index); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + uid_t trigger_owner_uid; + const char *trigger_name; + + get_trigger_info_for_log(trigger, &trigger_name, + &trigger_owner_uid); + + ERR("Failed to retrieve index for tracer token: token = %" PRIu64 ", trigger name = '%s', trigger owner uid = %d, status = %s", + tracer_token, trigger_name, + (int) trigger_owner_uid, + error_accounting_status_str(status)); + goto end; + } + + dimension_index = error_counter_index; + + /* + * Go over all error counters (ignoring uid) as a trigger (and trigger + * errors) can be generated from any applications that this session + * daemon is managing. + */ + cds_lfht_for_each_entry(error_counter_uid_ht->ht, &iter.iter, + uid_entry, node.node) { + const int ret = lttng_ust_ctl_counter_clear(uid_entry->daemon_counter, + &dimension_index); + + if (ret) { + uid_t trigger_owner_uid; + const char *trigger_name; + + get_trigger_info_for_log(trigger, &trigger_name, + &trigger_owner_uid); + ERR("Failed to clear event notifier counter value for trigger: counter uid = %d, trigger name = '%s', trigger owner uid = %d", + (int) uid_entry->node.key, trigger_name, + (int) trigger_owner_uid); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto end; + } + } + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; +end: + rcu_read_unlock(); + return status; +} +#endif /* HAVE_LIBLTTNG_UST_CTL */ + +static +enum event_notifier_error_accounting_status +event_notifier_error_accounting_kernel_clear( + const struct lttng_trigger *trigger) +{ + int ret; + uint64_t error_counter_index; + enum event_notifier_error_accounting_status status; + struct lttng_kernel_abi_counter_clear counter_clear = {}; + + status = get_error_counter_index_for_token(&kernel_state, + lttng_trigger_get_tracer_token(trigger), + &error_counter_index); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + uid_t trigger_owner_uid; + const char *trigger_name; + + get_trigger_info_for_log( + trigger, &trigger_name, &trigger_owner_uid); + + ERR("Failed to get event notifier error counter index: trigger owner uid = %d, trigger name = '%s', status = '%s'", + trigger_owner_uid, trigger_name, + error_accounting_status_str(status)); + goto end; + } + + counter_clear.index.number_dimensions = 1; + counter_clear.index.dimension_indexes[0] = error_counter_index; + + ret = kernctl_counter_clear( + kernel_error_accounting_entry.error_counter_fd, + &counter_clear); + if (ret) { + uid_t trigger_owner_uid; + const char *trigger_name; + + get_trigger_info_for_log( + trigger, &trigger_name, &trigger_owner_uid); + + ERR("Failed to clear kernel event notifier error counter: trigger owner uid = %d, trigger name = '%s'", + trigger_owner_uid, trigger_name); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto end; + } + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; +end: + return status; +} + +enum event_notifier_error_accounting_status +event_notifier_error_accounting_register_kernel( + int kernel_event_notifier_group_fd) +{ + int error_counter_fd = -1, ret; + enum event_notifier_error_accounting_status status; + lttng_kernel_abi_counter_conf error_counter_conf = { + .arithmetic = LTTNG_KERNEL_ABI_COUNTER_ARITHMETIC_MODULAR, + .bitness = sizeof(void *) == sizeof(uint32_t) ? + LTTNG_KERNEL_ABI_COUNTER_BITNESS_32 : + LTTNG_KERNEL_ABI_COUNTER_BITNESS_64, + .number_dimensions = 1, + .global_sum_step = 0, + }; + error_counter_conf.dimensions[0].size = kernel_state.number_indices; + error_counter_conf.dimensions[0].has_underflow = false; + error_counter_conf.dimensions[0].has_overflow = false; + + ret = kernctl_create_event_notifier_group_error_counter( + kernel_event_notifier_group_fd, &error_counter_conf); + if (ret < 0) { + PERROR("Failed to create event notifier group error counter through kernel ioctl: kernel_event_notifier_group_fd = %d", + kernel_event_notifier_group_fd); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto error; + } + + error_counter_fd = ret; + + /* Prevent fd duplication after execlp(). */ + ret = fcntl(error_counter_fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("Failed to set FD_CLOEXEC flag on event notifier error counter file descriptor: error_counter_fd = %d", + error_counter_fd); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto error; + } + + DBG("Created kernel event notifier group error counter: fd = %d", + error_counter_fd); + + kernel_error_accounting_entry.error_counter_fd = + error_counter_fd; + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; + +error: + return status; +} + +static +enum event_notifier_error_accounting_status create_error_counter_index_for_token( + struct error_accounting_state *state, uint64_t tracer_token, + uint64_t *error_counter_index) +{ + struct index_ht_entry *index_entry; + enum lttng_index_allocator_status index_alloc_status; + uint64_t local_error_counter_index; + enum event_notifier_error_accounting_status status; + + LTTNG_ASSERT(state); + + /* Allocate a new index for that counter. */ + index_alloc_status = lttng_index_allocator_alloc(state->index_allocator, + &local_error_counter_index); + switch (index_alloc_status) { + case LTTNG_INDEX_ALLOCATOR_STATUS_EMPTY: + DBG("No indices left in the configured event notifier error counter: " + "number-of-indices = %" PRIu64, + lttng_index_allocator_get_index_count( + state->index_allocator)); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NO_INDEX_AVAILABLE; + goto end; + case LTTNG_INDEX_ALLOCATOR_STATUS_OK: + break; + default: + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto end; + } + + index_entry = (index_ht_entry *) zmalloc(sizeof(*index_entry)); + if (index_entry == NULL) { + PERROR("Failed to allocate event notifier error counter hash table entry"); + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOMEM; + goto end; + } + + index_entry->error_counter_index = local_error_counter_index; + lttng_ht_node_init_u64(&index_entry->node, tracer_token); + lttng_ht_add_unique_u64(state->indices_ht, &index_entry->node); + + DBG("Allocated error counter index for tracer token: tracer token = %" PRIu64 ", index = %" PRIu64, + tracer_token, local_error_counter_index); + *error_counter_index = local_error_counter_index; + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; +end: + return status; +} + +enum event_notifier_error_accounting_status +event_notifier_error_accounting_register_event_notifier( + const struct lttng_trigger *trigger, + uint64_t *error_counter_index) +{ + enum event_notifier_error_accounting_status status; + uint64_t local_error_counter_index; + struct error_accounting_state *state; + + switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { + case LTTNG_DOMAIN_KERNEL: + state = &kernel_state; + break; + case LTTNG_DOMAIN_UST: + case LTTNG_DOMAIN_PYTHON: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + state = &ust_state; + break; + default: + abort(); + } + + /* + * Check if this event notifier already has a error counter index + * assigned. + */ + status = get_error_counter_index_for_token(state, + lttng_trigger_get_tracer_token(trigger), + &local_error_counter_index); + switch (status) { + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NOT_FOUND: + { + uid_t trigger_owner_uid; + const char *trigger_name; + + get_trigger_info_for_log( + trigger, &trigger_name, &trigger_owner_uid); + + DBG("Event notifier error counter index not found for tracer token (allocating a new one): trigger name = '%s', trigger owner uid = %d, tracer token = %" PRIu64, + trigger_name, trigger_owner_uid, + lttng_trigger_get_tracer_token(trigger)); + + status = create_error_counter_index_for_token(state, + lttng_trigger_get_tracer_token(trigger), + &local_error_counter_index); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + ERR("Error creating index for token: status = %s, trigger name = '%s', trigger owner uid = %d", + error_accounting_status_str(status), + trigger_name, trigger_owner_uid); + goto end; + } + /* fall-through. */ + } + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK: + *error_counter_index = local_error_counter_index; + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; + break; + default: + break; + } + +#ifdef HAVE_LIBLTTNG_UST_CTL + switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { + case LTTNG_DOMAIN_UST: + case LTTNG_DOMAIN_PYTHON: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + pthread_mutex_lock(&the_event_notifier_counter.lock); + the_event_notifier_counter.count++; + if (the_event_notifier_counter.count == 1) { + /* + * On the first event notifier, we get a reference to + * every existing UID entries. This ensures that the + * entries are kept around if there are still + * registered event notifiers but no apps. + */ + get_ref_all_ust_error_accounting_entry(); + } + pthread_mutex_unlock(&the_event_notifier_counter.lock); + break; + default: + break; + } +#endif /* HAVE_LIBLTTNG_UST_CTL */ + + +end: + return status; +} + +static +enum event_notifier_error_accounting_status +event_notifier_error_accounting_kernel_get_count( + const struct lttng_trigger *trigger, uint64_t *count) +{ + struct lttng_kernel_abi_counter_aggregate counter_aggregate = {}; + enum event_notifier_error_accounting_status status; + uint64_t error_counter_index; + int ret; + + status = get_error_counter_index_for_token(&kernel_state, + lttng_trigger_get_tracer_token(trigger), + &error_counter_index); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + ERR("Error getting index for token: status=%s", + error_accounting_status_str(status)); + goto end; + } + + counter_aggregate.index.number_dimensions = 1; + counter_aggregate.index.dimension_indexes[0] = error_counter_index; + + LTTNG_ASSERT(kernel_error_accounting_entry.error_counter_fd); + + ret = kernctl_counter_get_aggregate_value( + kernel_error_accounting_entry.error_counter_fd, + &counter_aggregate); + if (ret || counter_aggregate.value.value < 0) { + uid_t trigger_owner_uid; + const char *trigger_name; + + get_trigger_info_for_log(trigger, &trigger_name, + &trigger_owner_uid); + + if (counter_aggregate.value.value < 0) { + ERR("Invalid negative event notifier error counter value: trigger owner = %d, trigger name = '%s', value = %" PRId64, + trigger_owner_uid, trigger_name, + counter_aggregate.value.value); + } else { + ERR("Failed to getting event notifier error count: trigger owner = %d, trigger name = '%s', ret = %d", + trigger_owner_uid, trigger_name, ret); + } + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_ERR; + goto end; + } + + /* Error count can't be negative. */ + LTTNG_ASSERT(counter_aggregate.value.value >= 0); + *count = (uint64_t) counter_aggregate.value.value; + + status = EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; + +end: + return status; +} + +enum event_notifier_error_accounting_status +event_notifier_error_accounting_get_count( + const struct lttng_trigger *trigger, uint64_t *count) +{ + switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { + case LTTNG_DOMAIN_KERNEL: + return event_notifier_error_accounting_kernel_get_count( + trigger, count); + case LTTNG_DOMAIN_UST: + case LTTNG_DOMAIN_PYTHON: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: +#ifdef HAVE_LIBLTTNG_UST_CTL + return event_notifier_error_accounting_ust_get_count(trigger, + count); +#else + *count = 0; + return EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; +#endif /* HAVE_LIBLTTNG_UST_CTL */ + default: + abort(); + } +} + +static +enum event_notifier_error_accounting_status +event_notifier_error_accounting_clear(const struct lttng_trigger *trigger) +{ + switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { + case LTTNG_DOMAIN_KERNEL: + return event_notifier_error_accounting_kernel_clear(trigger); + case LTTNG_DOMAIN_UST: + case LTTNG_DOMAIN_PYTHON: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: +#ifdef HAVE_LIBLTTNG_UST_CTL + return event_notifier_error_accounting_ust_clear(trigger); +#else + return EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK; +#endif /* HAVE_LIBLTTNG_UST_CTL */ + default: + abort(); + } +} + +static void free_index_ht_entry(struct rcu_head *head) +{ + struct index_ht_entry *entry = caa_container_of(head, + struct index_ht_entry, rcu_head); + + free(entry); +} + +void event_notifier_error_accounting_unregister_event_notifier( + const struct lttng_trigger *trigger) +{ + struct lttng_ht_iter iter; + struct lttng_ht_node_u64 *node; + const uint64_t tracer_token = lttng_trigger_get_tracer_token(trigger); + enum event_notifier_error_accounting_status status; + struct error_accounting_state *state; + + status = event_notifier_error_accounting_clear(trigger); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + /* Trigger details already logged by callee on error. */ + ERR("Failed to clear event notifier error counter during unregistration of event notifier: status = '%s'", + error_accounting_status_str(status)); + goto end; + } + + rcu_read_lock(); + + switch (lttng_trigger_get_underlying_domain_type_restriction(trigger)) { + case LTTNG_DOMAIN_KERNEL: + state = &kernel_state; + break; +#ifdef HAVE_LIBLTTNG_UST_CTL + case LTTNG_DOMAIN_UST: + case LTTNG_DOMAIN_PYTHON: + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + state = &ust_state; + + pthread_mutex_lock(&the_event_notifier_counter.lock); + the_event_notifier_counter.count--; + if (the_event_notifier_counter.count == 0) { + + /* + * When unregistering the last event notifier, put one + * reference to every uid entries on the behalf of all + * event notifiers. + */ + put_ref_all_ust_error_accounting_entry(); + } + + pthread_mutex_unlock(&the_event_notifier_counter.lock); + + break; +#endif /* HAVE_LIBLTTNG_UST_CTL */ + default: + abort(); + } + + lttng_ht_lookup(state->indices_ht, &tracer_token, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (node) { + int del_ret; + struct index_ht_entry *index_entry = caa_container_of( + node, typeof(*index_entry), node); + enum lttng_index_allocator_status index_alloc_status; + + index_alloc_status = lttng_index_allocator_release( + state->index_allocator, + index_entry->error_counter_index); + if (index_alloc_status != LTTNG_INDEX_ALLOCATOR_STATUS_OK) { + uid_t trigger_owner_uid; + const char *trigger_name; + + get_trigger_info_for_log(trigger, &trigger_name, + &trigger_owner_uid); + + ERR("Failed to release event notifier error counter index: index = %" PRIu64 ", trigger name = '%s', trigger owner uid = %d", + index_entry->error_counter_index, + trigger_name, (int) trigger_owner_uid); + /* Don't exit, perform the rest of the clean-up. */ + } + + del_ret = lttng_ht_del(state->indices_ht, &iter); + LTTNG_ASSERT(!del_ret); + call_rcu(&index_entry->rcu_head, free_index_ht_entry); + } + +end: + rcu_read_unlock(); +} + +void event_notifier_error_accounting_fini(void) +{ + if (kernel_error_accounting_entry.error_counter_fd) { + const int ret = close(kernel_error_accounting_entry.error_counter_fd); + + if (ret) { + PERROR("Failed to close kernel event notifier error counter"); + } + } + + lttng_ht_destroy(error_counter_uid_ht); + + fini_error_accounting_state(&kernel_state); + fini_error_accounting_state(&ust_state); +} diff --git a/src/bin/lttng-sessiond/event.c b/src/bin/lttng-sessiond/event.c deleted file mode 100644 index 97bb69ba8..000000000 --- a/src/bin/lttng-sessiond/event.c +++ /dev/null @@ -1,928 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2016 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "channel.h" -#include "event.h" -#include "kernel.h" -#include "lttng-sessiond.h" -#include "lttng-ust-ctl.h" -#include "lttng-ust-error.h" -#include "ust-app.h" -#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. - */ -static void add_unique_ust_event(struct lttng_ht *ht, - struct ltt_ust_event *event) -{ - struct cds_lfht_node *node_ptr; - struct ltt_ust_ht_key key; - - LTTNG_ASSERT(ht); - LTTNG_ASSERT(ht->ht); - LTTNG_ASSERT(event); - - key.name = event->attr.name; - key.filter = (struct lttng_bytecode *) event->filter; - key.loglevel_type = event->attr.loglevel_type; - key.loglevel_value = event->attr.loglevel; - key.exclusion = event->exclusion; - - node_ptr = cds_lfht_add_unique(ht->ht, - ht->hash_fct(event->node.key, lttng_ht_seed), - trace_ust_ht_match_event, &key, &event->node.node); - LTTNG_ASSERT(node_ptr == &event->node.node); -} - -/* - * Disable kernel tracepoint events for a channel from the kernel session of - * a specified event_name and event type. - * On type LTTNG_EVENT_ALL all events with event_name are disabled. - * If event_name is NULL all events of the specified type are disabled. - */ -int event_kernel_disable_event(struct ltt_kernel_channel *kchan, - const char *event_name, enum lttng_event_type type) -{ - int ret, error = 0, found = 0; - struct ltt_kernel_event *kevent; - - LTTNG_ASSERT(kchan); - - /* For each event in the kernel session */ - cds_list_for_each_entry(kevent, &kchan->events_list.head, list) { - if (type != LTTNG_EVENT_ALL && kevent->type != type) - continue; - if (event_name != NULL && strcmp(event_name, kevent->event->name)) { - continue; - } - found++; - ret = kernel_disable_event(kevent); - if (ret < 0) { - error = 1; - continue; - } - } - DBG("Disable kernel event: found %d events with name: %s and type: %d", - found, event_name ? event_name : "NULL", type); - - if (event_name != NULL && !found) { - ret = LTTNG_ERR_NO_EVENT; - } else { - ret = error ? LTTNG_ERR_KERN_DISABLE_FAIL : LTTNG_OK; - } - - return ret; -} - -/* - * Enable kernel tracepoint event for a channel from the kernel session. - * We own filter_expression and filter. - */ -int event_kernel_enable_event(struct ltt_kernel_channel *kchan, - struct lttng_event *event, char *filter_expression, - struct lttng_bytecode *filter) -{ - int ret; - struct ltt_kernel_event *kevent; - - LTTNG_ASSERT(kchan); - LTTNG_ASSERT(event); - - kevent = trace_kernel_find_event(event->name, kchan, - event->type, filter); - if (kevent == NULL) { - ret = kernel_create_event(event, kchan, filter_expression, filter); - /* We have passed ownership */ - filter_expression = NULL; - filter = NULL; - if (ret) { - goto end; - } - } else if (kevent->enabled == 0) { - ret = kernel_enable_event(kevent); - if (ret < 0) { - ret = LTTNG_ERR_KERN_ENABLE_FAIL; - goto end; - } - } else { - /* At this point, the event is considered enabled */ - ret = LTTNG_ERR_KERN_EVENT_EXIST; - goto end; - } - - ret = LTTNG_OK; -end: - free(filter_expression); - free(filter); - return ret; -} - -/* - * ============================ - * UST : The Ultimate Frontier! - * ============================ - */ - -/* - * Enable UST tracepoint event for a channel from a UST session. - * We own filter_expression, filter, and exclusion. - */ -int event_ust_enable_tracepoint(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan, struct lttng_event *event, - char *filter_expression, - struct lttng_bytecode *filter, - struct lttng_event_exclusion *exclusion, - bool internal_event) -{ - int ret = LTTNG_OK, to_create = 0; - struct ltt_ust_event *uevent; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(uchan); - LTTNG_ASSERT(event); - - rcu_read_lock(); - - uevent = trace_ust_find_event(uchan->events, event->name, filter, - (enum lttng_ust_abi_loglevel_type) event->loglevel_type, - event->loglevel, exclusion); - if (!uevent) { - ret = trace_ust_create_event(event, filter_expression, - filter, exclusion, internal_event, &uevent); - /* We have passed ownership */ - filter_expression = NULL; - filter = NULL; - exclusion = NULL; - if (ret != LTTNG_OK) { - goto end; - } - - /* Valid to set it after the goto error since uevent is still NULL */ - to_create = 1; - } - - if (uevent->enabled) { - /* It's already enabled so everything is OK */ - LTTNG_ASSERT(!to_create); - ret = LTTNG_ERR_UST_EVENT_ENABLED; - goto end; - } - - uevent->enabled = 1; - if (to_create) { - /* Add ltt ust event to channel */ - add_unique_ust_event(uchan->events, uevent); - } - - if (!usess->active) { - goto end; - } - - if (to_create) { - /* Create event on all UST registered apps for session */ - ret = ust_app_create_event_glb(usess, uchan, uevent); - } else { - /* Enable event on all UST registered apps for session */ - ret = ust_app_enable_event_glb(usess, uchan, uevent); - } - - if (ret < 0) { - if (ret == -LTTNG_UST_ERR_EXIST) { - ret = LTTNG_ERR_UST_EVENT_EXIST; - } else { - ret = LTTNG_ERR_UST_ENABLE_FAIL; - } - goto end; - } - - DBG("Event UST %s %s in channel %s", uevent->attr.name, - to_create ? "created" : "enabled", uchan->name); - - ret = LTTNG_OK; - -end: - rcu_read_unlock(); - free(filter_expression); - free(filter); - free(exclusion); - return ret; -} - -/* - * Disable UST tracepoint of a channel from a UST session. - */ -int event_ust_disable_tracepoint(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan, const char *event_name) -{ - int ret; - struct ltt_ust_event *uevent; - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - struct lttng_ht *ht; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(uchan); - LTTNG_ASSERT(event_name); - - ht = uchan->events; - - rcu_read_lock(); - - /* - * We use a custom lookup since we need the iterator for the next_duplicate - * call in the do while loop below. - */ - cds_lfht_lookup(ht->ht, ht->hash_fct((void *) event_name, lttng_ht_seed), - trace_ust_ht_match_event_by_name, event_name, &iter.iter); - node = lttng_ht_iter_get_node_str(&iter); - if (node == NULL) { - DBG2("Trace UST event NOT found by name %s", event_name); - ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; - goto error; - } - - do { - uevent = caa_container_of(node, struct ltt_ust_event, node); - LTTNG_ASSERT(uevent); - - if (uevent->enabled == 0) { - /* It's already disabled so everything is OK */ - goto next; - } - uevent->enabled = 0; - DBG2("Event UST %s disabled in channel %s", uevent->attr.name, - uchan->name); - - if (!usess->active) { - goto next; - } - ret = ust_app_disable_event_glb(usess, uchan, uevent); - if (ret < 0 && ret != -LTTNG_UST_ERR_EXIST) { - ret = LTTNG_ERR_UST_DISABLE_FAIL; - goto error; - } -next: - /* Get next duplicate event by name. */ - cds_lfht_next_duplicate(ht->ht, trace_ust_ht_match_event_by_name, - event_name, &iter.iter); - node = lttng_ht_iter_get_node_str(&iter); - } while (node); - - ret = LTTNG_OK; - -error: - rcu_read_unlock(); - return ret; -} - -/* - * Disable all UST tracepoints for a channel from a UST session. - */ -int event_ust_disable_all_tracepoints(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan) -{ - int ret, i, size, error = 0; - struct lttng_ht_iter iter; - struct ltt_ust_event *uevent = NULL; - struct lttng_event *events = NULL; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(uchan); - - rcu_read_lock(); - - /* Disabling existing events */ - cds_lfht_for_each_entry(uchan->events->ht, &iter.iter, uevent, - node.node) { - if (uevent->enabled == 1) { - ret = event_ust_disable_tracepoint(usess, uchan, - uevent->attr.name); - if (ret < 0) { - error = LTTNG_ERR_UST_DISABLE_FAIL; - continue; - } - } - } - - /* Get all UST available events */ - size = ust_app_list_events(&events); - if (size < 0) { - ret = LTTNG_ERR_UST_LIST_FAIL; - goto error; - } - - for (i = 0; i < size; i++) { - ret = event_ust_disable_tracepoint(usess, uchan, - events[i].name); - if (ret < 0) { - /* Continue to disable the rest... */ - error = LTTNG_ERR_UST_DISABLE_FAIL; - continue; - } - } - - ret = error ? error : LTTNG_OK; -error: - rcu_read_unlock(); - free(events); - return ret; -} - -static void agent_enable_all(struct agent *agt) -{ - struct agent_event *aevent; - struct lttng_ht_iter iter; - - /* Flag every event as enabled. */ - rcu_read_lock(); - cds_lfht_for_each_entry ( - agt->events->ht, &iter.iter, aevent, node.node) { - aevent->enabled_count++; - } - rcu_read_unlock(); -} - -/* - * Enable all agent event for a given UST session. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -int event_agent_enable_all(struct ltt_ust_session *usess, - struct agent *agt, struct lttng_event *event, - struct lttng_bytecode *filter ,char *filter_expression) -{ - int ret; - - LTTNG_ASSERT(usess); - - DBG("Event agent enabling ALL events for session %" PRIu64, usess->id); - - /* Enable event on agent application through TCP socket. */ - ret = event_agent_enable(usess, agt, event, filter, filter_expression); - if (ret != LTTNG_OK) { - goto error; - } - - agent_enable_all(agt); - - ret = LTTNG_OK; - -error: - return ret; -} - -/* - * Check if this event's filter requires the activation of application contexts - * and enable them in the agent. - * TODO: bytecode iterator does not support non-legacy application - * contexts yet. Not an issue for now, since they are not generated by - * the lttng-ctl library. - */ -static int add_filter_app_ctx(struct lttng_bytecode *bytecode, - const char *filter_expression, struct agent *agt) -{ - int ret = LTTNG_OK; - char *provider_name = NULL, *ctx_name = NULL; - struct bytecode_symbol_iterator *it = - bytecode_symbol_iterator_create(bytecode); - - if (!it) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - - do { - struct lttng_event_context ctx; - const char *symbol_name = - bytecode_symbol_iterator_get_name(it); - - if (parse_application_context(symbol_name, &provider_name, - &ctx_name)) { - /* Not an application context. */ - continue; - } - - ctx.ctx = LTTNG_EVENT_CONTEXT_APP_CONTEXT; - ctx.u.app_ctx.provider_name = provider_name; - ctx.u.app_ctx.ctx_name = ctx_name; - - /* Recognized an application context. */ - DBG("Enabling event with filter expression \"%s\" requires enabling the %s:%s application context.", - filter_expression, provider_name, ctx_name); - - ret = agent_add_context(&ctx, agt); - if (ret != LTTNG_OK) { - ERR("Failed to add application context %s:%s.", - provider_name, ctx_name); - goto end; - } - - ret = agent_enable_context(&ctx, agt->domain); - if (ret != LTTNG_OK) { - ERR("Failed to enable application context %s:%s.", - provider_name, ctx_name); - goto end; - } - - free(provider_name); - free(ctx_name); - provider_name = ctx_name = NULL; - } while (bytecode_symbol_iterator_next(it) == 0); -end: - free(provider_name); - free(ctx_name); - bytecode_symbol_iterator_destroy(it); - return ret; -} - -static int agent_enable(struct agent *agt, - struct lttng_event *event, - struct lttng_bytecode *filter, - char *filter_expression) -{ - int ret, created = 0; - struct agent_event *aevent; - - LTTNG_ASSERT(event); - LTTNG_ASSERT(agt); - - aevent = agent_find_event(event->name, event->loglevel_type, - event->loglevel, filter_expression, agt); - if (!aevent) { - aevent = agent_create_event(event->name, event->loglevel_type, - event->loglevel, filter, - filter_expression); - if (!aevent) { - ret = LTTNG_ERR_NOMEM; - goto error; - } - filter = NULL; - filter_expression = NULL; - created = 1; - LTTNG_ASSERT(!AGENT_EVENT_IS_ENABLED(aevent)); - } - - if (created && aevent->filter) { - ret = add_filter_app_ctx( - aevent->filter, aevent->filter_expression, agt); - if (ret != LTTNG_OK) { - goto error; - } - } - - /* Already enabled? */ - if (AGENT_EVENT_IS_ENABLED(aevent)) { - ret = LTTNG_OK; - goto end; - } - - ret = agent_enable_event(aevent, agt->domain); - if (ret != LTTNG_OK) { - goto error; - } - - /* If the event was created prior to the enable, add it to the domain. */ - if (created) { - agent_add_event(aevent, agt); - } - - ret = LTTNG_OK; - goto end; - -error: - if (created) { - agent_destroy_event(aevent); - } -end: - free(filter); - free(filter_expression); - return ret; -} - -/* - * Enable a single agent event for a given UST session. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -int event_agent_enable(struct ltt_ust_session *usess, - struct agent *agt, - struct lttng_event *event, - struct lttng_bytecode *filter, - char *filter_expression) -{ - LTTNG_ASSERT(usess); - LTTNG_ASSERT(event); - LTTNG_ASSERT(agt); - - DBG("Enabling agent event: event pattern = '%s', session id = %" PRIu64 ", loglevel type = %d, loglevel = %d, filter expression = '%s'", - event->name, usess->id, event->loglevel_type, - event->loglevel, - filter_expression ? filter_expression : "(none)"); - - 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_trigger_status t_status; - enum lttng_domain_type d_type; - const struct lttng_condition *condition; - const struct lttng_event_rule *rule; - const char *filter_expression; - char *filter_expression_copy = NULL; - const struct lttng_bytecode *filter_bytecode; - struct lttng_bytecode *filter_bytecode_copy = NULL; - struct lttng_event *event = NULL; - uid_t trigger_owner_uid = 0; - const char *trigger_name; - - LTTNG_ASSERT(trigger); - LTTNG_ASSERT(agt); - - t_status = lttng_trigger_get_name(trigger, &trigger_name); - if (t_status != LTTNG_TRIGGER_STATUS_OK) { - trigger_name = "(anonymous)"; - } - - t_status = lttng_trigger_get_owner_uid(trigger, &trigger_owner_uid); - LTTNG_ASSERT(t_status == LTTNG_TRIGGER_STATUS_OK); - - condition = lttng_trigger_get_const_condition(trigger); - - LTTNG_ASSERT(lttng_condition_get_type(condition) == - LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); - - c_status = lttng_condition_event_rule_matches_get_rule( - condition, &rule); - LTTNG_ASSERT(c_status == LTTNG_CONDITION_STATUS_OK); - - switch (lttng_event_rule_get_type(rule)) { - case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING: - case LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING: - case LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING: - break; - default: - abort(); - break; - } - - d_type = lttng_event_rule_get_domain_type(rule); - LTTNG_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 = - lttng_bytecode_copy(filter_bytecode); - if (!filter_bytecode_copy) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - } - } - - DBG("Enabling agent event from trigger: trigger name = '%s', trigger owner uid = %d, token = %" PRIu64, - trigger_name, trigger_owner_uid, - lttng_trigger_get_tracer_token(trigger)); - - 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. - */ -const char *event_get_default_agent_ust_name(enum lttng_domain_type domain) -{ - const char *default_event_name = NULL; - - switch (domain) { - case LTTNG_DOMAIN_LOG4J: - default_event_name = DEFAULT_LOG4J_EVENT_NAME; - break; - case LTTNG_DOMAIN_JUL: - default_event_name = DEFAULT_JUL_EVENT_NAME; - break; - case LTTNG_DOMAIN_PYTHON: - default_event_name = DEFAULT_PYTHON_EVENT_NAME; - break; - default: - abort(); - } - - return default_event_name; -} - -static int trigger_agent_disable_one(const struct lttng_trigger *trigger, - struct agent *agt, - struct agent_event *aevent) - -{ - int ret; - - LTTNG_ASSERT(agt); - LTTNG_ASSERT(trigger); - LTTNG_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_tracer_token(trigger)); - - /* Already disabled? */ - if (!AGENT_EVENT_IS_ENABLED(aevent)) { - 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. - * - * Must be called with the RCU read lock held. - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -static int event_agent_disable_one(struct ltt_ust_session *usess, - struct agent *agt, struct agent_event *aevent) -{ - int ret; - struct ltt_ust_event *uevent = NULL; - struct ltt_ust_channel *uchan = NULL; - const char *ust_event_name, *ust_channel_name; - - LTTNG_ASSERT(agt); - LTTNG_ASSERT(usess); - LTTNG_ASSERT(aevent); - - DBG("Event agent disabling %s (loglevel type %d, loglevel value %d) for session %" PRIu64, - aevent->name, aevent->loglevel_type, aevent->loglevel_value, - usess->id); - - /* Already disabled? */ - if (!AGENT_EVENT_IS_ENABLED(aevent)) { - goto end; - } - - if (agt->domain == LTTNG_DOMAIN_JUL) { - ust_channel_name = DEFAULT_JUL_CHANNEL_NAME; - } else if (agt->domain == LTTNG_DOMAIN_LOG4J) { - ust_channel_name = DEFAULT_LOG4J_CHANNEL_NAME; - } else if (agt->domain == LTTNG_DOMAIN_PYTHON) { - ust_channel_name = DEFAULT_PYTHON_CHANNEL_NAME; - } else { - ret = LTTNG_ERR_INVALID; - goto error; - } - - /* - * Disable it on the UST side. First get the channel reference then find - * the event and finally disable it. - */ - uchan = trace_ust_find_channel_by_name(usess->domain_global.channels, - (char *) ust_channel_name); - if (!uchan) { - ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; - goto error; - } - - ust_event_name = event_get_default_agent_ust_name(agt->domain); - if (!ust_event_name) { - ret = LTTNG_ERR_FATAL; - goto error; - } - - /* - * Agent UST event has its loglevel type forced to - * LTTNG_UST_LOGLEVEL_ALL. The actual loglevel type/value filtering - * happens thanks to an UST filter. The following -1 is actually - * ignored since the type is LTTNG_UST_LOGLEVEL_ALL. - */ - uevent = trace_ust_find_event(uchan->events, (char *) ust_event_name, - aevent->filter, LTTNG_UST_ABI_LOGLEVEL_ALL, -1, NULL); - /* If the agent event exists, it must be available on the UST side. */ - LTTNG_ASSERT(uevent); - - if (usess->active) { - ret = ust_app_disable_event_glb(usess, uchan, uevent); - if (ret < 0 && ret != -LTTNG_UST_ERR_EXIST) { - ret = LTTNG_ERR_UST_DISABLE_FAIL; - goto error; - } - } - - /* - * Flag event that it's disabled so the shadow copy on the ust app side - * will disable it if an application shows up. - */ - uevent->enabled = 0; - - ret = agent_disable_event(aevent, agt->domain); - if (ret != LTTNG_OK) { - goto error; - } - -end: - return LTTNG_OK; - -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; - - LTTNG_ASSERT(trigger); - LTTNG_ASSERT(agt); - - DBG("Event agent disabling for trigger %" PRIu64, - lttng_trigger_get_tracer_token(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_tracer_token(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. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -int event_agent_disable(struct ltt_ust_session *usess, struct agent *agt, - const char *event_name) -{ - int ret = LTTNG_OK; - struct agent_event *aevent; - struct lttng_ht_iter iter; - struct lttng_ht_node_str *node; - - LTTNG_ASSERT(agt); - LTTNG_ASSERT(usess); - LTTNG_ASSERT(event_name); - - DBG("Event agent disabling %s (all loglevels) for session %" PRIu64, event_name, usess->id); - - rcu_read_lock(); - agent_find_events_by_name(event_name, agt, &iter); - node = lttng_ht_iter_get_node_str(&iter); - - if (node == NULL) { - DBG2("Event agent NOT found by name %s", event_name); - ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; - goto end; - } - - do { - aevent = caa_container_of(node, struct agent_event, node); - ret = event_agent_disable_one(usess, agt, aevent); - - if (ret != LTTNG_OK) { - goto end; - } - - /* Get next duplicate agent event by name. */ - agent_event_next_duplicate(event_name, agt, &iter); - node = lttng_ht_iter_get_node_str(&iter); - } while (node); -end: - rcu_read_unlock(); - return ret; -} -/* - * Disable all agent event for a given UST session. - * - * Return LTTNG_OK on success or else a LTTNG_ERR* code. - */ -int event_agent_disable_all(struct ltt_ust_session *usess, - struct agent *agt) -{ - int ret; - struct agent_event *aevent; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(agt); - LTTNG_ASSERT(usess); - - /* - * Disable event on agent application. Continue to disable all other events - * if the * event is not found. - */ - ret = event_agent_disable(usess, agt, "*"); - if (ret != LTTNG_OK && ret != LTTNG_ERR_UST_EVENT_NOT_FOUND) { - goto error; - } - - /* Disable every event. */ - rcu_read_lock(); - cds_lfht_for_each_entry(agt->events->ht, &iter.iter, aevent, - node.node) { - if (!AGENT_EVENT_IS_ENABLED(aevent)) { - continue; - } - - ret = event_agent_disable(usess, agt, aevent->name); - if (ret != LTTNG_OK) { - goto error_unlock; - } - } - ret = LTTNG_OK; - -error_unlock: - rcu_read_unlock(); -error: - return ret; -} diff --git a/src/bin/lttng-sessiond/event.cpp b/src/bin/lttng-sessiond/event.cpp new file mode 100644 index 000000000..284a8c472 --- /dev/null +++ b/src/bin/lttng-sessiond/event.cpp @@ -0,0 +1,928 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2016 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "channel.h" +#include "event.h" +#include "kernel.h" +#include "lttng-sessiond.h" +#include "lttng-ust-ctl.h" +#include "lttng-ust-error.h" +#include "ust-app.h" +#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. + */ +static void add_unique_ust_event(struct lttng_ht *ht, + struct ltt_ust_event *event) +{ + struct cds_lfht_node *node_ptr; + struct ltt_ust_ht_key key; + + LTTNG_ASSERT(ht); + LTTNG_ASSERT(ht->ht); + LTTNG_ASSERT(event); + + key.name = event->attr.name; + key.filter = (struct lttng_bytecode *) event->filter; + key.loglevel_type = (lttng_ust_abi_loglevel_type) event->attr.loglevel_type; + key.loglevel_value = event->attr.loglevel; + key.exclusion = event->exclusion; + + node_ptr = cds_lfht_add_unique(ht->ht, + ht->hash_fct(event->node.key, lttng_ht_seed), + trace_ust_ht_match_event, &key, &event->node.node); + LTTNG_ASSERT(node_ptr == &event->node.node); +} + +/* + * Disable kernel tracepoint events for a channel from the kernel session of + * a specified event_name and event type. + * On type LTTNG_EVENT_ALL all events with event_name are disabled. + * If event_name is NULL all events of the specified type are disabled. + */ +int event_kernel_disable_event(struct ltt_kernel_channel *kchan, + const char *event_name, enum lttng_event_type type) +{ + int ret, error = 0, found = 0; + struct ltt_kernel_event *kevent; + + LTTNG_ASSERT(kchan); + + /* For each event in the kernel session */ + cds_list_for_each_entry(kevent, &kchan->events_list.head, list) { + if (type != LTTNG_EVENT_ALL && kevent->type != type) + continue; + if (event_name != NULL && strcmp(event_name, kevent->event->name)) { + continue; + } + found++; + ret = kernel_disable_event(kevent); + if (ret < 0) { + error = 1; + continue; + } + } + DBG("Disable kernel event: found %d events with name: %s and type: %d", + found, event_name ? event_name : "NULL", type); + + if (event_name != NULL && !found) { + ret = LTTNG_ERR_NO_EVENT; + } else { + ret = error ? LTTNG_ERR_KERN_DISABLE_FAIL : LTTNG_OK; + } + + return ret; +} + +/* + * Enable kernel tracepoint event for a channel from the kernel session. + * We own filter_expression and filter. + */ +int event_kernel_enable_event(struct ltt_kernel_channel *kchan, + struct lttng_event *event, char *filter_expression, + struct lttng_bytecode *filter) +{ + int ret; + struct ltt_kernel_event *kevent; + + LTTNG_ASSERT(kchan); + LTTNG_ASSERT(event); + + kevent = trace_kernel_find_event(event->name, kchan, + event->type, filter); + if (kevent == NULL) { + ret = kernel_create_event(event, kchan, filter_expression, filter); + /* We have passed ownership */ + filter_expression = NULL; + filter = NULL; + if (ret) { + goto end; + } + } else if (kevent->enabled == 0) { + ret = kernel_enable_event(kevent); + if (ret < 0) { + ret = LTTNG_ERR_KERN_ENABLE_FAIL; + goto end; + } + } else { + /* At this point, the event is considered enabled */ + ret = LTTNG_ERR_KERN_EVENT_EXIST; + goto end; + } + + ret = LTTNG_OK; +end: + free(filter_expression); + free(filter); + return ret; +} + +/* + * ============================ + * UST : The Ultimate Frontier! + * ============================ + */ + +/* + * Enable UST tracepoint event for a channel from a UST session. + * We own filter_expression, filter, and exclusion. + */ +int event_ust_enable_tracepoint(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan, struct lttng_event *event, + char *filter_expression, + struct lttng_bytecode *filter, + struct lttng_event_exclusion *exclusion, + bool internal_event) +{ + int ret = LTTNG_OK, to_create = 0; + struct ltt_ust_event *uevent; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(uchan); + LTTNG_ASSERT(event); + + rcu_read_lock(); + + uevent = trace_ust_find_event(uchan->events, event->name, filter, + (enum lttng_ust_abi_loglevel_type) event->loglevel_type, + event->loglevel, exclusion); + if (!uevent) { + ret = trace_ust_create_event(event, filter_expression, + filter, exclusion, internal_event, &uevent); + /* We have passed ownership */ + filter_expression = NULL; + filter = NULL; + exclusion = NULL; + if (ret != LTTNG_OK) { + goto end; + } + + /* Valid to set it after the goto error since uevent is still NULL */ + to_create = 1; + } + + if (uevent->enabled) { + /* It's already enabled so everything is OK */ + LTTNG_ASSERT(!to_create); + ret = LTTNG_ERR_UST_EVENT_ENABLED; + goto end; + } + + uevent->enabled = 1; + if (to_create) { + /* Add ltt ust event to channel */ + add_unique_ust_event(uchan->events, uevent); + } + + if (!usess->active) { + goto end; + } + + if (to_create) { + /* Create event on all UST registered apps for session */ + ret = ust_app_create_event_glb(usess, uchan, uevent); + } else { + /* Enable event on all UST registered apps for session */ + ret = ust_app_enable_event_glb(usess, uchan, uevent); + } + + if (ret < 0) { + if (ret == -LTTNG_UST_ERR_EXIST) { + ret = LTTNG_ERR_UST_EVENT_EXIST; + } else { + ret = LTTNG_ERR_UST_ENABLE_FAIL; + } + goto end; + } + + DBG("Event UST %s %s in channel %s", uevent->attr.name, + to_create ? "created" : "enabled", uchan->name); + + ret = LTTNG_OK; + +end: + rcu_read_unlock(); + free(filter_expression); + free(filter); + free(exclusion); + return ret; +} + +/* + * Disable UST tracepoint of a channel from a UST session. + */ +int event_ust_disable_tracepoint(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan, const char *event_name) +{ + int ret; + struct ltt_ust_event *uevent; + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + struct lttng_ht *ht; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(uchan); + LTTNG_ASSERT(event_name); + + ht = uchan->events; + + rcu_read_lock(); + + /* + * We use a custom lookup since we need the iterator for the next_duplicate + * call in the do while loop below. + */ + cds_lfht_lookup(ht->ht, ht->hash_fct((void *) event_name, lttng_ht_seed), + trace_ust_ht_match_event_by_name, event_name, &iter.iter); + node = lttng_ht_iter_get_node_str(&iter); + if (node == NULL) { + DBG2("Trace UST event NOT found by name %s", event_name); + ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; + goto error; + } + + do { + uevent = caa_container_of(node, struct ltt_ust_event, node); + LTTNG_ASSERT(uevent); + + if (uevent->enabled == 0) { + /* It's already disabled so everything is OK */ + goto next; + } + uevent->enabled = 0; + DBG2("Event UST %s disabled in channel %s", uevent->attr.name, + uchan->name); + + if (!usess->active) { + goto next; + } + ret = ust_app_disable_event_glb(usess, uchan, uevent); + if (ret < 0 && ret != -LTTNG_UST_ERR_EXIST) { + ret = LTTNG_ERR_UST_DISABLE_FAIL; + goto error; + } +next: + /* Get next duplicate event by name. */ + cds_lfht_next_duplicate(ht->ht, trace_ust_ht_match_event_by_name, + event_name, &iter.iter); + node = lttng_ht_iter_get_node_str(&iter); + } while (node); + + ret = LTTNG_OK; + +error: + rcu_read_unlock(); + return ret; +} + +/* + * Disable all UST tracepoints for a channel from a UST session. + */ +int event_ust_disable_all_tracepoints(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan) +{ + int ret, i, size, error = 0; + struct lttng_ht_iter iter; + struct ltt_ust_event *uevent = NULL; + struct lttng_event *events = NULL; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(uchan); + + rcu_read_lock(); + + /* Disabling existing events */ + cds_lfht_for_each_entry(uchan->events->ht, &iter.iter, uevent, + node.node) { + if (uevent->enabled == 1) { + ret = event_ust_disable_tracepoint(usess, uchan, + uevent->attr.name); + if (ret < 0) { + error = LTTNG_ERR_UST_DISABLE_FAIL; + continue; + } + } + } + + /* Get all UST available events */ + size = ust_app_list_events(&events); + if (size < 0) { + ret = LTTNG_ERR_UST_LIST_FAIL; + goto error; + } + + for (i = 0; i < size; i++) { + ret = event_ust_disable_tracepoint(usess, uchan, + events[i].name); + if (ret < 0) { + /* Continue to disable the rest... */ + error = LTTNG_ERR_UST_DISABLE_FAIL; + continue; + } + } + + ret = error ? error : LTTNG_OK; +error: + rcu_read_unlock(); + free(events); + return ret; +} + +static void agent_enable_all(struct agent *agt) +{ + struct agent_event *aevent; + struct lttng_ht_iter iter; + + /* Flag every event as enabled. */ + rcu_read_lock(); + cds_lfht_for_each_entry ( + agt->events->ht, &iter.iter, aevent, node.node) { + aevent->enabled_count++; + } + rcu_read_unlock(); +} + +/* + * Enable all agent event for a given UST session. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int event_agent_enable_all(struct ltt_ust_session *usess, + struct agent *agt, struct lttng_event *event, + struct lttng_bytecode *filter ,char *filter_expression) +{ + int ret; + + LTTNG_ASSERT(usess); + + DBG("Event agent enabling ALL events for session %" PRIu64, usess->id); + + /* Enable event on agent application through TCP socket. */ + ret = event_agent_enable(usess, agt, event, filter, filter_expression); + if (ret != LTTNG_OK) { + goto error; + } + + agent_enable_all(agt); + + ret = LTTNG_OK; + +error: + return ret; +} + +/* + * Check if this event's filter requires the activation of application contexts + * and enable them in the agent. + * TODO: bytecode iterator does not support non-legacy application + * contexts yet. Not an issue for now, since they are not generated by + * the lttng-ctl library. + */ +static int add_filter_app_ctx(struct lttng_bytecode *bytecode, + const char *filter_expression, struct agent *agt) +{ + int ret = LTTNG_OK; + char *provider_name = NULL, *ctx_name = NULL; + struct bytecode_symbol_iterator *it = + bytecode_symbol_iterator_create(bytecode); + + if (!it) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + do { + struct lttng_event_context ctx; + const char *symbol_name = + bytecode_symbol_iterator_get_name(it); + + if (parse_application_context(symbol_name, &provider_name, + &ctx_name)) { + /* Not an application context. */ + continue; + } + + ctx.ctx = LTTNG_EVENT_CONTEXT_APP_CONTEXT; + ctx.u.app_ctx.provider_name = provider_name; + ctx.u.app_ctx.ctx_name = ctx_name; + + /* Recognized an application context. */ + DBG("Enabling event with filter expression \"%s\" requires enabling the %s:%s application context.", + filter_expression, provider_name, ctx_name); + + ret = agent_add_context(&ctx, agt); + if (ret != LTTNG_OK) { + ERR("Failed to add application context %s:%s.", + provider_name, ctx_name); + goto end; + } + + ret = agent_enable_context(&ctx, agt->domain); + if (ret != LTTNG_OK) { + ERR("Failed to enable application context %s:%s.", + provider_name, ctx_name); + goto end; + } + + free(provider_name); + free(ctx_name); + provider_name = ctx_name = NULL; + } while (bytecode_symbol_iterator_next(it) == 0); +end: + free(provider_name); + free(ctx_name); + bytecode_symbol_iterator_destroy(it); + return ret; +} + +static int agent_enable(struct agent *agt, + struct lttng_event *event, + struct lttng_bytecode *filter, + char *filter_expression) +{ + int ret, created = 0; + struct agent_event *aevent; + + LTTNG_ASSERT(event); + LTTNG_ASSERT(agt); + + aevent = agent_find_event(event->name, event->loglevel_type, + event->loglevel, filter_expression, agt); + if (!aevent) { + aevent = agent_create_event(event->name, event->loglevel_type, + event->loglevel, filter, + filter_expression); + if (!aevent) { + ret = LTTNG_ERR_NOMEM; + goto error; + } + filter = NULL; + filter_expression = NULL; + created = 1; + LTTNG_ASSERT(!AGENT_EVENT_IS_ENABLED(aevent)); + } + + if (created && aevent->filter) { + ret = add_filter_app_ctx( + aevent->filter, aevent->filter_expression, agt); + if (ret != LTTNG_OK) { + goto error; + } + } + + /* Already enabled? */ + if (AGENT_EVENT_IS_ENABLED(aevent)) { + ret = LTTNG_OK; + goto end; + } + + ret = agent_enable_event(aevent, agt->domain); + if (ret != LTTNG_OK) { + goto error; + } + + /* If the event was created prior to the enable, add it to the domain. */ + if (created) { + agent_add_event(aevent, agt); + } + + ret = LTTNG_OK; + goto end; + +error: + if (created) { + agent_destroy_event(aevent); + } +end: + free(filter); + free(filter_expression); + return ret; +} + +/* + * Enable a single agent event for a given UST session. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int event_agent_enable(struct ltt_ust_session *usess, + struct agent *agt, + struct lttng_event *event, + struct lttng_bytecode *filter, + char *filter_expression) +{ + LTTNG_ASSERT(usess); + LTTNG_ASSERT(event); + LTTNG_ASSERT(agt); + + DBG("Enabling agent event: event pattern = '%s', session id = %" PRIu64 ", loglevel type = %d, loglevel = %d, filter expression = '%s'", + event->name, usess->id, event->loglevel_type, + event->loglevel, + filter_expression ? filter_expression : "(none)"); + + 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_trigger_status t_status; + enum lttng_domain_type d_type; + const struct lttng_condition *condition; + const struct lttng_event_rule *rule; + const char *filter_expression; + char *filter_expression_copy = NULL; + const struct lttng_bytecode *filter_bytecode; + struct lttng_bytecode *filter_bytecode_copy = NULL; + struct lttng_event *event = NULL; + uid_t trigger_owner_uid = 0; + const char *trigger_name; + + LTTNG_ASSERT(trigger); + LTTNG_ASSERT(agt); + + t_status = lttng_trigger_get_name(trigger, &trigger_name); + if (t_status != LTTNG_TRIGGER_STATUS_OK) { + trigger_name = "(anonymous)"; + } + + t_status = lttng_trigger_get_owner_uid(trigger, &trigger_owner_uid); + LTTNG_ASSERT(t_status == LTTNG_TRIGGER_STATUS_OK); + + condition = lttng_trigger_get_const_condition(trigger); + + LTTNG_ASSERT(lttng_condition_get_type(condition) == + LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); + + c_status = lttng_condition_event_rule_matches_get_rule( + condition, &rule); + LTTNG_ASSERT(c_status == LTTNG_CONDITION_STATUS_OK); + + switch (lttng_event_rule_get_type(rule)) { + case LTTNG_EVENT_RULE_TYPE_JUL_LOGGING: + case LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING: + case LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING: + break; + default: + abort(); + break; + } + + d_type = lttng_event_rule_get_domain_type(rule); + LTTNG_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 = + lttng_bytecode_copy(filter_bytecode); + if (!filter_bytecode_copy) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + } + } + + DBG("Enabling agent event from trigger: trigger name = '%s', trigger owner uid = %d, token = %" PRIu64, + trigger_name, trigger_owner_uid, + lttng_trigger_get_tracer_token(trigger)); + + 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. + */ +const char *event_get_default_agent_ust_name(enum lttng_domain_type domain) +{ + const char *default_event_name = NULL; + + switch (domain) { + case LTTNG_DOMAIN_LOG4J: + default_event_name = DEFAULT_LOG4J_EVENT_NAME; + break; + case LTTNG_DOMAIN_JUL: + default_event_name = DEFAULT_JUL_EVENT_NAME; + break; + case LTTNG_DOMAIN_PYTHON: + default_event_name = DEFAULT_PYTHON_EVENT_NAME; + break; + default: + abort(); + } + + return default_event_name; +} + +static int trigger_agent_disable_one(const struct lttng_trigger *trigger, + struct agent *agt, + struct agent_event *aevent) + +{ + int ret; + + LTTNG_ASSERT(agt); + LTTNG_ASSERT(trigger); + LTTNG_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_tracer_token(trigger)); + + /* Already disabled? */ + if (!AGENT_EVENT_IS_ENABLED(aevent)) { + 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. + * + * Must be called with the RCU read lock held. + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +static int event_agent_disable_one(struct ltt_ust_session *usess, + struct agent *agt, struct agent_event *aevent) +{ + int ret; + struct ltt_ust_event *uevent = NULL; + struct ltt_ust_channel *uchan = NULL; + const char *ust_event_name, *ust_channel_name; + + LTTNG_ASSERT(agt); + LTTNG_ASSERT(usess); + LTTNG_ASSERT(aevent); + + DBG("Event agent disabling %s (loglevel type %d, loglevel value %d) for session %" PRIu64, + aevent->name, aevent->loglevel_type, aevent->loglevel_value, + usess->id); + + /* Already disabled? */ + if (!AGENT_EVENT_IS_ENABLED(aevent)) { + goto end; + } + + if (agt->domain == LTTNG_DOMAIN_JUL) { + ust_channel_name = DEFAULT_JUL_CHANNEL_NAME; + } else if (agt->domain == LTTNG_DOMAIN_LOG4J) { + ust_channel_name = DEFAULT_LOG4J_CHANNEL_NAME; + } else if (agt->domain == LTTNG_DOMAIN_PYTHON) { + ust_channel_name = DEFAULT_PYTHON_CHANNEL_NAME; + } else { + ret = LTTNG_ERR_INVALID; + goto error; + } + + /* + * Disable it on the UST side. First get the channel reference then find + * the event and finally disable it. + */ + uchan = trace_ust_find_channel_by_name(usess->domain_global.channels, + (char *) ust_channel_name); + if (!uchan) { + ret = LTTNG_ERR_UST_CHAN_NOT_FOUND; + goto error; + } + + ust_event_name = event_get_default_agent_ust_name(agt->domain); + if (!ust_event_name) { + ret = LTTNG_ERR_FATAL; + goto error; + } + + /* + * Agent UST event has its loglevel type forced to + * LTTNG_UST_LOGLEVEL_ALL. The actual loglevel type/value filtering + * happens thanks to an UST filter. The following -1 is actually + * ignored since the type is LTTNG_UST_LOGLEVEL_ALL. + */ + uevent = trace_ust_find_event(uchan->events, (char *) ust_event_name, + aevent->filter, LTTNG_UST_ABI_LOGLEVEL_ALL, -1, NULL); + /* If the agent event exists, it must be available on the UST side. */ + LTTNG_ASSERT(uevent); + + if (usess->active) { + ret = ust_app_disable_event_glb(usess, uchan, uevent); + if (ret < 0 && ret != -LTTNG_UST_ERR_EXIST) { + ret = LTTNG_ERR_UST_DISABLE_FAIL; + goto error; + } + } + + /* + * Flag event that it's disabled so the shadow copy on the ust app side + * will disable it if an application shows up. + */ + uevent->enabled = 0; + + ret = agent_disable_event(aevent, agt->domain); + if (ret != LTTNG_OK) { + goto error; + } + +end: + return LTTNG_OK; + +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; + + LTTNG_ASSERT(trigger); + LTTNG_ASSERT(agt); + + DBG("Event agent disabling for trigger %" PRIu64, + lttng_trigger_get_tracer_token(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_tracer_token(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. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int event_agent_disable(struct ltt_ust_session *usess, struct agent *agt, + const char *event_name) +{ + int ret = LTTNG_OK; + struct agent_event *aevent; + struct lttng_ht_iter iter; + struct lttng_ht_node_str *node; + + LTTNG_ASSERT(agt); + LTTNG_ASSERT(usess); + LTTNG_ASSERT(event_name); + + DBG("Event agent disabling %s (all loglevels) for session %" PRIu64, event_name, usess->id); + + rcu_read_lock(); + agent_find_events_by_name(event_name, agt, &iter); + node = lttng_ht_iter_get_node_str(&iter); + + if (node == NULL) { + DBG2("Event agent NOT found by name %s", event_name); + ret = LTTNG_ERR_UST_EVENT_NOT_FOUND; + goto end; + } + + do { + aevent = caa_container_of(node, struct agent_event, node); + ret = event_agent_disable_one(usess, agt, aevent); + + if (ret != LTTNG_OK) { + goto end; + } + + /* Get next duplicate agent event by name. */ + agent_event_next_duplicate(event_name, agt, &iter); + node = lttng_ht_iter_get_node_str(&iter); + } while (node); +end: + rcu_read_unlock(); + return ret; +} +/* + * Disable all agent event for a given UST session. + * + * Return LTTNG_OK on success or else a LTTNG_ERR* code. + */ +int event_agent_disable_all(struct ltt_ust_session *usess, + struct agent *agt) +{ + int ret; + struct agent_event *aevent; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(agt); + LTTNG_ASSERT(usess); + + /* + * Disable event on agent application. Continue to disable all other events + * if the * event is not found. + */ + ret = event_agent_disable(usess, agt, "*"); + if (ret != LTTNG_OK && ret != LTTNG_ERR_UST_EVENT_NOT_FOUND) { + goto error; + } + + /* Disable every event. */ + rcu_read_lock(); + cds_lfht_for_each_entry(agt->events->ht, &iter.iter, aevent, + node.node) { + if (!AGENT_EVENT_IS_ENABLED(aevent)) { + continue; + } + + ret = event_agent_disable(usess, agt, aevent->name); + if (ret != LTTNG_OK) { + goto error_unlock; + } + } + ret = LTTNG_OK; + +error_unlock: + rcu_read_unlock(); +error: + return ret; +} diff --git a/src/bin/lttng-sessiond/fd-limit.c b/src/bin/lttng-sessiond/fd-limit.c deleted file mode 100644 index 973f7d74a..000000000 --- a/src/bin/lttng-sessiond/fd-limit.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2012 Mathieu Desnoyers - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include "fd-limit.h" -#include -#include - -/* total count of fd. */ -static long fd_count; - -/* - * threshold in % of number of fd allowed. - */ -static long fd_threshold[LTTNG_FD_NR_TYPES] = { - [LTTNG_FD_APPS] = 75, -}; - -static rlim_t max_nr_fd; - -int lttng_fd_get(enum lttng_fd_type type, unsigned int nr) -{ - long newval; - - if (type >= LTTNG_FD_NR_TYPES) { - return -EINVAL; - } - - newval = uatomic_add_return(&fd_count, (long) nr); - if ((long) (newval * 100) - - (long) (max_nr_fd * fd_threshold[type]) > 0) { - uatomic_sub(&fd_count, (long) nr); - return -EPERM; - } - return 0; -} - -void lttng_fd_put(enum lttng_fd_type type, unsigned int nr) -{ - uatomic_sub(&fd_count, (long) nr); -} - -void lttng_fd_init(void) -{ - struct rlimit rlim; - int ret; - - ret = getrlimit(RLIMIT_NOFILE, &rlim); - if (ret < 0) { - PERROR("getrlimit"); - } - max_nr_fd = rlim.rlim_cur; -} diff --git a/src/bin/lttng-sessiond/fd-limit.cpp b/src/bin/lttng-sessiond/fd-limit.cpp new file mode 100644 index 000000000..833b64714 --- /dev/null +++ b/src/bin/lttng-sessiond/fd-limit.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012 Mathieu Desnoyers + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include "fd-limit.h" +#include +#include + +/* total count of fd. */ +static long fd_count; + +/* + * threshold in % of number of fd allowed. + */ +static long fd_threshold[LTTNG_FD_NR_TYPES] = { + 75, /* LTTNG_FD_APPS */ +}; + +static rlim_t max_nr_fd; + +int lttng_fd_get(enum lttng_fd_type type, unsigned int nr) +{ + long newval; + + if (type >= LTTNG_FD_NR_TYPES) { + return -EINVAL; + } + + newval = uatomic_add_return(&fd_count, (long) nr); + if ((long) (newval * 100) + - (long) (max_nr_fd * fd_threshold[type]) > 0) { + uatomic_sub(&fd_count, (long) nr); + return -EPERM; + } + return 0; +} + +void lttng_fd_put(enum lttng_fd_type type, unsigned int nr) +{ + uatomic_sub(&fd_count, (long) nr); +} + +void lttng_fd_init(void) +{ + struct rlimit rlim; + int ret; + + ret = getrlimit(RLIMIT_NOFILE, &rlim); + if (ret < 0) { + PERROR("getrlimit"); + } + max_nr_fd = rlim.rlim_cur; +} diff --git a/src/bin/lttng-sessiond/globals.c b/src/bin/lttng-sessiond/globals.c deleted file mode 100644 index ee08ef1b0..000000000 --- a/src/bin/lttng-sessiond/globals.c +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "lttng-sessiond.h" -#include - -lttng_uuid the_sessiond_uuid; - -int the_ust_consumerd64_fd = -1; -int the_ust_consumerd32_fd = -1; - -long the_page_size; - -struct health_app *the_health_sessiond; - -struct notification_thread_handle *the_notification_thread_handle; - -struct lttng_ht *the_agent_apps_ht_by_sock = NULL; -struct lttng_ht *the_trigger_agents_ht_by_domain = NULL; - -struct lttng_kernel_abi_tracer_version the_kernel_tracer_version; -struct lttng_kernel_abi_tracer_abi_version the_kernel_tracer_abi_version; - -int the_kernel_poll_pipe[2] = {-1, -1}; - -pid_t the_ppid; -pid_t the_child_ppid; - -struct sessiond_config the_config; - -struct consumer_data the_kconsumer_data = { - .type = LTTNG_CONSUMER_KERNEL, - .err_sock = -1, - .cmd_sock = -1, - .channel_monitor_pipe = -1, - .pid_mutex = PTHREAD_MUTEX_INITIALIZER, - .lock = PTHREAD_MUTEX_INITIALIZER, -}; - -struct consumer_data the_ustconsumer64_data = { - .type = LTTNG_CONSUMER64_UST, - .err_sock = -1, - .cmd_sock = -1, - .channel_monitor_pipe = -1, - .pid_mutex = PTHREAD_MUTEX_INITIALIZER, - .lock = PTHREAD_MUTEX_INITIALIZER, -}; - -struct consumer_data the_ustconsumer32_data = { - .type = LTTNG_CONSUMER32_UST, - .err_sock = -1, - .cmd_sock = -1, - .channel_monitor_pipe = -1, - .pid_mutex = PTHREAD_MUTEX_INITIALIZER, - .lock = PTHREAD_MUTEX_INITIALIZER, -}; - -enum consumerd_state the_ust_consumerd_state; -enum consumerd_state the_kernel_consumerd_state; - -static void __attribute__((constructor)) init_sessiond_uuid(void) -{ - if (lttng_uuid_generate(the_sessiond_uuid)) { - ERR("Failed to generate a session daemon UUID"); - abort(); - } -} diff --git a/src/bin/lttng-sessiond/globals.cpp b/src/bin/lttng-sessiond/globals.cpp new file mode 100644 index 000000000..b4cedf337 --- /dev/null +++ b/src/bin/lttng-sessiond/globals.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "lttng-sessiond.h" +#include + +lttng_uuid the_sessiond_uuid; + +int the_ust_consumerd64_fd = -1; +int the_ust_consumerd32_fd = -1; + +long the_page_size; + +struct health_app *the_health_sessiond; + +struct notification_thread_handle *the_notification_thread_handle; + +struct lttng_ht *the_agent_apps_ht_by_sock = NULL; +struct lttng_ht *the_trigger_agents_ht_by_domain = NULL; + +struct lttng_kernel_abi_tracer_version the_kernel_tracer_version; +struct lttng_kernel_abi_tracer_abi_version the_kernel_tracer_abi_version; + +int the_kernel_poll_pipe[2] = {-1, -1}; + +pid_t the_ppid; +pid_t the_child_ppid; + +struct sessiond_config the_config; + +consumer_data the_kconsumer_data(LTTNG_CONSUMER_KERNEL); +consumer_data the_ustconsumer64_data(LTTNG_CONSUMER64_UST); +consumer_data the_ustconsumer32_data(LTTNG_CONSUMER32_UST); + +enum consumerd_state the_ust_consumerd_state; +enum consumerd_state the_kernel_consumerd_state; + +static void __attribute__((constructor)) init_sessiond_uuid(void) +{ + if (lttng_uuid_generate(the_sessiond_uuid)) { + ERR("Failed to generate a session daemon UUID"); + abort(); + } +} diff --git a/src/bin/lttng-sessiond/health.c b/src/bin/lttng-sessiond/health.c deleted file mode 100644 index 7fc557da5..000000000 --- a/src/bin/lttng-sessiond/health.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2012 David Goulet - * Copyright (C) 2018 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "lttng-sessiond.h" -#include "health-sessiond.h" -#include -#include -#include -#include -#include -#include -#include "utils.h" -#include "thread.h" - -struct thread_notifiers { - struct lttng_pipe *quit_pipe; - sem_t ready; -}; - -static -void mark_thread_as_ready(struct thread_notifiers *notifiers) -{ - DBG("Marking health management thread as ready"); - sem_post(¬ifiers->ready); -} - -static -void wait_until_thread_is_ready(struct thread_notifiers *notifiers) -{ - DBG("Waiting for health management thread to be ready"); - sem_wait(¬ifiers->ready); - DBG("Health management thread is ready"); -} - -static void cleanup_health_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - - lttng_pipe_destroy(notifiers->quit_pipe); - sem_destroy(¬ifiers->ready); - free(notifiers); -} - -/* - * Thread managing health check socket. - */ -static void *thread_manage_health(void *data) -{ - const bool is_root = (getuid() == 0); - int sock = -1, new_sock = -1, ret, i, pollfd, err = -1; - uint32_t revents, nb_fd; - struct lttng_poll_event events; - struct health_comm_msg msg; - struct health_comm_reply reply; - /* Thread-specific quit pipe. */ - struct thread_notifiers *notifiers = data; - const int quit_pipe_read_fd = lttng_pipe_get_readfd( - notifiers->quit_pipe); - - DBG("[thread] Manage health check started"); - - rcu_register_thread(); - - /* - * Created with a size of two for: - * - client socket - * - thread quit pipe - */ - ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); - if (ret < 0) { - goto error; - } - - /* Create unix socket */ - sock = lttcomm_create_unix_sock(the_config.health_unix_sock_path.value); - if (sock < 0) { - ERR("Unable to create health check Unix socket"); - goto error; - } - - if (is_root) { - /* lttng health client socket path permissions */ - gid_t gid; - - ret = utils_get_group_id(the_config.tracing_group_name.value, true, &gid); - if (ret) { - /* Default to root group. */ - gid = 0; - } - - ret = chown(the_config.health_unix_sock_path.value, 0, gid); - if (ret < 0) { - ERR("Unable to set group on %s", the_config.health_unix_sock_path.value); - PERROR("chown"); - goto error; - } - - ret = chmod(the_config.health_unix_sock_path.value, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); - if (ret < 0) { - ERR("Unable to set permissions on %s", the_config.health_unix_sock_path.value); - PERROR("chmod"); - goto error; - } - } - - /* - * Set the CLOEXEC flag. Return code is useless because either way, the - * show must go on. - */ - (void) utils_set_fd_cloexec(sock); - - ret = lttcomm_listen_unix_sock(sock); - if (ret < 0) { - goto error; - } - - ret = lttng_poll_add(&events, quit_pipe_read_fd, LPOLLIN | LPOLLERR); - if (ret < 0) { - goto error; - } - - /* Add the application registration socket */ - ret = lttng_poll_add(&events, sock, LPOLLIN | LPOLLPRI); - if (ret < 0) { - goto error; - } - - mark_thread_as_ready(notifiers); - while (1) { - DBG("Health check ready"); - - /* Infinite blocking call, waiting for transmission */ -restart: - ret = lttng_poll_wait(&events, -1); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - goto restart; - } - goto error; - } - - nb_fd = ret; - - for (i = 0; i < nb_fd; i++) { - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - /* Event on the registration socket */ - if (pollfd == sock) { - if (revents & LPOLLIN) { - continue; - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ERR("Health socket poll error"); - goto error; - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - } else { - /* Event on the thread's quit pipe. */ - err = 0; - goto exit; - } - } - - new_sock = lttcomm_accept_unix_sock(sock); - if (new_sock < 0) { - goto error; - } - - /* - * Set the CLOEXEC flag. Return code is useless because either way, the - * show must go on. - */ - (void) utils_set_fd_cloexec(new_sock); - - DBG("Receiving data from client for health..."); - ret = lttcomm_recv_unix_sock(new_sock, (void *)&msg, sizeof(msg)); - if (ret <= 0) { - DBG("Nothing recv() from client... continuing"); - ret = close(new_sock); - if (ret) { - PERROR("close"); - } - continue; - } - - rcu_thread_online(); - - memset(&reply, 0, sizeof(reply)); - for (i = 0; i < NR_HEALTH_SESSIOND_TYPES; i++) { - /* - * health_check_state returns 0 if health is - * bad. - */ - if (!health_check_state(the_health_sessiond, i)) { - reply.ret_code |= 1ULL << i; - } - } - - DBG2("Health check return value %" PRIx64, reply.ret_code); - - ret = lttcomm_send_unix_sock(new_sock, (void *) &reply, - sizeof(reply)); - if (ret < 0) { - ERR("Failed to send health data back to client"); - } - - /* End of transmission */ - ret = close(new_sock); - if (ret) { - PERROR("close"); - } - } - -exit: -error: - if (err) { - ERR("Health error occurred in %s", __func__); - } - DBG("Health check thread dying"); - unlink(the_config.health_unix_sock_path.value); - if (sock >= 0) { - ret = close(sock); - if (ret) { - PERROR("close"); - } - } - - lttng_poll_clean(&events); - rcu_unregister_thread(); - return NULL; -} - -static bool shutdown_health_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); - - return notify_thread_pipe(write_fd) == 1; -} - -bool launch_health_management_thread(void) -{ - struct thread_notifiers *notifiers; - struct lttng_thread *thread; - - notifiers = zmalloc(sizeof(*notifiers)); - if (!notifiers) { - goto error_alloc; - } - - sem_init(¬ifiers->ready, 0, 0); - notifiers->quit_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!notifiers->quit_pipe) { - goto error; - } - thread = lttng_thread_create("Health management", - thread_manage_health, - shutdown_health_management_thread, - cleanup_health_management_thread, - notifiers); - if (!thread) { - goto error; - } - - wait_until_thread_is_ready(notifiers); - lttng_thread_put(thread); - return true; -error: - cleanup_health_management_thread(notifiers); -error_alloc: - return false; -} diff --git a/src/bin/lttng-sessiond/health.cpp b/src/bin/lttng-sessiond/health.cpp new file mode 100644 index 000000000..53b9ee01f --- /dev/null +++ b/src/bin/lttng-sessiond/health.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2012 David Goulet + * Copyright (C) 2018 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "lttng-sessiond.h" +#include "health-sessiond.h" +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include "thread.h" + +struct thread_notifiers { + struct lttng_pipe *quit_pipe; + sem_t ready; +}; + +static +void mark_thread_as_ready(struct thread_notifiers *notifiers) +{ + DBG("Marking health management thread as ready"); + sem_post(¬ifiers->ready); +} + +static +void wait_until_thread_is_ready(struct thread_notifiers *notifiers) +{ + DBG("Waiting for health management thread to be ready"); + sem_wait(¬ifiers->ready); + DBG("Health management thread is ready"); +} + +static void cleanup_health_management_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + + lttng_pipe_destroy(notifiers->quit_pipe); + sem_destroy(¬ifiers->ready); + free(notifiers); +} + +/* + * Thread managing health check socket. + */ +static void *thread_manage_health(void *data) +{ + const bool is_root = (getuid() == 0); + int sock = -1, new_sock = -1, ret, i, pollfd, err = -1; + uint32_t revents, nb_fd; + struct lttng_poll_event events; + struct health_comm_msg msg; + struct health_comm_reply reply; + /* Thread-specific quit pipe. */ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int quit_pipe_read_fd = lttng_pipe_get_readfd( + notifiers->quit_pipe); + + DBG("[thread] Manage health check started"); + + rcu_register_thread(); + + /* + * Created with a size of two for: + * - client socket + * - thread quit pipe + */ + ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); + if (ret < 0) { + goto error; + } + + /* Create unix socket */ + sock = lttcomm_create_unix_sock(the_config.health_unix_sock_path.value); + if (sock < 0) { + ERR("Unable to create health check Unix socket"); + goto error; + } + + if (is_root) { + /* lttng health client socket path permissions */ + gid_t gid; + + ret = utils_get_group_id(the_config.tracing_group_name.value, true, &gid); + if (ret) { + /* Default to root group. */ + gid = 0; + } + + ret = chown(the_config.health_unix_sock_path.value, 0, gid); + if (ret < 0) { + ERR("Unable to set group on %s", the_config.health_unix_sock_path.value); + PERROR("chown"); + goto error; + } + + ret = chmod(the_config.health_unix_sock_path.value, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if (ret < 0) { + ERR("Unable to set permissions on %s", the_config.health_unix_sock_path.value); + PERROR("chmod"); + goto error; + } + } + + /* + * Set the CLOEXEC flag. Return code is useless because either way, the + * show must go on. + */ + (void) utils_set_fd_cloexec(sock); + + ret = lttcomm_listen_unix_sock(sock); + if (ret < 0) { + goto error; + } + + ret = lttng_poll_add(&events, quit_pipe_read_fd, LPOLLIN | LPOLLERR); + if (ret < 0) { + goto error; + } + + /* Add the application registration socket */ + ret = lttng_poll_add(&events, sock, LPOLLIN | LPOLLPRI); + if (ret < 0) { + goto error; + } + + mark_thread_as_ready(notifiers); + while (1) { + DBG("Health check ready"); + + /* Infinite blocking call, waiting for transmission */ +restart: + ret = lttng_poll_wait(&events, -1); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + goto restart; + } + goto error; + } + + nb_fd = ret; + + for (i = 0; i < nb_fd; i++) { + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + /* Event on the registration socket */ + if (pollfd == sock) { + if (revents & LPOLLIN) { + continue; + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ERR("Health socket poll error"); + goto error; + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + } else { + /* Event on the thread's quit pipe. */ + err = 0; + goto exit; + } + } + + new_sock = lttcomm_accept_unix_sock(sock); + if (new_sock < 0) { + goto error; + } + + /* + * Set the CLOEXEC flag. Return code is useless because either way, the + * show must go on. + */ + (void) utils_set_fd_cloexec(new_sock); + + DBG("Receiving data from client for health..."); + ret = lttcomm_recv_unix_sock(new_sock, (void *)&msg, sizeof(msg)); + if (ret <= 0) { + DBG("Nothing recv() from client... continuing"); + ret = close(new_sock); + if (ret) { + PERROR("close"); + } + continue; + } + + rcu_thread_online(); + + memset(&reply, 0, sizeof(reply)); + for (i = 0; i < NR_HEALTH_SESSIOND_TYPES; i++) { + /* + * health_check_state returns 0 if health is + * bad. + */ + if (!health_check_state(the_health_sessiond, i)) { + reply.ret_code |= 1ULL << i; + } + } + + DBG2("Health check return value %" PRIx64, reply.ret_code); + + ret = lttcomm_send_unix_sock(new_sock, (void *) &reply, + sizeof(reply)); + if (ret < 0) { + ERR("Failed to send health data back to client"); + } + + /* End of transmission */ + ret = close(new_sock); + if (ret) { + PERROR("close"); + } + } + +exit: +error: + if (err) { + ERR("Health error occurred in %s", __func__); + } + DBG("Health check thread dying"); + unlink(the_config.health_unix_sock_path.value); + if (sock >= 0) { + ret = close(sock); + if (ret) { + PERROR("close"); + } + } + + lttng_poll_clean(&events); + rcu_unregister_thread(); + return NULL; +} + +static bool shutdown_health_management_thread(void *data) +{ + struct thread_notifiers *notifiers = ( thread_notifiers *) data; + const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); + + return notify_thread_pipe(write_fd) == 1; +} + +bool launch_health_management_thread(void) +{ + struct thread_notifiers *notifiers; + struct lttng_thread *thread; + + notifiers = (thread_notifiers *) zmalloc(sizeof(*notifiers)); + if (!notifiers) { + goto error_alloc; + } + + sem_init(¬ifiers->ready, 0, 0); + notifiers->quit_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!notifiers->quit_pipe) { + goto error; + } + thread = lttng_thread_create("Health management", + thread_manage_health, + shutdown_health_management_thread, + cleanup_health_management_thread, + notifiers); + if (!thread) { + goto error; + } + + wait_until_thread_is_ready(notifiers); + lttng_thread_put(thread); + return true; +error: + cleanup_health_management_thread(notifiers); +error_alloc: + return false; +} diff --git a/src/bin/lttng-sessiond/ht-cleanup.c b/src/bin/lttng-sessiond/ht-cleanup.c deleted file mode 100644 index 050f18e2b..000000000 --- a/src/bin/lttng-sessiond/ht-cleanup.c +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2013 Mathieu Desnoyers - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE - -#include -#include -#include -#include - -#include "lttng-sessiond.h" -#include "health-sessiond.h" -#include "testpoint.h" -#include "utils.h" -#include "ht-cleanup.h" - -static int ht_cleanup_quit_pipe[2] = { -1, -1 }; - -/* - * Check if the ht_cleanup thread quit pipe was triggered. - * - * Return true if it was triggered else false; - */ -static bool check_quit_pipe(int fd, uint32_t events) -{ - return (fd == ht_cleanup_quit_pipe[0] && (events & LPOLLIN)); -} - -static int init_pipe(int *pipe_fds) -{ - int ret, i; - - ret = pipe(pipe_fds); - if (ret < 0) { - PERROR("ht_cleanup thread quit pipe"); - goto error; - } - - for (i = 0; i < 2; i++) { - ret = fcntl(pipe_fds[i], F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("fcntl ht_cleanup_quit_pipe"); - goto error; - } - } -error: - return ret; -} - -/* - * Create a poll set with O_CLOEXEC and add the thread quit pipe to the set. - */ -static int set_pollset(struct lttng_poll_event *events, size_t size) -{ - int ret; - - ret = lttng_poll_create(events, size, LTTNG_CLOEXEC); - if (ret < 0) { - goto error; - } - - ret = lttng_poll_add(events, ht_cleanup_quit_pipe[0], - LPOLLIN | LPOLLERR); - if (ret < 0) { - goto error; - } - - ret = lttng_poll_add(events, the_ht_cleanup_pipe[0], LPOLLIN | LPOLLERR); - if (ret < 0) { - DBG("lttng_poll_add error %d.", ret); - goto error; - } - - return 0; - -error: - return ret; -} - -static void cleanup_ht_cleanup_thread(void *data) -{ - utils_close_pipe(ht_cleanup_quit_pipe); - utils_close_pipe(the_ht_cleanup_pipe); -} - -static void *thread_ht_cleanup(void *data) -{ - int ret, i, pollfd, err = -1; - ssize_t size_ret; - uint32_t revents, nb_fd; - struct lttng_poll_event events; - - DBG("startup."); - - rcu_register_thread(); - rcu_thread_online(); - - health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_HT_CLEANUP); - - if (testpoint(sessiond_thread_ht_cleanup)) { - DBG("testpoint."); - goto error_testpoint; - } - - health_code_update(); - - ret = set_pollset(&events, 2); - if (ret < 0) { - DBG("sessiond_set_ht_cleanup_thread_pollset error %d.", ret); - goto error_poll_create; - } - - health_code_update(); - - while (1) { - restart: - DBG3("Polling."); - health_poll_entry(); - ret = lttng_poll_wait(&events, -1); - DBG3("Returning from poll on %d fds.", - LTTNG_POLL_GETNB(&events)); - health_poll_exit(); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - continue; - } - goto error; - } - - nb_fd = ret; - for (i = 0; i < nb_fd; i++) { - struct lttng_ht *ht; - - health_code_update(); - - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - if (pollfd != the_ht_cleanup_pipe[0]) { - continue; - } - - if (revents & LPOLLIN) { - /* Get socket from dispatch thread. */ - size_ret = lttng_read(the_ht_cleanup_pipe[0], - &ht, sizeof(ht)); - if (size_ret < sizeof(ht)) { - PERROR("ht cleanup notify pipe"); - goto error; - } - health_code_update(); - /* - * The whole point of this thread is to call - * lttng_ht_destroy from a context that is NOT: - * 1) a read-side RCU lock, - * 2) a call_rcu thread. - */ - lttng_ht_destroy(ht); - - health_code_update(); - - /* - * Ensure that we never process the quit pipe - * event while there is still data available - * on the ht clean pipe. - */ - goto restart; - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ERR("ht cleanup pipe error"); - goto error; - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - } - - for (i = 0; i < nb_fd; i++) { - health_code_update(); - - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - if (!revents) { - /* No activity for this FD (poll implementation). */ - continue; - } - - if (pollfd == the_ht_cleanup_pipe[0]) { - continue; - } - - /* Thread quit pipe has been closed. Killing thread. */ - ret = check_quit_pipe(pollfd, revents); - if (ret) { - err = 0; - DBG("[ht-cleanup] quit."); - goto exit; - } - } - } - -exit: -error: - lttng_poll_clean(&events); -error_poll_create: -error_testpoint: - DBG("[ht-cleanup] Thread terminates."); - if (err) { - health_error(); - ERR("Health error occurred in %s", __func__); - } - health_unregister(the_health_sessiond); - rcu_thread_offline(); - rcu_unregister_thread(); - return NULL; -} - -static bool shutdown_ht_cleanup_thread(void *data) -{ - int ret; - - ret = notify_thread_pipe(ht_cleanup_quit_pipe[1]); - if (ret < 0) { - ERR("write error on ht_cleanup quit pipe"); - goto end; - } -end: - return ret; -} - -struct lttng_thread *launch_ht_cleanup_thread(void) -{ - int ret; - struct lttng_thread *thread; - - ret = init_pipe(the_ht_cleanup_pipe); - if (ret) { - goto error; - } - - ret = init_pipe(ht_cleanup_quit_pipe); - if (ret) { - goto error; - } - - thread = lttng_thread_create("HT cleanup", - thread_ht_cleanup, - shutdown_ht_cleanup_thread, - cleanup_ht_cleanup_thread, - NULL); - if (!thread) { - goto error; - } - return thread; -error: - cleanup_ht_cleanup_thread(NULL); - return NULL; -} diff --git a/src/bin/lttng-sessiond/ht-cleanup.cpp b/src/bin/lttng-sessiond/ht-cleanup.cpp new file mode 100644 index 000000000..050f18e2b --- /dev/null +++ b/src/bin/lttng-sessiond/ht-cleanup.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2013 Mathieu Desnoyers + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE + +#include +#include +#include +#include + +#include "lttng-sessiond.h" +#include "health-sessiond.h" +#include "testpoint.h" +#include "utils.h" +#include "ht-cleanup.h" + +static int ht_cleanup_quit_pipe[2] = { -1, -1 }; + +/* + * Check if the ht_cleanup thread quit pipe was triggered. + * + * Return true if it was triggered else false; + */ +static bool check_quit_pipe(int fd, uint32_t events) +{ + return (fd == ht_cleanup_quit_pipe[0] && (events & LPOLLIN)); +} + +static int init_pipe(int *pipe_fds) +{ + int ret, i; + + ret = pipe(pipe_fds); + if (ret < 0) { + PERROR("ht_cleanup thread quit pipe"); + goto error; + } + + for (i = 0; i < 2; i++) { + ret = fcntl(pipe_fds[i], F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("fcntl ht_cleanup_quit_pipe"); + goto error; + } + } +error: + return ret; +} + +/* + * Create a poll set with O_CLOEXEC and add the thread quit pipe to the set. + */ +static int set_pollset(struct lttng_poll_event *events, size_t size) +{ + int ret; + + ret = lttng_poll_create(events, size, LTTNG_CLOEXEC); + if (ret < 0) { + goto error; + } + + ret = lttng_poll_add(events, ht_cleanup_quit_pipe[0], + LPOLLIN | LPOLLERR); + if (ret < 0) { + goto error; + } + + ret = lttng_poll_add(events, the_ht_cleanup_pipe[0], LPOLLIN | LPOLLERR); + if (ret < 0) { + DBG("lttng_poll_add error %d.", ret); + goto error; + } + + return 0; + +error: + return ret; +} + +static void cleanup_ht_cleanup_thread(void *data) +{ + utils_close_pipe(ht_cleanup_quit_pipe); + utils_close_pipe(the_ht_cleanup_pipe); +} + +static void *thread_ht_cleanup(void *data) +{ + int ret, i, pollfd, err = -1; + ssize_t size_ret; + uint32_t revents, nb_fd; + struct lttng_poll_event events; + + DBG("startup."); + + rcu_register_thread(); + rcu_thread_online(); + + health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_HT_CLEANUP); + + if (testpoint(sessiond_thread_ht_cleanup)) { + DBG("testpoint."); + goto error_testpoint; + } + + health_code_update(); + + ret = set_pollset(&events, 2); + if (ret < 0) { + DBG("sessiond_set_ht_cleanup_thread_pollset error %d.", ret); + goto error_poll_create; + } + + health_code_update(); + + while (1) { + restart: + DBG3("Polling."); + health_poll_entry(); + ret = lttng_poll_wait(&events, -1); + DBG3("Returning from poll on %d fds.", + LTTNG_POLL_GETNB(&events)); + health_poll_exit(); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + continue; + } + goto error; + } + + nb_fd = ret; + for (i = 0; i < nb_fd; i++) { + struct lttng_ht *ht; + + health_code_update(); + + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + if (pollfd != the_ht_cleanup_pipe[0]) { + continue; + } + + if (revents & LPOLLIN) { + /* Get socket from dispatch thread. */ + size_ret = lttng_read(the_ht_cleanup_pipe[0], + &ht, sizeof(ht)); + if (size_ret < sizeof(ht)) { + PERROR("ht cleanup notify pipe"); + goto error; + } + health_code_update(); + /* + * The whole point of this thread is to call + * lttng_ht_destroy from a context that is NOT: + * 1) a read-side RCU lock, + * 2) a call_rcu thread. + */ + lttng_ht_destroy(ht); + + health_code_update(); + + /* + * Ensure that we never process the quit pipe + * event while there is still data available + * on the ht clean pipe. + */ + goto restart; + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ERR("ht cleanup pipe error"); + goto error; + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + } + + for (i = 0; i < nb_fd; i++) { + health_code_update(); + + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + if (!revents) { + /* No activity for this FD (poll implementation). */ + continue; + } + + if (pollfd == the_ht_cleanup_pipe[0]) { + continue; + } + + /* Thread quit pipe has been closed. Killing thread. */ + ret = check_quit_pipe(pollfd, revents); + if (ret) { + err = 0; + DBG("[ht-cleanup] quit."); + goto exit; + } + } + } + +exit: +error: + lttng_poll_clean(&events); +error_poll_create: +error_testpoint: + DBG("[ht-cleanup] Thread terminates."); + if (err) { + health_error(); + ERR("Health error occurred in %s", __func__); + } + health_unregister(the_health_sessiond); + rcu_thread_offline(); + rcu_unregister_thread(); + return NULL; +} + +static bool shutdown_ht_cleanup_thread(void *data) +{ + int ret; + + ret = notify_thread_pipe(ht_cleanup_quit_pipe[1]); + if (ret < 0) { + ERR("write error on ht_cleanup quit pipe"); + goto end; + } +end: + return ret; +} + +struct lttng_thread *launch_ht_cleanup_thread(void) +{ + int ret; + struct lttng_thread *thread; + + ret = init_pipe(the_ht_cleanup_pipe); + if (ret) { + goto error; + } + + ret = init_pipe(ht_cleanup_quit_pipe); + if (ret) { + goto error; + } + + thread = lttng_thread_create("HT cleanup", + thread_ht_cleanup, + shutdown_ht_cleanup_thread, + cleanup_ht_cleanup_thread, + NULL); + if (!thread) { + goto error; + } + return thread; +error: + cleanup_ht_cleanup_thread(NULL); + return NULL; +} diff --git a/src/bin/lttng-sessiond/kernel-consumer.c b/src/bin/lttng-sessiond/kernel-consumer.c deleted file mode 100644 index 364705fcb..000000000 --- a/src/bin/lttng-sessiond/kernel-consumer.c +++ /dev/null @@ -1,531 +0,0 @@ -/* - * Copyright (C) 2012 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "consumer.h" -#include "health-sessiond.h" -#include "kernel-consumer.h" -#include "notification-thread-commands.h" -#include "session.h" -#include "lttng-sessiond.h" - -static char *create_channel_path(struct consumer_output *consumer, - size_t *consumer_path_offset) -{ - int ret; - char tmp_path[PATH_MAX]; - char *pathname = NULL; - - LTTNG_ASSERT(consumer); - - /* Get the right path name destination */ - if (consumer->type == CONSUMER_DST_LOCAL || - (consumer->type == CONSUMER_DST_NET && - consumer->relay_major_version == 2 && - consumer->relay_minor_version >= 11)) { - pathname = strdup(consumer->domain_subdir); - if (!pathname) { - PERROR("Failed to copy domain subdirectory string %s", - consumer->domain_subdir); - goto error; - } - *consumer_path_offset = strlen(consumer->domain_subdir); - DBG3("Kernel local consumer trace path relative to current trace chunk: \"%s\"", - pathname); - } else { - /* Network output, relayd < 2.11. */ - ret = snprintf(tmp_path, sizeof(tmp_path), "%s%s", - consumer->dst.net.base_dir, - consumer->domain_subdir); - if (ret < 0) { - PERROR("snprintf kernel metadata path"); - goto error; - } else if (ret >= sizeof(tmp_path)) { - ERR("Kernel channel path exceeds the maximal allowed length of of %zu bytes (%i bytes required) with path \"%s%s\"", - sizeof(tmp_path), ret, - consumer->dst.net.base_dir, - consumer->domain_subdir); - goto error; - } - pathname = lttng_strndup(tmp_path, sizeof(tmp_path)); - if (!pathname) { - PERROR("lttng_strndup"); - goto error; - } - *consumer_path_offset = 0; - DBG3("Kernel network consumer subdir path: %s", pathname); - } - - return pathname; - -error: - free(pathname); - return NULL; -} - -/* - * Sending a single channel to the consumer with command ADD_CHANNEL. - */ -static -int kernel_consumer_add_channel(struct consumer_socket *sock, - struct ltt_kernel_channel *channel, - struct ltt_kernel_session *ksession, - unsigned int monitor) -{ - int ret; - char *pathname = NULL; - struct lttcomm_consumer_msg lkm; - struct consumer_output *consumer; - enum lttng_error_code status; - struct ltt_session *session = NULL; - struct lttng_channel_extended *channel_attr_extended; - bool is_local_trace; - size_t consumer_path_offset = 0; - - /* Safety net */ - LTTNG_ASSERT(channel); - LTTNG_ASSERT(ksession); - LTTNG_ASSERT(ksession->consumer); - - consumer = ksession->consumer; - channel_attr_extended = (struct lttng_channel_extended *) - channel->channel->attr.extended.ptr; - - DBG("Kernel consumer adding channel %s to kernel consumer", - channel->channel->name); - is_local_trace = consumer->net_seq_index == -1ULL; - - pathname = create_channel_path(consumer, &consumer_path_offset); - if (!pathname) { - ret = -1; - goto error; - } - - if (is_local_trace && ksession->current_trace_chunk) { - enum lttng_trace_chunk_status chunk_status; - char *pathname_index; - - ret = asprintf(&pathname_index, "%s/" DEFAULT_INDEX_DIR, - pathname); - if (ret < 0) { - ERR("Failed to format channel index directory"); - ret = -1; - goto error; - } - - /* - * Create the index subdirectory which will take care - * of implicitly creating the channel's path. - */ - chunk_status = lttng_trace_chunk_create_subdirectory( - ksession->current_trace_chunk, pathname_index); - free(pathname_index); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = -1; - goto error; - } - } - - /* Prep channel message structure */ - consumer_init_add_channel_comm_msg(&lkm, - channel->key, - ksession->id, - &pathname[consumer_path_offset], - ksession->uid, - ksession->gid, - consumer->net_seq_index, - channel->channel->name, - channel->stream_count, - channel->channel->attr.output, - CONSUMER_CHANNEL_TYPE_DATA, - channel->channel->attr.tracefile_size, - channel->channel->attr.tracefile_count, - monitor, - channel->channel->attr.live_timer_interval, - ksession->is_live_session, - channel_attr_extended->monitor_timer_interval, - ksession->current_trace_chunk); - - health_code_update(); - - ret = consumer_send_channel(sock, &lkm); - if (ret < 0) { - goto error; - } - - health_code_update(); - rcu_read_lock(); - session = session_find_by_id(ksession->id); - LTTNG_ASSERT(session); - LTTNG_ASSERT(pthread_mutex_trylock(&session->lock)); - LTTNG_ASSERT(session_trylock_list()); - - status = notification_thread_command_add_channel( - the_notification_thread_handle, session->name, - ksession->uid, ksession->gid, channel->channel->name, - channel->key, LTTNG_DOMAIN_KERNEL, - channel->channel->attr.subbuf_size * - channel->channel->attr.num_subbuf); - rcu_read_unlock(); - if (status != LTTNG_OK) { - ret = -1; - goto error; - } - - channel->published_to_notification_thread = true; - -error: - if (session) { - session_put(session); - } - free(pathname); - return ret; -} - -/* - * Sending metadata to the consumer with command ADD_CHANNEL and ADD_STREAM. - * - * The consumer socket lock must be held by the caller. - */ -int kernel_consumer_add_metadata(struct consumer_socket *sock, - struct ltt_kernel_session *ksession, unsigned int monitor) -{ - int ret; - struct lttcomm_consumer_msg lkm; - struct consumer_output *consumer; - - rcu_read_lock(); - - /* Safety net */ - LTTNG_ASSERT(ksession); - LTTNG_ASSERT(ksession->consumer); - LTTNG_ASSERT(sock); - - DBG("Sending metadata %d to kernel consumer", - ksession->metadata_stream_fd); - - /* Get consumer output pointer */ - consumer = ksession->consumer; - - /* Prep channel message structure */ - consumer_init_add_channel_comm_msg(&lkm, - ksession->metadata->key, - ksession->id, - "", - ksession->uid, - ksession->gid, - consumer->net_seq_index, - ksession->metadata->conf->name, - 1, - ksession->metadata->conf->attr.output, - CONSUMER_CHANNEL_TYPE_METADATA, - ksession->metadata->conf->attr.tracefile_size, - ksession->metadata->conf->attr.tracefile_count, - monitor, - ksession->metadata->conf->attr.live_timer_interval, - ksession->is_live_session, - 0, - ksession->current_trace_chunk); - - health_code_update(); - - ret = consumer_send_channel(sock, &lkm); - if (ret < 0) { - goto error; - } - - health_code_update(); - - /* Prep stream message structure */ - consumer_init_add_stream_comm_msg(&lkm, - ksession->metadata->key, - ksession->metadata_stream_fd, - 0 /* CPU: 0 for metadata. */); - - health_code_update(); - - /* Send stream and file descriptor */ - ret = consumer_send_stream(sock, consumer, &lkm, - &ksession->metadata_stream_fd, 1); - if (ret < 0) { - goto error; - } - - health_code_update(); - -error: - rcu_read_unlock(); - return ret; -} - -/* - * Sending a single stream to the consumer with command ADD_STREAM. - */ -static -int kernel_consumer_add_stream(struct consumer_socket *sock, - struct ltt_kernel_channel *channel, - struct ltt_kernel_stream *stream, - struct ltt_kernel_session *session, unsigned int monitor) -{ - int ret; - struct lttcomm_consumer_msg lkm; - struct consumer_output *consumer; - - LTTNG_ASSERT(channel); - LTTNG_ASSERT(stream); - LTTNG_ASSERT(session); - LTTNG_ASSERT(session->consumer); - LTTNG_ASSERT(sock); - - DBG("Sending stream %d of channel %s to kernel consumer", - stream->fd, channel->channel->name); - - /* Get consumer output pointer */ - consumer = session->consumer; - - /* Prep stream consumer message */ - consumer_init_add_stream_comm_msg(&lkm, - channel->key, - stream->fd, - stream->cpu); - - health_code_update(); - - /* Send stream and file descriptor */ - ret = consumer_send_stream(sock, consumer, &lkm, &stream->fd, 1); - if (ret < 0) { - goto error; - } - - health_code_update(); - -error: - return ret; -} - -/* - * Sending the notification that all streams were sent with STREAMS_SENT. - */ -int kernel_consumer_streams_sent(struct consumer_socket *sock, - struct ltt_kernel_session *session, uint64_t channel_key) -{ - int ret; - struct lttcomm_consumer_msg lkm; - struct consumer_output *consumer; - - LTTNG_ASSERT(sock); - LTTNG_ASSERT(session); - - DBG("Sending streams_sent"); - /* Get consumer output pointer */ - consumer = session->consumer; - - /* Prep stream consumer message */ - consumer_init_streams_sent_comm_msg(&lkm, - LTTNG_CONSUMER_STREAMS_SENT, - channel_key, consumer->net_seq_index); - - health_code_update(); - - /* Send stream and file descriptor */ - ret = consumer_send_msg(sock, &lkm); - if (ret < 0) { - goto error; - } - -error: - return ret; -} - -/* - * Send all stream fds of kernel channel to the consumer. - * - * The consumer socket lock must be held by the caller. - */ -int kernel_consumer_send_channel_streams(struct consumer_socket *sock, - struct ltt_kernel_channel *channel, struct ltt_kernel_session *ksession, - unsigned int monitor) -{ - int ret = LTTNG_OK; - struct ltt_kernel_stream *stream; - - /* Safety net */ - LTTNG_ASSERT(channel); - LTTNG_ASSERT(ksession); - LTTNG_ASSERT(ksession->consumer); - LTTNG_ASSERT(sock); - - rcu_read_lock(); - - /* Bail out if consumer is disabled */ - if (!ksession->consumer->enabled) { - ret = LTTNG_OK; - goto error; - } - - DBG("Sending streams of channel %s to kernel consumer", - channel->channel->name); - - if (!channel->sent_to_consumer) { - ret = kernel_consumer_add_channel(sock, channel, ksession, monitor); - if (ret < 0) { - goto error; - } - channel->sent_to_consumer = true; - } - - /* Send streams */ - cds_list_for_each_entry(stream, &channel->stream_list.head, list) { - if (!stream->fd || stream->sent_to_consumer) { - continue; - } - - /* Add stream on the kernel consumer side. */ - ret = kernel_consumer_add_stream(sock, channel, stream, - ksession, monitor); - if (ret < 0) { - goto error; - } - stream->sent_to_consumer = true; - } - -error: - rcu_read_unlock(); - return ret; -} - -/* - * Send all stream fds of the kernel session to the consumer. - * - * The consumer socket lock must be held by the caller. - */ -int kernel_consumer_send_session(struct consumer_socket *sock, - struct ltt_kernel_session *session) -{ - int ret, monitor = 0; - struct ltt_kernel_channel *chan; - - /* Safety net */ - LTTNG_ASSERT(session); - LTTNG_ASSERT(session->consumer); - LTTNG_ASSERT(sock); - - /* Bail out if consumer is disabled */ - if (!session->consumer->enabled) { - ret = LTTNG_OK; - goto error; - } - - /* Don't monitor the streams on the consumer if in flight recorder. */ - if (session->output_traces) { - monitor = 1; - } - - DBG("Sending session stream to kernel consumer"); - - if (session->metadata_stream_fd >= 0 && session->metadata) { - ret = kernel_consumer_add_metadata(sock, session, monitor); - if (ret < 0) { - goto error; - } - } - - /* Send channel and streams of it */ - cds_list_for_each_entry(chan, &session->channel_list.head, list) { - ret = kernel_consumer_send_channel_streams(sock, chan, session, - monitor); - if (ret < 0) { - goto error; - } - if (monitor) { - /* - * Inform the relay that all the streams for the - * channel were sent. - */ - ret = kernel_consumer_streams_sent(sock, session, chan->key); - if (ret < 0) { - goto error; - } - } - } - - DBG("Kernel consumer FDs of metadata and channel streams sent"); - - session->consumer_fds_sent = 1; - return 0; - -error: - return ret; -} - -int kernel_consumer_destroy_channel(struct consumer_socket *socket, - struct ltt_kernel_channel *channel) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(channel); - LTTNG_ASSERT(socket); - - DBG("Sending kernel consumer destroy channel key %" PRIu64, channel->key); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_DESTROY_CHANNEL; - msg.u.destroy_channel.key = channel->key; - - pthread_mutex_lock(socket->lock); - health_code_update(); - - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto error; - } - -error: - health_code_update(); - pthread_mutex_unlock(socket->lock); - return ret; -} - -int kernel_consumer_destroy_metadata(struct consumer_socket *socket, - struct ltt_kernel_metadata *metadata) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(metadata); - LTTNG_ASSERT(socket); - - DBG("Sending kernel consumer destroy channel key %" PRIu64, metadata->key); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_DESTROY_CHANNEL; - msg.u.destroy_channel.key = metadata->key; - - pthread_mutex_lock(socket->lock); - health_code_update(); - - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto error; - } - -error: - health_code_update(); - pthread_mutex_unlock(socket->lock); - return ret; -} diff --git a/src/bin/lttng-sessiond/kernel-consumer.cpp b/src/bin/lttng-sessiond/kernel-consumer.cpp new file mode 100644 index 000000000..364705fcb --- /dev/null +++ b/src/bin/lttng-sessiond/kernel-consumer.cpp @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2012 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "consumer.h" +#include "health-sessiond.h" +#include "kernel-consumer.h" +#include "notification-thread-commands.h" +#include "session.h" +#include "lttng-sessiond.h" + +static char *create_channel_path(struct consumer_output *consumer, + size_t *consumer_path_offset) +{ + int ret; + char tmp_path[PATH_MAX]; + char *pathname = NULL; + + LTTNG_ASSERT(consumer); + + /* Get the right path name destination */ + if (consumer->type == CONSUMER_DST_LOCAL || + (consumer->type == CONSUMER_DST_NET && + consumer->relay_major_version == 2 && + consumer->relay_minor_version >= 11)) { + pathname = strdup(consumer->domain_subdir); + if (!pathname) { + PERROR("Failed to copy domain subdirectory string %s", + consumer->domain_subdir); + goto error; + } + *consumer_path_offset = strlen(consumer->domain_subdir); + DBG3("Kernel local consumer trace path relative to current trace chunk: \"%s\"", + pathname); + } else { + /* Network output, relayd < 2.11. */ + ret = snprintf(tmp_path, sizeof(tmp_path), "%s%s", + consumer->dst.net.base_dir, + consumer->domain_subdir); + if (ret < 0) { + PERROR("snprintf kernel metadata path"); + goto error; + } else if (ret >= sizeof(tmp_path)) { + ERR("Kernel channel path exceeds the maximal allowed length of of %zu bytes (%i bytes required) with path \"%s%s\"", + sizeof(tmp_path), ret, + consumer->dst.net.base_dir, + consumer->domain_subdir); + goto error; + } + pathname = lttng_strndup(tmp_path, sizeof(tmp_path)); + if (!pathname) { + PERROR("lttng_strndup"); + goto error; + } + *consumer_path_offset = 0; + DBG3("Kernel network consumer subdir path: %s", pathname); + } + + return pathname; + +error: + free(pathname); + return NULL; +} + +/* + * Sending a single channel to the consumer with command ADD_CHANNEL. + */ +static +int kernel_consumer_add_channel(struct consumer_socket *sock, + struct ltt_kernel_channel *channel, + struct ltt_kernel_session *ksession, + unsigned int monitor) +{ + int ret; + char *pathname = NULL; + struct lttcomm_consumer_msg lkm; + struct consumer_output *consumer; + enum lttng_error_code status; + struct ltt_session *session = NULL; + struct lttng_channel_extended *channel_attr_extended; + bool is_local_trace; + size_t consumer_path_offset = 0; + + /* Safety net */ + LTTNG_ASSERT(channel); + LTTNG_ASSERT(ksession); + LTTNG_ASSERT(ksession->consumer); + + consumer = ksession->consumer; + channel_attr_extended = (struct lttng_channel_extended *) + channel->channel->attr.extended.ptr; + + DBG("Kernel consumer adding channel %s to kernel consumer", + channel->channel->name); + is_local_trace = consumer->net_seq_index == -1ULL; + + pathname = create_channel_path(consumer, &consumer_path_offset); + if (!pathname) { + ret = -1; + goto error; + } + + if (is_local_trace && ksession->current_trace_chunk) { + enum lttng_trace_chunk_status chunk_status; + char *pathname_index; + + ret = asprintf(&pathname_index, "%s/" DEFAULT_INDEX_DIR, + pathname); + if (ret < 0) { + ERR("Failed to format channel index directory"); + ret = -1; + goto error; + } + + /* + * Create the index subdirectory which will take care + * of implicitly creating the channel's path. + */ + chunk_status = lttng_trace_chunk_create_subdirectory( + ksession->current_trace_chunk, pathname_index); + free(pathname_index); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = -1; + goto error; + } + } + + /* Prep channel message structure */ + consumer_init_add_channel_comm_msg(&lkm, + channel->key, + ksession->id, + &pathname[consumer_path_offset], + ksession->uid, + ksession->gid, + consumer->net_seq_index, + channel->channel->name, + channel->stream_count, + channel->channel->attr.output, + CONSUMER_CHANNEL_TYPE_DATA, + channel->channel->attr.tracefile_size, + channel->channel->attr.tracefile_count, + monitor, + channel->channel->attr.live_timer_interval, + ksession->is_live_session, + channel_attr_extended->monitor_timer_interval, + ksession->current_trace_chunk); + + health_code_update(); + + ret = consumer_send_channel(sock, &lkm); + if (ret < 0) { + goto error; + } + + health_code_update(); + rcu_read_lock(); + session = session_find_by_id(ksession->id); + LTTNG_ASSERT(session); + LTTNG_ASSERT(pthread_mutex_trylock(&session->lock)); + LTTNG_ASSERT(session_trylock_list()); + + status = notification_thread_command_add_channel( + the_notification_thread_handle, session->name, + ksession->uid, ksession->gid, channel->channel->name, + channel->key, LTTNG_DOMAIN_KERNEL, + channel->channel->attr.subbuf_size * + channel->channel->attr.num_subbuf); + rcu_read_unlock(); + if (status != LTTNG_OK) { + ret = -1; + goto error; + } + + channel->published_to_notification_thread = true; + +error: + if (session) { + session_put(session); + } + free(pathname); + return ret; +} + +/* + * Sending metadata to the consumer with command ADD_CHANNEL and ADD_STREAM. + * + * The consumer socket lock must be held by the caller. + */ +int kernel_consumer_add_metadata(struct consumer_socket *sock, + struct ltt_kernel_session *ksession, unsigned int monitor) +{ + int ret; + struct lttcomm_consumer_msg lkm; + struct consumer_output *consumer; + + rcu_read_lock(); + + /* Safety net */ + LTTNG_ASSERT(ksession); + LTTNG_ASSERT(ksession->consumer); + LTTNG_ASSERT(sock); + + DBG("Sending metadata %d to kernel consumer", + ksession->metadata_stream_fd); + + /* Get consumer output pointer */ + consumer = ksession->consumer; + + /* Prep channel message structure */ + consumer_init_add_channel_comm_msg(&lkm, + ksession->metadata->key, + ksession->id, + "", + ksession->uid, + ksession->gid, + consumer->net_seq_index, + ksession->metadata->conf->name, + 1, + ksession->metadata->conf->attr.output, + CONSUMER_CHANNEL_TYPE_METADATA, + ksession->metadata->conf->attr.tracefile_size, + ksession->metadata->conf->attr.tracefile_count, + monitor, + ksession->metadata->conf->attr.live_timer_interval, + ksession->is_live_session, + 0, + ksession->current_trace_chunk); + + health_code_update(); + + ret = consumer_send_channel(sock, &lkm); + if (ret < 0) { + goto error; + } + + health_code_update(); + + /* Prep stream message structure */ + consumer_init_add_stream_comm_msg(&lkm, + ksession->metadata->key, + ksession->metadata_stream_fd, + 0 /* CPU: 0 for metadata. */); + + health_code_update(); + + /* Send stream and file descriptor */ + ret = consumer_send_stream(sock, consumer, &lkm, + &ksession->metadata_stream_fd, 1); + if (ret < 0) { + goto error; + } + + health_code_update(); + +error: + rcu_read_unlock(); + return ret; +} + +/* + * Sending a single stream to the consumer with command ADD_STREAM. + */ +static +int kernel_consumer_add_stream(struct consumer_socket *sock, + struct ltt_kernel_channel *channel, + struct ltt_kernel_stream *stream, + struct ltt_kernel_session *session, unsigned int monitor) +{ + int ret; + struct lttcomm_consumer_msg lkm; + struct consumer_output *consumer; + + LTTNG_ASSERT(channel); + LTTNG_ASSERT(stream); + LTTNG_ASSERT(session); + LTTNG_ASSERT(session->consumer); + LTTNG_ASSERT(sock); + + DBG("Sending stream %d of channel %s to kernel consumer", + stream->fd, channel->channel->name); + + /* Get consumer output pointer */ + consumer = session->consumer; + + /* Prep stream consumer message */ + consumer_init_add_stream_comm_msg(&lkm, + channel->key, + stream->fd, + stream->cpu); + + health_code_update(); + + /* Send stream and file descriptor */ + ret = consumer_send_stream(sock, consumer, &lkm, &stream->fd, 1); + if (ret < 0) { + goto error; + } + + health_code_update(); + +error: + return ret; +} + +/* + * Sending the notification that all streams were sent with STREAMS_SENT. + */ +int kernel_consumer_streams_sent(struct consumer_socket *sock, + struct ltt_kernel_session *session, uint64_t channel_key) +{ + int ret; + struct lttcomm_consumer_msg lkm; + struct consumer_output *consumer; + + LTTNG_ASSERT(sock); + LTTNG_ASSERT(session); + + DBG("Sending streams_sent"); + /* Get consumer output pointer */ + consumer = session->consumer; + + /* Prep stream consumer message */ + consumer_init_streams_sent_comm_msg(&lkm, + LTTNG_CONSUMER_STREAMS_SENT, + channel_key, consumer->net_seq_index); + + health_code_update(); + + /* Send stream and file descriptor */ + ret = consumer_send_msg(sock, &lkm); + if (ret < 0) { + goto error; + } + +error: + return ret; +} + +/* + * Send all stream fds of kernel channel to the consumer. + * + * The consumer socket lock must be held by the caller. + */ +int kernel_consumer_send_channel_streams(struct consumer_socket *sock, + struct ltt_kernel_channel *channel, struct ltt_kernel_session *ksession, + unsigned int monitor) +{ + int ret = LTTNG_OK; + struct ltt_kernel_stream *stream; + + /* Safety net */ + LTTNG_ASSERT(channel); + LTTNG_ASSERT(ksession); + LTTNG_ASSERT(ksession->consumer); + LTTNG_ASSERT(sock); + + rcu_read_lock(); + + /* Bail out if consumer is disabled */ + if (!ksession->consumer->enabled) { + ret = LTTNG_OK; + goto error; + } + + DBG("Sending streams of channel %s to kernel consumer", + channel->channel->name); + + if (!channel->sent_to_consumer) { + ret = kernel_consumer_add_channel(sock, channel, ksession, monitor); + if (ret < 0) { + goto error; + } + channel->sent_to_consumer = true; + } + + /* Send streams */ + cds_list_for_each_entry(stream, &channel->stream_list.head, list) { + if (!stream->fd || stream->sent_to_consumer) { + continue; + } + + /* Add stream on the kernel consumer side. */ + ret = kernel_consumer_add_stream(sock, channel, stream, + ksession, monitor); + if (ret < 0) { + goto error; + } + stream->sent_to_consumer = true; + } + +error: + rcu_read_unlock(); + return ret; +} + +/* + * Send all stream fds of the kernel session to the consumer. + * + * The consumer socket lock must be held by the caller. + */ +int kernel_consumer_send_session(struct consumer_socket *sock, + struct ltt_kernel_session *session) +{ + int ret, monitor = 0; + struct ltt_kernel_channel *chan; + + /* Safety net */ + LTTNG_ASSERT(session); + LTTNG_ASSERT(session->consumer); + LTTNG_ASSERT(sock); + + /* Bail out if consumer is disabled */ + if (!session->consumer->enabled) { + ret = LTTNG_OK; + goto error; + } + + /* Don't monitor the streams on the consumer if in flight recorder. */ + if (session->output_traces) { + monitor = 1; + } + + DBG("Sending session stream to kernel consumer"); + + if (session->metadata_stream_fd >= 0 && session->metadata) { + ret = kernel_consumer_add_metadata(sock, session, monitor); + if (ret < 0) { + goto error; + } + } + + /* Send channel and streams of it */ + cds_list_for_each_entry(chan, &session->channel_list.head, list) { + ret = kernel_consumer_send_channel_streams(sock, chan, session, + monitor); + if (ret < 0) { + goto error; + } + if (monitor) { + /* + * Inform the relay that all the streams for the + * channel were sent. + */ + ret = kernel_consumer_streams_sent(sock, session, chan->key); + if (ret < 0) { + goto error; + } + } + } + + DBG("Kernel consumer FDs of metadata and channel streams sent"); + + session->consumer_fds_sent = 1; + return 0; + +error: + return ret; +} + +int kernel_consumer_destroy_channel(struct consumer_socket *socket, + struct ltt_kernel_channel *channel) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(channel); + LTTNG_ASSERT(socket); + + DBG("Sending kernel consumer destroy channel key %" PRIu64, channel->key); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_DESTROY_CHANNEL; + msg.u.destroy_channel.key = channel->key; + + pthread_mutex_lock(socket->lock); + health_code_update(); + + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto error; + } + +error: + health_code_update(); + pthread_mutex_unlock(socket->lock); + return ret; +} + +int kernel_consumer_destroy_metadata(struct consumer_socket *socket, + struct ltt_kernel_metadata *metadata) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(metadata); + LTTNG_ASSERT(socket); + + DBG("Sending kernel consumer destroy channel key %" PRIu64, metadata->key); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_DESTROY_CHANNEL; + msg.u.destroy_channel.key = metadata->key; + + pthread_mutex_lock(socket->lock); + health_code_update(); + + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto error; + } + +error: + health_code_update(); + pthread_mutex_unlock(socket->lock); + return ret; +} diff --git a/src/bin/lttng-sessiond/kernel.c b/src/bin/lttng-sessiond/kernel.c deleted file mode 100644 index ffbde695e..000000000 --- a/src/bin/lttng-sessiond/kernel.c +++ /dev/null @@ -1,2544 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#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 "event-notifier-error-accounting.h" -#include "lttng-sessiond.h" -#include "lttng-syscall.h" -#include "condition-internal.h" -#include "consumer.h" -#include "kernel.h" -#include "kernel-consumer.h" -#include "kern-modules.h" -#include "sessiond-config.h" -#include "utils.h" -#include "rotate.h" -#include "modprobe.h" -#include "tracker.h" -#include "notification-thread-commands.h" - -/* - * Key used to reference a channel between the sessiond and the consumer. This - * is only read and updated with the session_list lock held. - */ -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_event_notifier_group_fd = -1; -static int kernel_tracer_event_notifier_group_notification_fd = -1; -static struct cds_lfht *kernel_token_to_event_notifier_rule_ht; - -/* - * Add context on a kernel channel. - * - * Assumes the ownership of ctx. - */ -int kernel_add_channel_context(struct ltt_kernel_channel *chan, - struct ltt_kernel_context *ctx) -{ - int ret; - - LTTNG_ASSERT(chan); - LTTNG_ASSERT(ctx); - - DBG("Adding context to channel %s", chan->channel->name); - ret = kernctl_add_context(chan->fd, &ctx->ctx); - if (ret < 0) { - switch (-ret) { - case ENOSYS: - /* Exists but not available for this kernel */ - ret = LTTNG_ERR_KERN_CONTEXT_UNAVAILABLE; - goto error; - case EEXIST: - /* If EEXIST, we just ignore the error */ - ret = 0; - goto end; - default: - PERROR("add context ioctl"); - ret = LTTNG_ERR_KERN_CONTEXT_FAIL; - goto error; - } - } - ret = 0; - -end: - cds_list_add_tail(&ctx->list, &chan->ctx_list); - ctx->in_list = true; - ctx = NULL; -error: - if (ctx) { - trace_kernel_destroy_context(ctx); - } - return ret; -} - -/* - * Create a new kernel session, register it to the kernel tracer and add it to - * the session daemon session. - */ -int kernel_create_session(struct ltt_session *session) -{ - int ret; - struct ltt_kernel_session *lks; - - LTTNG_ASSERT(session); - - /* Allocate data structure */ - lks = trace_kernel_create_session(); - if (lks == NULL) { - ret = -1; - goto error; - } - - /* Kernel tracer session creation */ - ret = kernctl_create_session(kernel_tracer_fd); - if (ret < 0) { - PERROR("ioctl kernel create session"); - goto error; - } - - lks->fd = ret; - /* Prevent fd duplication after execlp() */ - ret = fcntl(lks->fd, F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("fcntl session fd"); - } - - lks->id = session->id; - lks->consumer_fds_sent = 0; - session->kernel_session = lks; - - DBG("Kernel session created (fd: %d)", lks->fd); - - /* - * This is necessary since the creation time is present in the session - * name when it is generated. - */ - if (session->has_auto_generated_name) { - ret = kernctl_session_set_name(lks->fd, DEFAULT_SESSION_NAME); - } else { - ret = kernctl_session_set_name(lks->fd, session->name); - } - if (ret) { - WARN("Could not set kernel session name for session %" PRIu64 " name: %s", - session->id, session->name); - } - - ret = kernctl_session_set_creation_time(lks->fd, session->creation_time); - if (ret) { - WARN("Could not set kernel session creation time for session %" PRIu64 " name: %s", - session->id, session->name); - } - - return 0; - -error: - if (lks) { - trace_kernel_destroy_session(lks); - trace_kernel_free_session(lks); - } - return ret; -} - -/* - * Create a kernel channel, register it to the kernel tracer and add it to the - * kernel session. - */ -int kernel_create_channel(struct ltt_kernel_session *session, - struct lttng_channel *chan) -{ - int ret; - struct ltt_kernel_channel *lkc; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(chan); - - /* Allocate kernel channel */ - lkc = trace_kernel_create_channel(chan); - if (lkc == NULL) { - goto error; - } - - DBG3("Kernel create channel %s with attr: %d, %" PRIu64 ", %" PRIu64 ", %u, %u, %d, %d", - chan->name, lkc->channel->attr.overwrite, - lkc->channel->attr.subbuf_size, lkc->channel->attr.num_subbuf, - lkc->channel->attr.switch_timer_interval, lkc->channel->attr.read_timer_interval, - lkc->channel->attr.live_timer_interval, lkc->channel->attr.output); - - /* Kernel tracer channel creation */ - ret = kernctl_create_channel(session->fd, &lkc->channel->attr); - if (ret < 0) { - PERROR("ioctl kernel create channel"); - goto error; - } - - /* Setup the channel fd */ - lkc->fd = ret; - /* Prevent fd duplication after execlp() */ - ret = fcntl(lkc->fd, F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("fcntl session fd"); - } - - /* Add channel to session */ - cds_list_add(&lkc->list, &session->channel_list.head); - session->channel_count++; - lkc->session = session; - lkc->key = ++next_kernel_channel_key; - - DBG("Kernel channel %s created (fd: %d, key: %" PRIu64 ")", - lkc->channel->name, lkc->fd, lkc->key); - - return 0; - -error: - if (lkc) { - free(lkc->channel); - free(lkc); - } - return -1; -} - -/* - * Create a kernel event notifier group, register it to the kernel tracer and - * add it to the kernel session. - */ -static int kernel_create_event_notifier_group(int *event_notifier_group_fd) -{ - int ret; - int local_fd = -1; - - LTTNG_ASSERT(event_notifier_group_fd); - - /* Kernel event notifier group creation. */ - ret = kernctl_create_event_notifier_group(kernel_tracer_fd); - if (ret < 0) { - PERROR("Failed to create kernel event notifier group"); - ret = -1; - goto error; - } - - local_fd = ret; - - /* Prevent fd duplication after execlp(). */ - ret = fcntl(local_fd, F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("Failed to set FD_CLOEXEC on kernel event notifier group file descriptor: fd = %d", - local_fd); - goto error; - } - - DBG("Created kernel event notifier group: fd = %d", local_fd); - *event_notifier_group_fd = local_fd; - local_fd = -1; - ret = 0; -error: - if (local_fd >= 0) { - ret = close(local_fd); - if (ret) { - PERROR("Failed to close kernel event notifier group file descriptor: fd = %d", - 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. - * - * Returns 0 on success and set the offset out parameter to the offset of the - * elf symbol - * Returns -1 on error - */ -static -int extract_userspace_probe_offset_function_elf( - const struct lttng_userspace_probe_location *probe_location, - uid_t uid, gid_t gid, uint64_t *offset) -{ - int fd; - int ret = 0; - const char *symbol = NULL; - const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; - enum lttng_userspace_probe_location_lookup_method_type lookup_method_type; - - LTTNG_ASSERT(lttng_userspace_probe_location_get_type(probe_location) == - LTTNG_USERSPACE_PROBE_LOCATION_TYPE_FUNCTION); - - lookup = lttng_userspace_probe_location_get_lookup_method( - probe_location); - if (!lookup) { - ret = -1; - goto end; - } - - lookup_method_type = - lttng_userspace_probe_location_lookup_method_get_type(lookup); - - LTTNG_ASSERT(lookup_method_type == - LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF); - - symbol = lttng_userspace_probe_location_function_get_function_name( - probe_location); - if (!symbol) { - ret = -1; - goto end; - } - - fd = lttng_userspace_probe_location_function_get_binary_fd(probe_location); - if (fd < 0) { - ret = -1; - goto end; - } - - 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); - goto end; - } - - DBG("userspace probe elf offset for %s is 0x%jd", symbol, (intmax_t)(*offset)); -end: - return ret; -} - -/* - * Compute the offsets of the instrumentation bytes in the binary based on the - * tracepoint probe location using the SDT lookup method. This function - * allocates the offsets buffer, the caller must free it. - * - * Returns 0 on success and set the offset out parameter to the offsets of the - * SDT tracepoint. - * Returns -1 on error. - */ -static -int extract_userspace_probe_offset_tracepoint_sdt( - const struct lttng_userspace_probe_location *probe_location, - uid_t uid, gid_t gid, uint64_t **offsets, - uint32_t *offsets_count) -{ - enum lttng_userspace_probe_location_lookup_method_type lookup_method_type; - const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; - const char *probe_name = NULL, *provider_name = NULL; - int ret = 0; - int fd, i; - - LTTNG_ASSERT(lttng_userspace_probe_location_get_type(probe_location) == - LTTNG_USERSPACE_PROBE_LOCATION_TYPE_TRACEPOINT); - - lookup = lttng_userspace_probe_location_get_lookup_method(probe_location); - if (!lookup) { - ret = -1; - goto end; - } - - lookup_method_type = - lttng_userspace_probe_location_lookup_method_get_type(lookup); - - LTTNG_ASSERT(lookup_method_type == - LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT); - - - probe_name = lttng_userspace_probe_location_tracepoint_get_probe_name( - probe_location); - if (!probe_name) { - ret = -1; - goto end; - } - - provider_name = lttng_userspace_probe_location_tracepoint_get_provider_name( - probe_location); - if (!provider_name) { - ret = -1; - goto end; - } - - fd = lttng_userspace_probe_location_tracepoint_get_binary_fd(probe_location); - if (fd < 0) { - ret = -1; - goto end; - } - - ret = run_as_extract_sdt_probe_offsets(fd, provider_name, probe_name, - uid, gid, offsets, offsets_count); - if (ret < 0) { - DBG("userspace probe offset calculation failed for sdt " - "probe %s:%s", provider_name, probe_name); - goto end; - } - - if (*offsets_count == 0) { - DBG("no userspace probe offset found"); - goto end; - } - - DBG("%u userspace probe SDT offsets found for %s:%s at:", - *offsets_count, provider_name, probe_name); - for (i = 0; i < *offsets_count; i++) { - DBG("\t0x%jd", (intmax_t)((*offsets)[i])); - } -end: - return ret; -} - -static -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; - int ret; - - lookup_method = lttng_userspace_probe_location_get_lookup_method(location); - if (!lookup_method) { - ret = -1; - goto end; - } - - type = lttng_userspace_probe_location_lookup_method_get_type(lookup_method); - switch (type) { - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF: - { - struct lttng_kernel_abi_event_callsite callsite; - uint64_t offset; - - ret = extract_userspace_probe_offset_function_elf(location, - uid, gid, &offset); - if (ret) { - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto end; - } - - callsite.u.uprobe.offset = offset; - ret = kernctl_add_callsite(fd, &callsite); - if (ret) { - WARN("Failed to add callsite to ELF userspace probe."); - ret = LTTNG_ERR_KERN_ENABLE_FAIL; - goto end; - } - break; - } - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: - { - int i; - uint64_t *offsets = NULL; - uint32_t offsets_count; - struct lttng_kernel_abi_event_callsite callsite; - - /* - * This call allocates the offsets buffer. This buffer must be freed - * by the caller - */ - ret = extract_userspace_probe_offset_tracepoint_sdt(location, - uid, gid, &offsets, &offsets_count); - if (ret) { - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto end; - } - for (i = 0; i < offsets_count; i++) { - callsite.u.uprobe.offset = offsets[i]; - ret = kernctl_add_callsite(fd, &callsite); - if (ret) { - WARN("Failed to add callsite to SDT userspace probe"); - ret = LTTNG_ERR_KERN_ENABLE_FAIL; - free(offsets); - goto end; - } - } - free(offsets); - break; - } - default: - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto end; - } -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) -{ - int ret; - const struct lttng_userspace_probe_location *location = NULL; - - LTTNG_ASSERT(ev); - LTTNG_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("Failed to add callsite to userspace probe event '%s'", - ev->name); - } - -end: - return ret; -} - -/* - * Extract the offsets of the instrumentation point for the different look-up - * methods. - */ -static int userspace_probe_event_rule_add_callsites( - const struct lttng_event_rule *rule, - const struct lttng_credentials *creds, - int fd) -{ - int ret; - enum lttng_event_rule_status status; - enum lttng_event_rule_type event_rule_type; - const struct lttng_userspace_probe_location *location = NULL; - - LTTNG_ASSERT(rule); - LTTNG_ASSERT(creds); - - event_rule_type = lttng_event_rule_get_type(rule); - LTTNG_ASSERT(event_rule_type == LTTNG_EVENT_RULE_TYPE_KERNEL_UPROBE); - - status = lttng_event_rule_kernel_uprobe_get_location(rule, &location); - if (status != LTTNG_EVENT_RULE_STATUS_OK || !location) { - ret = -1; - goto end; - } - - ret = userspace_probe_add_callsite(location, - lttng_credentials_get_uid(creds), - lttng_credentials_get_gid(creds), fd); - if (ret) { - WARN("Failed to add callsite to user space probe object: fd = %d", - 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. - * We own filter_expression and filter. - */ -int kernel_create_event(struct lttng_event *ev, - struct ltt_kernel_channel *channel, - char *filter_expression, - struct lttng_bytecode *filter) -{ - int err, fd; - enum lttng_error_code ret; - struct ltt_kernel_event *event; - - LTTNG_ASSERT(ev); - LTTNG_ASSERT(channel); - - /* We pass ownership of filter_expression and filter */ - ret = trace_kernel_create_event(ev, filter_expression, - filter, &event); - if (ret != LTTNG_OK) { - goto error; - } - - fd = kernctl_create_event(channel->fd, event->event); - if (fd < 0) { - switch (-fd) { - case EEXIST: - ret = LTTNG_ERR_KERN_EVENT_EXIST; - break; - case ENOSYS: - WARN("Event type not implemented"); - ret = LTTNG_ERR_KERN_EVENT_ENOSYS; - break; - case ENOENT: - WARN("Event %s not found!", ev->name); - ret = LTTNG_ERR_KERN_ENABLE_FAIL; - break; - default: - ret = LTTNG_ERR_KERN_ENABLE_FAIL; - PERROR("create event ioctl"); - } - goto free_event; - } - - event->type = ev->type; - event->fd = fd; - /* Prevent fd duplication after execlp() */ - err = fcntl(event->fd, F_SETFD, FD_CLOEXEC); - if (err < 0) { - PERROR("fcntl session fd"); - } - - if (filter) { - err = kernctl_filter(event->fd, 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 (ev->type == LTTNG_EVENT_USERSPACE_PROBE) { - ret = userspace_probe_event_add_callsites(ev, channel->session, - 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 event"); - ret = LTTNG_ERR_KERN_ENABLE_FAIL; - break; - } - goto enable_error; - } - - /* Add event to event list */ - cds_list_add(&event->list, &channel->events_list.head); - channel->event_count++; - - DBG("Event %s created (fd: %d)", ev->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; -} - -/* - * Disable a kernel channel. - */ -int kernel_disable_channel(struct ltt_kernel_channel *chan) -{ - int ret; - - LTTNG_ASSERT(chan); - - ret = kernctl_disable(chan->fd); - if (ret < 0) { - PERROR("disable chan ioctl"); - goto error; - } - - chan->enabled = 0; - DBG("Kernel channel %s disabled (fd: %d, key: %" PRIu64 ")", - chan->channel->name, chan->fd, chan->key); - - return 0; - -error: - return ret; -} - -/* - * Enable a kernel channel. - */ -int kernel_enable_channel(struct ltt_kernel_channel *chan) -{ - int ret; - - LTTNG_ASSERT(chan); - - ret = kernctl_enable(chan->fd); - if (ret < 0 && ret != -EEXIST) { - PERROR("Enable kernel chan"); - goto error; - } - - chan->enabled = 1; - DBG("Kernel channel %s enabled (fd: %d, key: %" PRIu64 ")", - chan->channel->name, chan->fd, chan->key); - - return 0; - -error: - return ret; -} - -/* - * Enable a kernel event. - */ -int kernel_enable_event(struct ltt_kernel_event *event) -{ - int ret; - - LTTNG_ASSERT(event); - - ret = kernctl_enable(event->fd); - if (ret < 0) { - switch (-ret) { - case EEXIST: - ret = LTTNG_ERR_KERN_EVENT_EXIST; - break; - default: - PERROR("enable kernel event"); - break; - } - goto error; - } - - event->enabled = 1; - DBG("Kernel event %s enabled (fd: %d)", event->event->name, event->fd); - - return 0; - -error: - return ret; -} - -/* - * Disable a kernel event. - */ -int kernel_disable_event(struct ltt_kernel_event *event) -{ - int ret; - - LTTNG_ASSERT(event); - - ret = kernctl_disable(event->fd); - if (ret < 0) { - PERROR("Failed to disable kernel event: name = '%s', fd = %d", - event->event->name, event->fd); - goto error; - } - - event->enabled = 0; - DBG("Kernel event %s disabled (fd: %d)", event->event->name, event->fd); - - return 0; - -error: - return ret; -} - -/* - * Disable a kernel event notifier. - */ -static -int kernel_disable_event_notifier_rule(struct ltt_kernel_event_notifier_rule *event) -{ - int ret; - - LTTNG_ASSERT(event); - - rcu_read_lock(); - cds_lfht_del(kernel_token_to_event_notifier_rule_ht, &event->ht_node); - rcu_read_unlock(); - - ret = kernctl_disable(event->fd); - if (ret < 0) { - PERROR("Failed to disable kernel event notifier: fd = %d, token = %" PRIu64, - event->fd, event->token); - goto error; - } - - event->enabled = 0; - DBG("Disabled kernel event notifier: fd = %d, token = %" PRIu64, - event->fd, event->token); - -error: - return ret; -} - -static -struct process_attr_tracker *_kernel_get_process_attr_tracker( - struct ltt_kernel_session *session, - enum lttng_process_attr process_attr) -{ - switch (process_attr) { - case LTTNG_PROCESS_ATTR_PROCESS_ID: - return session->tracker_pid; - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - return session->tracker_vpid; - case LTTNG_PROCESS_ATTR_USER_ID: - return session->tracker_uid; - case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: - return session->tracker_vuid; - case LTTNG_PROCESS_ATTR_GROUP_ID: - return session->tracker_gid; - case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: - return session->tracker_vgid; - default: - return NULL; - } -} - -const struct process_attr_tracker *kernel_get_process_attr_tracker( - struct ltt_kernel_session *session, - enum lttng_process_attr process_attr) -{ - return (const struct process_attr_tracker *) - _kernel_get_process_attr_tracker(session, process_attr); -} - -enum lttng_error_code kernel_process_attr_tracker_set_tracking_policy( - struct ltt_kernel_session *session, - enum lttng_process_attr process_attr, - enum lttng_tracking_policy policy) -{ - int ret; - enum lttng_error_code ret_code = LTTNG_OK; - struct process_attr_tracker *tracker = - _kernel_get_process_attr_tracker(session, process_attr); - enum lttng_tracking_policy previous_policy; - - if (!tracker) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - previous_policy = process_attr_tracker_get_tracking_policy(tracker); - ret = process_attr_tracker_set_tracking_policy(tracker, policy); - if (ret) { - ret_code = LTTNG_ERR_UNK; - goto end; - } - - if (previous_policy == policy) { - goto end; - } - - switch (policy) { - case LTTNG_TRACKING_POLICY_INCLUDE_ALL: - if (process_attr == LTTNG_PROCESS_ATTR_PROCESS_ID) { - /* - * Maintain a special case for the process ID process - * attribute tracker as it was the only supported - * attribute prior to 2.12. - */ - ret = kernctl_track_pid(session->fd, -1); - } else { - ret = kernctl_track_id(session->fd, process_attr, -1); - } - break; - case LTTNG_TRACKING_POLICY_EXCLUDE_ALL: - case LTTNG_TRACKING_POLICY_INCLUDE_SET: - /* fall-through. */ - if (process_attr == LTTNG_PROCESS_ATTR_PROCESS_ID) { - /* - * Maintain a special case for the process ID process - * attribute tracker as it was the only supported - * attribute prior to 2.12. - */ - ret = kernctl_untrack_pid(session->fd, -1); - } else { - ret = kernctl_untrack_id(session->fd, process_attr, -1); - } - break; - default: - abort(); - } - /* kern-ctl error handling */ - switch (-ret) { - case 0: - ret_code = LTTNG_OK; - break; - case EINVAL: - ret_code = LTTNG_ERR_INVALID; - break; - case ENOMEM: - ret_code = LTTNG_ERR_NOMEM; - break; - case EEXIST: - ret_code = LTTNG_ERR_PROCESS_ATTR_EXISTS; - break; - default: - ret_code = LTTNG_ERR_UNK; - break; - } -end: - return ret_code; -} - -enum lttng_error_code kernel_process_attr_tracker_inclusion_set_add_value( - struct ltt_kernel_session *session, - enum lttng_process_attr process_attr, - const struct process_attr_value *value) -{ - int ret, integral_value; - enum lttng_error_code ret_code; - struct process_attr_tracker *tracker; - enum process_attr_tracker_status status; - - /* - * Convert process attribute tracker value to the integral - * representation required by the kern-ctl API. - */ - switch (process_attr) { - case LTTNG_PROCESS_ATTR_PROCESS_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - integral_value = (int) value->value.pid; - break; - case LTTNG_PROCESS_ATTR_USER_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: - if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME) { - uid_t uid; - - ret_code = utils_user_id_from_name( - value->value.user_name, &uid); - if (ret_code != LTTNG_OK) { - goto end; - } - integral_value = (int) uid; - } else { - integral_value = (int) value->value.uid; - } - break; - case LTTNG_PROCESS_ATTR_GROUP_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: - if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME) { - gid_t gid; - - ret_code = utils_group_id_from_name( - value->value.group_name, &gid); - if (ret_code != LTTNG_OK) { - goto end; - } - integral_value = (int) gid; - } else { - integral_value = (int) value->value.gid; - } - break; - default: - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - tracker = _kernel_get_process_attr_tracker(session, process_attr); - if (!tracker) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - status = process_attr_tracker_inclusion_set_add_value(tracker, value); - if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { - switch (status) { - case PROCESS_ATTR_TRACKER_STATUS_EXISTS: - ret_code = LTTNG_ERR_PROCESS_ATTR_EXISTS; - break; - case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: - ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; - break; - case PROCESS_ATTR_TRACKER_STATUS_ERROR: - default: - ret_code = LTTNG_ERR_UNK; - break; - } - goto end; - } - - DBG("Kernel track %s %d for session id %" PRIu64, - lttng_process_attr_to_string(process_attr), - integral_value, session->id); - if (process_attr == LTTNG_PROCESS_ATTR_PROCESS_ID) { - /* - * Maintain a special case for the process ID process attribute - * tracker as it was the only supported attribute prior to 2.12. - */ - ret = kernctl_track_pid(session->fd, integral_value); - } else { - ret = kernctl_track_id( - session->fd, process_attr, integral_value); - } - if (ret == 0) { - ret_code = LTTNG_OK; - goto end; - } - - kernel_wait_quiescent(); - - /* kern-ctl error handling */ - switch (-ret) { - case 0: - ret_code = LTTNG_OK; - break; - case EINVAL: - ret_code = LTTNG_ERR_INVALID; - break; - case ENOMEM: - ret_code = LTTNG_ERR_NOMEM; - break; - case EEXIST: - ret_code = LTTNG_ERR_PROCESS_ATTR_EXISTS; - break; - default: - ret_code = LTTNG_ERR_UNK; - break; - } - - /* Attempt to remove the value from the tracker. */ - status = process_attr_tracker_inclusion_set_remove_value( - tracker, value); - if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { - ERR("Failed to roll-back the tracking of kernel %s process attribute %d while handling a kern-ctl error", - lttng_process_attr_to_string(process_attr), - integral_value); - } -end: - return ret_code; -} - -enum lttng_error_code kernel_process_attr_tracker_inclusion_set_remove_value( - struct ltt_kernel_session *session, - enum lttng_process_attr process_attr, - const struct process_attr_value *value) -{ - int ret, integral_value; - enum lttng_error_code ret_code; - struct process_attr_tracker *tracker; - enum process_attr_tracker_status status; - - /* - * Convert process attribute tracker value to the integral - * representation required by the kern-ctl API. - */ - switch (process_attr) { - case LTTNG_PROCESS_ATTR_PROCESS_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - integral_value = (int) value->value.pid; - break; - case LTTNG_PROCESS_ATTR_USER_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: - if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME) { - uid_t uid; - - ret_code = utils_user_id_from_name( - value->value.user_name, &uid); - if (ret_code != LTTNG_OK) { - goto end; - } - integral_value = (int) uid; - } else { - integral_value = (int) value->value.uid; - } - break; - case LTTNG_PROCESS_ATTR_GROUP_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: - if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME) { - gid_t gid; - - ret_code = utils_group_id_from_name( - value->value.group_name, &gid); - if (ret_code != LTTNG_OK) { - goto end; - } - integral_value = (int) gid; - } else { - integral_value = (int) value->value.gid; - } - break; - default: - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - tracker = _kernel_get_process_attr_tracker(session, process_attr); - if (!tracker) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - status = process_attr_tracker_inclusion_set_remove_value( - tracker, value); - if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { - switch (status) { - case PROCESS_ATTR_TRACKER_STATUS_MISSING: - ret_code = LTTNG_ERR_PROCESS_ATTR_MISSING; - break; - case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: - ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; - break; - case PROCESS_ATTR_TRACKER_STATUS_ERROR: - default: - ret_code = LTTNG_ERR_UNK; - break; - } - goto end; - } - - DBG("Kernel track %s %d for session id %" PRIu64, - lttng_process_attr_to_string(process_attr), - integral_value, session->id); - if (process_attr == LTTNG_PROCESS_ATTR_PROCESS_ID) { - /* - * Maintain a special case for the process ID process attribute - * tracker as it was the only supported attribute prior to 2.12. - */ - ret = kernctl_untrack_pid(session->fd, integral_value); - } else { - ret = kernctl_untrack_id( - session->fd, process_attr, integral_value); - } - if (ret == 0) { - ret_code = LTTNG_OK; - goto end; - } - kernel_wait_quiescent(); - - /* kern-ctl error handling */ - switch (-ret) { - case 0: - ret_code = LTTNG_OK; - break; - case EINVAL: - ret_code = LTTNG_ERR_INVALID; - break; - case ENOMEM: - ret_code = LTTNG_ERR_NOMEM; - break; - case ENOENT: - ret_code = LTTNG_ERR_PROCESS_ATTR_MISSING; - break; - default: - ret_code = LTTNG_ERR_UNK; - break; - } - - /* Attempt to add the value to the tracker. */ - status = process_attr_tracker_inclusion_set_add_value( - tracker, value); - if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { - ERR("Failed to roll-back the tracking of kernel %s process attribute %d while handling a kern-ctl error", - lttng_process_attr_to_string(process_attr), - integral_value); - } -end: - return ret_code; -} - -/* - * Create kernel metadata, open from the kernel tracer and add it to the - * kernel session. - */ -int kernel_open_metadata(struct ltt_kernel_session *session) -{ - int ret; - struct ltt_kernel_metadata *lkm = NULL; - - LTTNG_ASSERT(session); - - /* Allocate kernel metadata */ - lkm = trace_kernel_create_metadata(); - if (lkm == NULL) { - goto error; - } - - /* Kernel tracer metadata creation */ - ret = kernctl_open_metadata(session->fd, &lkm->conf->attr); - if (ret < 0) { - goto error_open; - } - - lkm->fd = ret; - lkm->key = ++next_kernel_channel_key; - /* Prevent fd duplication after execlp() */ - ret = fcntl(lkm->fd, F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("fcntl session fd"); - } - - session->metadata = lkm; - - DBG("Kernel metadata opened (fd: %d)", lkm->fd); - - return 0; - -error_open: - trace_kernel_destroy_metadata(lkm); -error: - return -1; -} - -/* - * Start tracing session. - */ -int kernel_start_session(struct ltt_kernel_session *session) -{ - int ret; - - LTTNG_ASSERT(session); - - ret = kernctl_start_session(session->fd); - if (ret < 0) { - PERROR("ioctl start session"); - goto error; - } - - DBG("Kernel session started"); - - return 0; - -error: - return ret; -} - -/* - * Make a kernel wait to make sure in-flight probe have completed. - */ -void kernel_wait_quiescent(void) -{ - int ret; - int fd = kernel_tracer_fd; - - DBG("Kernel quiescent wait on %d", fd); - - ret = kernctl_wait_quiescent(fd); - if (ret < 0) { - PERROR("wait quiescent ioctl"); - ERR("Kernel quiescent wait failed"); - } -} - -/* - * Force flush buffer of metadata. - */ -int kernel_metadata_flush_buffer(int fd) -{ - int ret; - - DBG("Kernel flushing metadata buffer on fd %d", fd); - - ret = kernctl_buffer_flush(fd); - if (ret < 0) { - ERR("Fail to flush metadata buffers %d (ret: %d)", fd, ret); - } - - return 0; -} - -/* - * Force flush buffer for channel. - */ -int kernel_flush_buffer(struct ltt_kernel_channel *channel) -{ - int ret; - struct ltt_kernel_stream *stream; - - LTTNG_ASSERT(channel); - - DBG("Flush buffer for channel %s", channel->channel->name); - - cds_list_for_each_entry(stream, &channel->stream_list.head, list) { - DBG("Flushing channel stream %d", stream->fd); - ret = kernctl_buffer_flush(stream->fd); - if (ret < 0) { - PERROR("ioctl"); - ERR("Fail to flush buffer for stream %d (ret: %d)", - stream->fd, ret); - } - } - - return 0; -} - -/* - * Stop tracing session. - */ -int kernel_stop_session(struct ltt_kernel_session *session) -{ - int ret; - - LTTNG_ASSERT(session); - - ret = kernctl_stop_session(session->fd); - if (ret < 0) { - goto error; - } - - DBG("Kernel session stopped"); - - return 0; - -error: - return ret; -} - -/* - * Open stream of channel, register it to the kernel tracer and add it - * to the stream list of the channel. - * - * Note: given that the streams may appear in random order wrt CPU - * number (e.g. cpu hotplug), the index value of the stream number in - * the stream name is not necessarily linked to the CPU number. - * - * Return the number of created stream. Else, a negative value. - */ -int kernel_open_channel_stream(struct ltt_kernel_channel *channel) -{ - int ret; - struct ltt_kernel_stream *lks; - - LTTNG_ASSERT(channel); - - while ((ret = kernctl_create_stream(channel->fd)) >= 0) { - lks = trace_kernel_create_stream(channel->channel->name, - channel->stream_count); - if (lks == NULL) { - ret = close(ret); - if (ret) { - PERROR("close"); - } - goto error; - } - - lks->fd = ret; - /* Prevent fd duplication after execlp() */ - ret = fcntl(lks->fd, F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("fcntl session fd"); - } - - lks->tracefile_size = channel->channel->attr.tracefile_size; - lks->tracefile_count = channel->channel->attr.tracefile_count; - - /* Add stream to channel stream list */ - cds_list_add(&lks->list, &channel->stream_list.head); - channel->stream_count++; - - DBG("Kernel stream %s created (fd: %d, state: %d)", lks->name, lks->fd, - lks->state); - } - - return channel->stream_count; - -error: - return -1; -} - -/* - * Open the metadata stream and set it to the kernel session. - */ -int kernel_open_metadata_stream(struct ltt_kernel_session *session) -{ - int ret; - - LTTNG_ASSERT(session); - - ret = kernctl_create_stream(session->metadata->fd); - if (ret < 0) { - PERROR("kernel create metadata stream"); - goto error; - } - - DBG("Kernel metadata stream created (fd: %d)", ret); - session->metadata_stream_fd = ret; - /* Prevent fd duplication after execlp() */ - ret = fcntl(session->metadata_stream_fd, F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("fcntl session fd"); - } - - return 0; - -error: - return -1; -} - -/* - * Get the event list from the kernel tracer and return the number of elements. - */ -ssize_t kernel_list_events(struct lttng_event **events) -{ - int fd, ret; - char *event; - size_t nbmem, count = 0; - FILE *fp; - struct lttng_event *elist; - - LTTNG_ASSERT(events); - - fd = kernctl_tracepoint_list(kernel_tracer_fd); - if (fd < 0) { - PERROR("kernel tracepoint list"); - goto error; - } - - fp = fdopen(fd, "r"); - if (fp == NULL) { - PERROR("kernel tracepoint list fdopen"); - goto error_fp; - } - - /* - * Init memory size counter - * See kernel-ctl.h for explanation of this value - */ - nbmem = KERNEL_EVENT_INIT_LIST_SIZE; - elist = zmalloc(sizeof(struct lttng_event) * nbmem); - if (elist == NULL) { - PERROR("alloc list events"); - count = -ENOMEM; - goto end; - } - - while (fscanf(fp, "event { name = %m[^;]; };\n", &event) == 1) { - if (count >= nbmem) { - struct lttng_event *new_elist; - size_t new_nbmem; - - new_nbmem = nbmem << 1; - DBG("Reallocating event list from %zu to %zu bytes", - nbmem, new_nbmem); - new_elist = realloc(elist, new_nbmem * sizeof(struct lttng_event)); - if (new_elist == NULL) { - PERROR("realloc list events"); - free(event); - free(elist); - count = -ENOMEM; - goto end; - } - /* Zero the new memory */ - memset(new_elist + nbmem, 0, - (new_nbmem - nbmem) * sizeof(struct lttng_event)); - nbmem = new_nbmem; - elist = new_elist; - } - strncpy(elist[count].name, event, LTTNG_SYMBOL_NAME_LEN); - elist[count].name[LTTNG_SYMBOL_NAME_LEN - 1] = '\0'; - elist[count].enabled = -1; - count++; - free(event); - } - - *events = elist; - DBG("Kernel list events done (%zu events)", count); -end: - ret = fclose(fp); /* closes both fp and fd */ - if (ret) { - PERROR("fclose"); - } - return count; - -error_fp: - ret = close(fd); - if (ret) { - PERROR("close"); - } -error: - return -1; -} - -/* - * Get kernel version and validate it. - */ -int kernel_validate_version(struct lttng_kernel_abi_tracer_version *version, - struct lttng_kernel_abi_tracer_abi_version *abi_version) -{ - int ret; - - ret = kernctl_tracer_version(kernel_tracer_fd, version); - if (ret < 0) { - ERR("Failed to retrieve the lttng-modules version"); - goto error; - } - - /* Validate version */ - if (version->major != VERSION_MAJOR) { - ERR("Kernel tracer major version (%d) is not compatible with lttng-tools major version (%d)", - version->major, VERSION_MAJOR); - goto error_version; - } - ret = kernctl_tracer_abi_version(kernel_tracer_fd, abi_version); - if (ret < 0) { - ERR("Failed to retrieve lttng-modules ABI version"); - goto error; - } - if (abi_version->major != LTTNG_KERNEL_ABI_MAJOR_VERSION) { - ERR("Kernel tracer ABI version (%d.%d) does not match the expected ABI major version (%d.*)", - abi_version->major, abi_version->minor, - LTTNG_KERNEL_ABI_MAJOR_VERSION); - goto error; - } - DBG2("Kernel tracer version validated (%d.%d, ABI %d.%d)", - version->major, version->minor, - abi_version->major, abi_version->minor); - return 0; - -error_version: - ret = -1; - -error: - ERR("Kernel tracer version check failed; kernel tracing will not be available"); - return ret; -} - -/* - * Kernel work-arounds called at the start of sessiond main(). - */ -int init_kernel_workarounds(void) -{ - int ret; - FILE *fp; - - /* - * boot_id needs to be read once before being used concurrently - * to deal with a Linux kernel race. A fix is proposed for - * upstream, but the work-around is needed for older kernels. - */ - fp = fopen("/proc/sys/kernel/random/boot_id", "r"); - if (!fp) { - goto end_boot_id; - } - while (!feof(fp)) { - char buf[37] = ""; - - ret = fread(buf, 1, sizeof(buf), fp); - if (ret < 0) { - /* Ignore error, we don't really care */ - } - } - ret = fclose(fp); - if (ret) { - PERROR("fclose"); - } -end_boot_id: - return 0; -} - -/* - * Teardown of a kernel session, keeping data required by destroy notifiers. - */ -void kernel_destroy_session(struct ltt_kernel_session *ksess) -{ - struct lttng_trace_chunk *trace_chunk; - - if (ksess == NULL) { - DBG3("No kernel session when tearing down session"); - return; - } - - DBG("Tearing down kernel session"); - trace_chunk = ksess->current_trace_chunk; - - /* - * Destroy channels on the consumer if at least one FD has been sent and we - * are in no output mode because the streams are in *no* monitor mode so we - * have to send a command to clean them up or else they leaked. - */ - if (!ksess->output_traces && ksess->consumer_fds_sent) { - int ret; - struct consumer_socket *socket; - struct lttng_ht_iter iter; - - /* For each consumer socket. */ - rcu_read_lock(); - cds_lfht_for_each_entry(ksess->consumer->socks->ht, &iter.iter, - socket, node.node) { - struct ltt_kernel_channel *chan; - - /* For each channel, ask the consumer to destroy it. */ - cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { - ret = kernel_consumer_destroy_channel(socket, chan); - if (ret < 0) { - /* Consumer is probably dead. Use next socket. */ - continue; - } - } - } - rcu_read_unlock(); - } - - /* Close any relayd session */ - consumer_output_send_destroy_relayd(ksess->consumer); - - trace_kernel_destroy_session(ksess); - lttng_trace_chunk_put(trace_chunk); -} - -/* Teardown of data required by destroy notifiers. */ -void kernel_free_session(struct ltt_kernel_session *ksess) -{ - if (ksess == NULL) { - return; - } - trace_kernel_free_session(ksess); -} - -/* - * Destroy a kernel channel object. It does not do anything on the tracer side. - */ -void kernel_destroy_channel(struct ltt_kernel_channel *kchan) -{ - struct ltt_kernel_session *ksess = NULL; - - LTTNG_ASSERT(kchan); - LTTNG_ASSERT(kchan->channel); - - DBG3("Kernel destroy channel %s", kchan->channel->name); - - /* Update channel count of associated session. */ - if (kchan->session) { - /* Keep pointer reference so we can update it after the destroy. */ - ksess = kchan->session; - } - - trace_kernel_destroy_channel(kchan); - - /* - * At this point the kernel channel is not visible anymore. This is safe - * since in order to work on a visible kernel session, the tracing session - * lock (ltt_session.lock) MUST be acquired. - */ - if (ksess) { - ksess->channel_count--; - } -} - -/* - * Take a snapshot for a given kernel session. - * - * Return LTTNG_OK on success or else return a LTTNG_ERR code. - */ -enum lttng_error_code kernel_snapshot_record( - struct ltt_kernel_session *ksess, - const struct consumer_output *output, int wait, - uint64_t nb_packets_per_stream) -{ - int err, ret, saved_metadata_fd; - enum lttng_error_code status = LTTNG_OK; - struct consumer_socket *socket; - struct lttng_ht_iter iter; - struct ltt_kernel_metadata *saved_metadata; - char *trace_path = NULL; - size_t consumer_path_offset = 0; - - LTTNG_ASSERT(ksess); - LTTNG_ASSERT(ksess->consumer); - LTTNG_ASSERT(output); - - DBG("Kernel snapshot record started"); - - /* Save current metadata since the following calls will change it. */ - saved_metadata = ksess->metadata; - saved_metadata_fd = ksess->metadata_stream_fd; - - rcu_read_lock(); - - ret = kernel_open_metadata(ksess); - if (ret < 0) { - status = LTTNG_ERR_KERN_META_FAIL; - goto error; - } - - ret = kernel_open_metadata_stream(ksess); - if (ret < 0) { - status = LTTNG_ERR_KERN_META_FAIL; - goto error_open_stream; - } - - trace_path = setup_channel_trace_path(ksess->consumer, - "", &consumer_path_offset); - if (!trace_path) { - status = LTTNG_ERR_INVALID; - goto error; - } - /* Send metadata to consumer and snapshot everything. */ - cds_lfht_for_each_entry(output->socks->ht, &iter.iter, - socket, node.node) { - struct ltt_kernel_channel *chan; - - pthread_mutex_lock(socket->lock); - /* This stream must not be monitored by the consumer. */ - ret = kernel_consumer_add_metadata(socket, ksess, 0); - pthread_mutex_unlock(socket->lock); - if (ret < 0) { - status = LTTNG_ERR_KERN_META_FAIL; - goto error_consumer; - } - - /* For each channel, ask the consumer to snapshot it. */ - cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { - status = consumer_snapshot_channel(socket, chan->key, output, 0, - ksess->uid, ksess->gid, - &trace_path[consumer_path_offset], wait, - nb_packets_per_stream); - if (status != LTTNG_OK) { - (void) kernel_consumer_destroy_metadata(socket, - ksess->metadata); - goto error_consumer; - } - } - - /* Snapshot metadata, */ - status = consumer_snapshot_channel(socket, ksess->metadata->key, output, - 1, ksess->uid, ksess->gid, &trace_path[consumer_path_offset], - wait, 0); - if (status != LTTNG_OK) { - goto error_consumer; - } - - /* - * The metadata snapshot is done, ask the consumer to destroy it since - * it's not monitored on the consumer side. - */ - (void) kernel_consumer_destroy_metadata(socket, ksess->metadata); - } - -error_consumer: - /* Close newly opened metadata stream. It's now on the consumer side. */ - err = close(ksess->metadata_stream_fd); - if (err < 0) { - PERROR("close snapshot kernel"); - } - -error_open_stream: - trace_kernel_destroy_metadata(ksess->metadata); -error: - /* Restore metadata state.*/ - ksess->metadata = saved_metadata; - ksess->metadata_stream_fd = saved_metadata_fd; - rcu_read_unlock(); - free(trace_path); - return status; -} - -/* - * Get the syscall mask array from the kernel tracer. - * - * Return 0 on success else a negative value. In both case, syscall_mask should - * be freed. - */ -int kernel_syscall_mask(int chan_fd, char **syscall_mask, uint32_t *nr_bits) -{ - LTTNG_ASSERT(syscall_mask); - LTTNG_ASSERT(nr_bits); - - return kernctl_syscall_mask(chan_fd, syscall_mask, nr_bits); -} - -static -int kernel_tracer_abi_greater_or_equal(unsigned int major, unsigned int minor) -{ - int ret; - struct lttng_kernel_abi_tracer_abi_version abi; - - ret = kernctl_tracer_abi_version(kernel_tracer_fd, &abi); - if (ret < 0) { - ERR("Failed to retrieve lttng-modules ABI version"); - goto error; - } - - ret = abi.major > major || (abi.major == major && abi.minor >= minor); -error: - return ret; -} - -/* - * Check for the support of the RING_BUFFER_SNAPSHOT_SAMPLE_POSITIONS via abi - * version number. - * - * Return 1 on success, 0 when feature is not supported, negative value in case - * of errors. - */ -int kernel_supports_ring_buffer_snapshot_sample_positions(void) -{ - /* - * RING_BUFFER_SNAPSHOT_SAMPLE_POSITIONS was introduced in 2.3 - */ - return kernel_tracer_abi_greater_or_equal(2, 3); -} - -/* - * Check for the support of the packet sequence number via abi version number. - * - * Return 1 on success, 0 when feature is not supported, negative value in case - * of errors. - */ -int kernel_supports_ring_buffer_packet_sequence_number(void) -{ - /* - * Packet sequence number was introduced in LTTng 2.8, - * lttng-modules ABI 2.1. - */ - return kernel_tracer_abi_greater_or_equal(2, 1); -} - -/* - * Check for the support of event notifiers via abi version number. - * - * Return 1 on success, 0 when feature is not supported, negative value in case - * of errors. - */ -int kernel_supports_event_notifiers(void) -{ - /* - * Event notifiers were introduced in LTTng 2.13, lttng-modules ABI 2.6. - */ - return kernel_tracer_abi_greater_or_equal(2, 6); -} - -/* - * Rotate a kernel session. - * - * Return LTTNG_OK on success or else an LTTng error code. - */ -enum lttng_error_code kernel_rotate_session(struct ltt_session *session) -{ - int ret; - enum lttng_error_code status = LTTNG_OK; - struct consumer_socket *socket; - struct lttng_ht_iter iter; - struct ltt_kernel_session *ksess = session->kernel_session; - - LTTNG_ASSERT(ksess); - LTTNG_ASSERT(ksess->consumer); - - DBG("Rotate kernel session %s started (session %" PRIu64 ")", - session->name, session->id); - - rcu_read_lock(); - - /* - * Note that this loop will end after one iteration given that there is - * only one kernel consumer. - */ - cds_lfht_for_each_entry(ksess->consumer->socks->ht, &iter.iter, - socket, node.node) { - struct ltt_kernel_channel *chan; - - /* For each channel, ask the consumer to rotate it. */ - cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { - DBG("Rotate kernel channel %" PRIu64 ", session %s", - chan->key, session->name); - ret = consumer_rotate_channel(socket, chan->key, - ksess->uid, ksess->gid, ksess->consumer, - /* is_metadata_channel */ false); - if (ret < 0) { - status = LTTNG_ERR_ROTATION_FAIL_CONSUMER; - goto error; - } - } - - /* - * Rotate the metadata channel. - */ - ret = consumer_rotate_channel(socket, ksess->metadata->key, - ksess->uid, ksess->gid, ksess->consumer, - /* is_metadata_channel */ true); - if (ret < 0) { - status = LTTNG_ERR_ROTATION_FAIL_CONSUMER; - goto error; - } - } - -error: - rcu_read_unlock(); - return status; -} - -enum lttng_error_code kernel_create_channel_subdirectories( - const struct ltt_kernel_session *ksess) -{ - enum lttng_error_code ret = LTTNG_OK; - enum lttng_trace_chunk_status chunk_status; - - rcu_read_lock(); - LTTNG_ASSERT(ksess->current_trace_chunk); - - /* - * Create the index subdirectory which will take care - * of implicitly creating the channel's path. - */ - chunk_status = lttng_trace_chunk_create_subdirectory( - ksess->current_trace_chunk, - DEFAULT_KERNEL_TRACE_DIR "/" DEFAULT_INDEX_DIR); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = LTTNG_ERR_CREATE_DIR_FAIL; - goto error; - } -error: - rcu_read_unlock(); - return ret; -} - -/* - * Setup necessary data for kernel tracer action. - */ -int init_kernel_tracer(void) -{ - int ret; - bool is_root = !getuid(); - - /* Modprobe lttng kernel modules */ - ret = modprobe_lttng_control(); - if (ret < 0) { - goto error; - } - - /* Open debugfs lttng */ - kernel_tracer_fd = open(module_proc_lttng, O_RDWR); - if (kernel_tracer_fd < 0) { - DBG("Failed to open %s", module_proc_lttng); - goto error_open; - } - - /* Validate kernel version */ - ret = kernel_validate_version(&the_kernel_tracer_version, - &the_kernel_tracer_abi_version); - if (ret < 0) { - goto error_version; - } - - ret = modprobe_lttng_data(); - if (ret < 0) { - goto error_modules; - } - - ret = kernel_supports_ring_buffer_snapshot_sample_positions(); - 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_supports_event_notifiers(); - if (ret < 0) { - ERR("Failed to check for kernel tracer event notifier support"); - goto error_modules; - } - ret = kernel_create_event_notifier_group(&kernel_tracer_event_notifier_group_fd); - if (ret < 0) { - /* This is not fatal. */ - WARN("Failed to create kernel event notifier group"); - kernel_tracer_event_notifier_group_fd = -1; - } else { - enum event_notifier_error_accounting_status error_accounting_status; - enum lttng_error_code error_code_ret = - kernel_create_event_notifier_group_notification_fd( - &kernel_tracer_event_notifier_group_notification_fd); - - if (error_code_ret != LTTNG_OK) { - goto error_modules; - } - - error_accounting_status = event_notifier_error_accounting_register_kernel( - kernel_tracer_event_notifier_group_fd); - if (error_accounting_status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - ERR("Failed to initialize event notifier error accounting for kernel tracer"); - error_code_ret = LTTNG_ERR_EVENT_NOTIFIER_ERROR_ACCOUNTING; - goto error_modules; - } - - kernel_token_to_event_notifier_rule_ht = cds_lfht_new( - DEFAULT_HT_SIZE, 1, 0, - CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, - NULL); - if (!kernel_token_to_event_notifier_rule_ht) { - goto error_token_ht; - } - } - - DBG("Kernel tracer initialized: kernel tracer fd = %d, event notifier group fd = %d, event notifier group notification fd = %d", - kernel_tracer_fd, kernel_tracer_event_notifier_group_fd, - kernel_tracer_event_notifier_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: - modprobe_remove_lttng_control(); - ret = close(kernel_tracer_fd); - if (ret) { - PERROR("Failed to close kernel tracer file descriptor: fd = %d", - kernel_tracer_fd); - } - - kernel_tracer_fd = -1; - return LTTNG_ERR_KERN_VERSION; - - -error_token_ht: - ret = close(kernel_tracer_event_notifier_group_notification_fd); - if (ret) { - PERROR("Failed to close kernel tracer event notifier group notification file descriptor: fd = %d", - kernel_tracer_event_notifier_group_notification_fd); - } - - kernel_tracer_event_notifier_group_notification_fd = -1; - -error_modules: - ret = close(kernel_tracer_event_notifier_group_fd); - if (ret) { - PERROR("Failed to close kernel tracer event notifier group file descriptor: fd = %d", - kernel_tracer_event_notifier_group_fd); - } - - kernel_tracer_event_notifier_group_fd = -1; - - ret = close(kernel_tracer_fd); - if (ret) { - PERROR("Failed to close kernel tracer file descriptor: fd = %d", - kernel_tracer_fd); - } - - kernel_tracer_fd = -1; - -error_open: - modprobe_remove_lttng_control(); - -error: - WARN("No kernel tracer available"); - kernel_tracer_fd = -1; - if (!is_root) { - return LTTNG_ERR_NEED_ROOT_SESSIOND; - } else { - return LTTNG_ERR_KERN_NA; - } -} - -void cleanup_kernel_tracer(void) -{ - DBG2("Closing kernel event notifier group notification file descriptor"); - if (kernel_tracer_event_notifier_group_notification_fd >= 0) { - int ret = notification_thread_command_remove_tracer_event_source( - the_notification_thread_handle, - kernel_tracer_event_notifier_group_notification_fd); - if (ret != LTTNG_OK) { - ERR("Failed to remove kernel event notifier notification from notification thread"); - } - - ret = close(kernel_tracer_event_notifier_group_notification_fd); - if (ret) { - PERROR("Failed to close kernel event notifier group notification file descriptor: fd = %d", - kernel_tracer_event_notifier_group_notification_fd); - } - - kernel_tracer_event_notifier_group_notification_fd = -1; - } - - if (kernel_token_to_event_notifier_rule_ht) { - const int ret = cds_lfht_destroy( - kernel_token_to_event_notifier_rule_ht, NULL); - LTTNG_ASSERT(ret == 0); - } - - DBG2("Closing kernel event notifier group file descriptor"); - if (kernel_tracer_event_notifier_group_fd >= 0) { - const int ret = close(kernel_tracer_event_notifier_group_fd); - - if (ret) { - PERROR("Failed to close kernel event notifier group file descriptor: fd = %d", - kernel_tracer_event_notifier_group_fd); - } - - kernel_tracer_event_notifier_group_fd = -1; - } - - DBG2("Closing kernel fd"); - if (kernel_tracer_fd >= 0) { - const int ret = close(kernel_tracer_fd); - - if (ret) { - PERROR("Failed to close kernel tracer file descriptor: fd = %d", - kernel_tracer_fd); - } - - kernel_tracer_fd = -1; - } - - free(syscall_table); -} - -bool kernel_tracer_is_initialized(void) -{ - return kernel_tracer_fd >= 0; -} - -/* - * Clear a kernel session. - * - * Return LTTNG_OK on success or else an LTTng error code. - */ -enum lttng_error_code kernel_clear_session(struct ltt_session *session) -{ - int ret; - enum lttng_error_code status = LTTNG_OK; - struct consumer_socket *socket; - struct lttng_ht_iter iter; - struct ltt_kernel_session *ksess = session->kernel_session; - - LTTNG_ASSERT(ksess); - LTTNG_ASSERT(ksess->consumer); - - DBG("Clear kernel session %s (session %" PRIu64 ")", - session->name, session->id); - - rcu_read_lock(); - - if (ksess->active) { - ERR("Expecting inactive session %s (%" PRIu64 ")", session->name, session->id); - status = LTTNG_ERR_FATAL; - goto end; - } - - /* - * Note that this loop will end after one iteration given that there is - * only one kernel consumer. - */ - cds_lfht_for_each_entry(ksess->consumer->socks->ht, &iter.iter, - socket, node.node) { - struct ltt_kernel_channel *chan; - - /* For each channel, ask the consumer to clear it. */ - cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { - DBG("Clear kernel channel %" PRIu64 ", session %s", - chan->key, session->name); - ret = consumer_clear_channel(socket, chan->key); - if (ret < 0) { - goto error; - } - } - - if (!ksess->metadata) { - /* - * Nothing to do for the metadata. - * This is a snapshot session. - * The metadata is genererated on the fly. - */ - continue; - } - - /* - * Clear the metadata channel. - * Metadata channel is not cleared per se but we still need to - * perform a rotation operation on it behind the scene. - */ - ret = consumer_clear_channel(socket, ksess->metadata->key); - if (ret < 0) { - goto error; - } - } - - goto end; -error: - switch (-ret) { - case LTTCOMM_CONSUMERD_RELAYD_CLEAR_DISALLOWED: - status = LTTNG_ERR_CLEAR_RELAY_DISALLOWED; - break; - default: - status = LTTNG_ERR_CLEAR_FAIL_CONSUMER; - break; - } -end: - rcu_read_unlock(); - return status; -} - -enum lttng_error_code kernel_create_event_notifier_group_notification_fd( - int *event_notifier_group_notification_fd) -{ - int local_fd = -1, ret; - enum lttng_error_code error_code_ret; - - LTTNG_ASSERT(event_notifier_group_notification_fd); - - ret = kernctl_create_event_notifier_group_notification_fd( - kernel_tracer_event_notifier_group_fd); - if (ret < 0) { - PERROR("Failed to create kernel event notifier group notification file descriptor"); - error_code_ret = LTTNG_ERR_EVENT_NOTIFIER_GROUP_NOTIFICATION_FD; - goto error; - } - - local_fd = ret; - - /* Prevent fd duplication after execlp(). */ - ret = fcntl(local_fd, F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("Failed to set FD_CLOEXEC on kernel event notifier group notification file descriptor: fd = %d", - local_fd); - error_code_ret = LTTNG_ERR_EVENT_NOTIFIER_GROUP_NOTIFICATION_FD; - goto error; - } - - DBG("Created kernel notifier group notification file descriptor: fd = %d", - local_fd); - error_code_ret = LTTNG_OK; - *event_notifier_group_notification_fd = local_fd; - local_fd = -1; - -error: - if (local_fd >= 0) { - ret = close(local_fd); - if (ret) { - PERROR("Failed to close kernel event notifier group notification file descriptor: fd = %d", - local_fd); - } - } - - return error_code_ret; -} - -enum lttng_error_code kernel_destroy_event_notifier_group_notification_fd( - int event_notifier_group_notification_fd) -{ - enum lttng_error_code ret_code = LTTNG_OK; - - DBG("Closing event notifier group notification file descriptor: fd = %d", - event_notifier_group_notification_fd); - if (event_notifier_group_notification_fd >= 0) { - const int ret = close(event_notifier_group_notification_fd); - if (ret) { - PERROR("Failed to close event notifier group notification file descriptor: fd = %d", - event_notifier_group_notification_fd); - } - } - - return ret_code; -} - -static -unsigned long hash_trigger(const struct lttng_trigger *trigger) -{ - const struct lttng_condition *condition = - lttng_trigger_get_const_condition(trigger); - - return lttng_condition_hash(condition); -} - -static -int match_trigger(struct cds_lfht_node *node, const void *key) -{ - const struct ltt_kernel_event_notifier_rule *event_notifier_rule; - const struct lttng_trigger *trigger = key; - - event_notifier_rule = caa_container_of(node, - const struct ltt_kernel_event_notifier_rule, ht_node); - - return lttng_trigger_is_equal(trigger, event_notifier_rule->trigger); -} - -static enum lttng_error_code kernel_create_event_notifier_rule( - struct lttng_trigger *trigger, - const struct lttng_credentials *creds, uint64_t token) -{ - int err, fd, ret = 0; - enum lttng_error_code error_code_ret; - enum lttng_condition_status condition_status; - enum lttng_condition_type condition_type; - enum lttng_event_rule_type event_rule_type; - struct ltt_kernel_event_notifier_rule *event_notifier_rule; - struct lttng_kernel_abi_event_notifier kernel_event_notifier = {}; - unsigned int capture_bytecode_count = 0, i; - const struct lttng_condition *condition = NULL; - const struct lttng_event_rule *event_rule = NULL; - enum lttng_condition_status cond_status; - - LTTNG_ASSERT(trigger); - - condition = lttng_trigger_get_const_condition(trigger); - LTTNG_ASSERT(condition); - - condition_type = lttng_condition_get_type(condition); - LTTNG_ASSERT(condition_type == LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); - - /* Does not acquire a reference. */ - condition_status = lttng_condition_event_rule_matches_get_rule( - condition, &event_rule); - LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); - LTTNG_ASSERT(event_rule); - - event_rule_type = lttng_event_rule_get_type(event_rule); - LTTNG_ASSERT(event_rule_type != LTTNG_EVENT_RULE_TYPE_UNKNOWN); - - error_code_ret = trace_kernel_create_event_notifier_rule(trigger, token, - lttng_condition_event_rule_matches_get_error_counter_index( - condition), - &event_notifier_rule); - if (error_code_ret != LTTNG_OK) { - goto error; - } - - error_code_ret = trace_kernel_init_event_notifier_from_event_rule( - event_rule, &kernel_event_notifier); - if (error_code_ret != LTTNG_OK) { - goto free_event; - } - - kernel_event_notifier.event.token = event_notifier_rule->token; - kernel_event_notifier.error_counter_idx = - lttng_condition_event_rule_matches_get_error_counter_index( - condition); - - fd = kernctl_create_event_notifier( - kernel_tracer_event_notifier_group_fd, - &kernel_event_notifier); - if (fd < 0) { - switch (-fd) { - case EEXIST: - error_code_ret = LTTNG_ERR_KERN_EVENT_EXIST; - break; - case ENOSYS: - WARN("Failed to create kernel event notifier: not notifier type not implemented"); - error_code_ret = LTTNG_ERR_KERN_EVENT_ENOSYS; - break; - case ENOENT: - WARN("Failed to create kernel event notifier: not found: name = '%s'", - kernel_event_notifier.event.name); - error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; - break; - default: - PERROR("Failed to create kernel event notifier: error code = %d, name = '%s'", - fd, kernel_event_notifier.event.name); - error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; - } - goto free_event; - } - - event_notifier_rule->fd = fd; - /* Prevent fd duplication after execlp(). */ - err = fcntl(event_notifier_rule->fd, F_SETFD, FD_CLOEXEC); - if (err < 0) { - PERROR("Failed to set FD_CLOEXEC on kernel event notifier file descriptor: fd = %d", - fd); - error_code_ret = LTTNG_ERR_FATAL; - goto set_cloexec_error; - } - - if (event_notifier_rule->filter) { - err = kernctl_filter(event_notifier_rule->fd, event_notifier_rule->filter); - if (err < 0) { - switch (-err) { - case ENOMEM: - error_code_ret = LTTNG_ERR_FILTER_NOMEM; - break; - default: - error_code_ret = LTTNG_ERR_FILTER_INVAL; - break; - } - goto filter_error; - } - } - - if (lttng_event_rule_get_type(event_rule) == - LTTNG_EVENT_RULE_TYPE_KERNEL_UPROBE) { - ret = userspace_probe_event_rule_add_callsites( - event_rule, creds, event_notifier_rule->fd); - if (ret) { - error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; - goto add_callsite_error; - } - } - - /* Set the capture bytecode if any. */ - cond_status = lttng_condition_event_rule_matches_get_capture_descriptor_count( - condition, &capture_bytecode_count); - LTTNG_ASSERT(cond_status == LTTNG_CONDITION_STATUS_OK); - - for (i = 0; i < capture_bytecode_count; i++) { - const struct lttng_bytecode *capture_bytecode = - lttng_condition_event_rule_matches_get_capture_bytecode_at_index( - condition, i); - - if (capture_bytecode == NULL) { - ERR("Unexpected NULL capture bytecode on condition"); - error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; - goto capture_error; - } - - ret = kernctl_capture(event_notifier_rule->fd, capture_bytecode); - if (ret < 0) { - ERR("Failed to set capture bytecode on event notifier rule fd: fd = %d", - event_notifier_rule->fd); - error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; - goto capture_error; - } - } - - err = kernctl_enable(event_notifier_rule->fd); - if (err < 0) { - switch (-err) { - case EEXIST: - error_code_ret = LTTNG_ERR_KERN_EVENT_EXIST; - break; - default: - PERROR("enable kernel event notifier"); - error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; - break; - } - goto enable_error; - } - - /* Add trigger to kernel token mapping in the hash table. */ - rcu_read_lock(); - cds_lfht_add(kernel_token_to_event_notifier_rule_ht, hash_trigger(trigger), - &event_notifier_rule->ht_node); - rcu_read_unlock(); - - DBG("Created kernel event notifier: name = '%s', fd = %d", - kernel_event_notifier.event.name, - event_notifier_rule->fd); - - return LTTNG_OK; - -capture_error: -add_callsite_error: -enable_error: -set_cloexec_error: -filter_error: - { - const int close_ret = close(event_notifier_rule->fd); - - if (close_ret) { - PERROR("Failed to close kernel event notifier file descriptor: fd = %d", - event_notifier_rule->fd); - } - } -free_event: - free(event_notifier_rule); -error: - return error_code_ret; -} - -enum lttng_error_code kernel_register_event_notifier( - struct lttng_trigger *trigger, - const struct lttng_credentials *cmd_creds) -{ - enum lttng_error_code ret; - enum lttng_condition_status status; - enum lttng_domain_type domain_type; - const struct lttng_event_rule *event_rule; - const struct lttng_condition *const condition = - lttng_trigger_get_const_condition(trigger); - const uint64_t token = lttng_trigger_get_tracer_token(trigger); - - LTTNG_ASSERT(condition); - - /* Does not acquire a reference to the event rule. */ - status = lttng_condition_event_rule_matches_get_rule( - condition, &event_rule); - LTTNG_ASSERT(status == LTTNG_CONDITION_STATUS_OK); - - domain_type = lttng_event_rule_get_domain_type(event_rule); - LTTNG_ASSERT(domain_type == LTTNG_DOMAIN_KERNEL); - - ret = kernel_create_event_notifier_rule(trigger, cmd_creds, token); - if (ret != LTTNG_OK) { - ERR("Failed to create kernel event notifier rule"); - } - - return ret; -} - -enum lttng_error_code kernel_unregister_event_notifier( - const struct lttng_trigger *trigger) -{ - struct ltt_kernel_event_notifier_rule *token_event_rule_element; - struct cds_lfht_node *node; - struct cds_lfht_iter iter; - enum lttng_error_code error_code_ret; - int ret; - - rcu_read_lock(); - - cds_lfht_lookup(kernel_token_to_event_notifier_rule_ht, - hash_trigger(trigger), match_trigger, trigger, &iter); - - node = cds_lfht_iter_get_node(&iter); - if (!node) { - error_code_ret = LTTNG_ERR_TRIGGER_NOT_FOUND; - goto error; - } - - token_event_rule_element = caa_container_of(node, - struct ltt_kernel_event_notifier_rule, ht_node); - - ret = kernel_disable_event_notifier_rule(token_event_rule_element); - if (ret) { - error_code_ret = LTTNG_ERR_FATAL; - goto error; - } - - trace_kernel_destroy_event_notifier_rule(token_event_rule_element); - error_code_ret = LTTNG_OK; - -error: - rcu_read_unlock(); - - return error_code_ret; -} - -int kernel_get_notification_fd(void) -{ - return kernel_tracer_event_notifier_group_notification_fd; -} diff --git a/src/bin/lttng-sessiond/kernel.cpp b/src/bin/lttng-sessiond/kernel.cpp new file mode 100644 index 000000000..22ce819b5 --- /dev/null +++ b/src/bin/lttng-sessiond/kernel.cpp @@ -0,0 +1,2544 @@ +/* + * Copyright (C) 2011 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#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 "event-notifier-error-accounting.h" +#include "lttng-sessiond.h" +#include "lttng-syscall.h" +#include "condition-internal.h" +#include "consumer.h" +#include "kernel.h" +#include "kernel-consumer.h" +#include "kern-modules.h" +#include "sessiond-config.h" +#include "utils.h" +#include "rotate.h" +#include "modprobe.h" +#include "tracker.h" +#include "notification-thread-commands.h" + +/* + * Key used to reference a channel between the sessiond and the consumer. This + * is only read and updated with the session_list lock held. + */ +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_event_notifier_group_fd = -1; +static int kernel_tracer_event_notifier_group_notification_fd = -1; +static struct cds_lfht *kernel_token_to_event_notifier_rule_ht; + +/* + * Add context on a kernel channel. + * + * Assumes the ownership of ctx. + */ +int kernel_add_channel_context(struct ltt_kernel_channel *chan, + struct ltt_kernel_context *ctx) +{ + int ret; + + LTTNG_ASSERT(chan); + LTTNG_ASSERT(ctx); + + DBG("Adding context to channel %s", chan->channel->name); + ret = kernctl_add_context(chan->fd, &ctx->ctx); + if (ret < 0) { + switch (-ret) { + case ENOSYS: + /* Exists but not available for this kernel */ + ret = LTTNG_ERR_KERN_CONTEXT_UNAVAILABLE; + goto error; + case EEXIST: + /* If EEXIST, we just ignore the error */ + ret = 0; + goto end; + default: + PERROR("add context ioctl"); + ret = LTTNG_ERR_KERN_CONTEXT_FAIL; + goto error; + } + } + ret = 0; + +end: + cds_list_add_tail(&ctx->list, &chan->ctx_list); + ctx->in_list = true; + ctx = NULL; +error: + if (ctx) { + trace_kernel_destroy_context(ctx); + } + return ret; +} + +/* + * Create a new kernel session, register it to the kernel tracer and add it to + * the session daemon session. + */ +int kernel_create_session(struct ltt_session *session) +{ + int ret; + struct ltt_kernel_session *lks; + + LTTNG_ASSERT(session); + + /* Allocate data structure */ + lks = trace_kernel_create_session(); + if (lks == NULL) { + ret = -1; + goto error; + } + + /* Kernel tracer session creation */ + ret = kernctl_create_session(kernel_tracer_fd); + if (ret < 0) { + PERROR("ioctl kernel create session"); + goto error; + } + + lks->fd = ret; + /* Prevent fd duplication after execlp() */ + ret = fcntl(lks->fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("fcntl session fd"); + } + + lks->id = session->id; + lks->consumer_fds_sent = 0; + session->kernel_session = lks; + + DBG("Kernel session created (fd: %d)", lks->fd); + + /* + * This is necessary since the creation time is present in the session + * name when it is generated. + */ + if (session->has_auto_generated_name) { + ret = kernctl_session_set_name(lks->fd, DEFAULT_SESSION_NAME); + } else { + ret = kernctl_session_set_name(lks->fd, session->name); + } + if (ret) { + WARN("Could not set kernel session name for session %" PRIu64 " name: %s", + session->id, session->name); + } + + ret = kernctl_session_set_creation_time(lks->fd, session->creation_time); + if (ret) { + WARN("Could not set kernel session creation time for session %" PRIu64 " name: %s", + session->id, session->name); + } + + return 0; + +error: + if (lks) { + trace_kernel_destroy_session(lks); + trace_kernel_free_session(lks); + } + return ret; +} + +/* + * Create a kernel channel, register it to the kernel tracer and add it to the + * kernel session. + */ +int kernel_create_channel(struct ltt_kernel_session *session, + struct lttng_channel *chan) +{ + int ret; + struct ltt_kernel_channel *lkc; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(chan); + + /* Allocate kernel channel */ + lkc = trace_kernel_create_channel(chan); + if (lkc == NULL) { + goto error; + } + + DBG3("Kernel create channel %s with attr: %d, %" PRIu64 ", %" PRIu64 ", %u, %u, %d, %d", + chan->name, lkc->channel->attr.overwrite, + lkc->channel->attr.subbuf_size, lkc->channel->attr.num_subbuf, + lkc->channel->attr.switch_timer_interval, lkc->channel->attr.read_timer_interval, + lkc->channel->attr.live_timer_interval, lkc->channel->attr.output); + + /* Kernel tracer channel creation */ + ret = kernctl_create_channel(session->fd, &lkc->channel->attr); + if (ret < 0) { + PERROR("ioctl kernel create channel"); + goto error; + } + + /* Setup the channel fd */ + lkc->fd = ret; + /* Prevent fd duplication after execlp() */ + ret = fcntl(lkc->fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("fcntl session fd"); + } + + /* Add channel to session */ + cds_list_add(&lkc->list, &session->channel_list.head); + session->channel_count++; + lkc->session = session; + lkc->key = ++next_kernel_channel_key; + + DBG("Kernel channel %s created (fd: %d, key: %" PRIu64 ")", + lkc->channel->name, lkc->fd, lkc->key); + + return 0; + +error: + if (lkc) { + free(lkc->channel); + free(lkc); + } + return -1; +} + +/* + * Create a kernel event notifier group, register it to the kernel tracer and + * add it to the kernel session. + */ +static int kernel_create_event_notifier_group(int *event_notifier_group_fd) +{ + int ret; + int local_fd = -1; + + LTTNG_ASSERT(event_notifier_group_fd); + + /* Kernel event notifier group creation. */ + ret = kernctl_create_event_notifier_group(kernel_tracer_fd); + if (ret < 0) { + PERROR("Failed to create kernel event notifier group"); + ret = -1; + goto error; + } + + local_fd = ret; + + /* Prevent fd duplication after execlp(). */ + ret = fcntl(local_fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("Failed to set FD_CLOEXEC on kernel event notifier group file descriptor: fd = %d", + local_fd); + goto error; + } + + DBG("Created kernel event notifier group: fd = %d", local_fd); + *event_notifier_group_fd = local_fd; + local_fd = -1; + ret = 0; +error: + if (local_fd >= 0) { + ret = close(local_fd); + if (ret) { + PERROR("Failed to close kernel event notifier group file descriptor: fd = %d", + 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. + * + * Returns 0 on success and set the offset out parameter to the offset of the + * elf symbol + * Returns -1 on error + */ +static +int extract_userspace_probe_offset_function_elf( + const struct lttng_userspace_probe_location *probe_location, + uid_t uid, gid_t gid, uint64_t *offset) +{ + int fd; + int ret = 0; + const char *symbol = NULL; + const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; + enum lttng_userspace_probe_location_lookup_method_type lookup_method_type; + + LTTNG_ASSERT(lttng_userspace_probe_location_get_type(probe_location) == + LTTNG_USERSPACE_PROBE_LOCATION_TYPE_FUNCTION); + + lookup = lttng_userspace_probe_location_get_lookup_method( + probe_location); + if (!lookup) { + ret = -1; + goto end; + } + + lookup_method_type = + lttng_userspace_probe_location_lookup_method_get_type(lookup); + + LTTNG_ASSERT(lookup_method_type == + LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF); + + symbol = lttng_userspace_probe_location_function_get_function_name( + probe_location); + if (!symbol) { + ret = -1; + goto end; + } + + fd = lttng_userspace_probe_location_function_get_binary_fd(probe_location); + if (fd < 0) { + ret = -1; + goto end; + } + + 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); + goto end; + } + + DBG("userspace probe elf offset for %s is 0x%jd", symbol, (intmax_t)(*offset)); +end: + return ret; +} + +/* + * Compute the offsets of the instrumentation bytes in the binary based on the + * tracepoint probe location using the SDT lookup method. This function + * allocates the offsets buffer, the caller must free it. + * + * Returns 0 on success and set the offset out parameter to the offsets of the + * SDT tracepoint. + * Returns -1 on error. + */ +static +int extract_userspace_probe_offset_tracepoint_sdt( + const struct lttng_userspace_probe_location *probe_location, + uid_t uid, gid_t gid, uint64_t **offsets, + uint32_t *offsets_count) +{ + enum lttng_userspace_probe_location_lookup_method_type lookup_method_type; + const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; + const char *probe_name = NULL, *provider_name = NULL; + int ret = 0; + int fd, i; + + LTTNG_ASSERT(lttng_userspace_probe_location_get_type(probe_location) == + LTTNG_USERSPACE_PROBE_LOCATION_TYPE_TRACEPOINT); + + lookup = lttng_userspace_probe_location_get_lookup_method(probe_location); + if (!lookup) { + ret = -1; + goto end; + } + + lookup_method_type = + lttng_userspace_probe_location_lookup_method_get_type(lookup); + + LTTNG_ASSERT(lookup_method_type == + LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT); + + + probe_name = lttng_userspace_probe_location_tracepoint_get_probe_name( + probe_location); + if (!probe_name) { + ret = -1; + goto end; + } + + provider_name = lttng_userspace_probe_location_tracepoint_get_provider_name( + probe_location); + if (!provider_name) { + ret = -1; + goto end; + } + + fd = lttng_userspace_probe_location_tracepoint_get_binary_fd(probe_location); + if (fd < 0) { + ret = -1; + goto end; + } + + ret = run_as_extract_sdt_probe_offsets(fd, provider_name, probe_name, + uid, gid, offsets, offsets_count); + if (ret < 0) { + DBG("userspace probe offset calculation failed for sdt " + "probe %s:%s", provider_name, probe_name); + goto end; + } + + if (*offsets_count == 0) { + DBG("no userspace probe offset found"); + goto end; + } + + DBG("%u userspace probe SDT offsets found for %s:%s at:", + *offsets_count, provider_name, probe_name); + for (i = 0; i < *offsets_count; i++) { + DBG("\t0x%jd", (intmax_t)((*offsets)[i])); + } +end: + return ret; +} + +static +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; + int ret; + + lookup_method = lttng_userspace_probe_location_get_lookup_method(location); + if (!lookup_method) { + ret = -1; + goto end; + } + + type = lttng_userspace_probe_location_lookup_method_get_type(lookup_method); + switch (type) { + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF: + { + struct lttng_kernel_abi_event_callsite callsite; + uint64_t offset; + + ret = extract_userspace_probe_offset_function_elf(location, + uid, gid, &offset); + if (ret) { + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto end; + } + + callsite.u.uprobe.offset = offset; + ret = kernctl_add_callsite(fd, &callsite); + if (ret) { + WARN("Failed to add callsite to ELF userspace probe."); + ret = LTTNG_ERR_KERN_ENABLE_FAIL; + goto end; + } + break; + } + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: + { + int i; + uint64_t *offsets = NULL; + uint32_t offsets_count; + struct lttng_kernel_abi_event_callsite callsite; + + /* + * This call allocates the offsets buffer. This buffer must be freed + * by the caller + */ + ret = extract_userspace_probe_offset_tracepoint_sdt(location, + uid, gid, &offsets, &offsets_count); + if (ret) { + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto end; + } + for (i = 0; i < offsets_count; i++) { + callsite.u.uprobe.offset = offsets[i]; + ret = kernctl_add_callsite(fd, &callsite); + if (ret) { + WARN("Failed to add callsite to SDT userspace probe"); + ret = LTTNG_ERR_KERN_ENABLE_FAIL; + free(offsets); + goto end; + } + } + free(offsets); + break; + } + default: + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto end; + } +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) +{ + int ret; + const struct lttng_userspace_probe_location *location = NULL; + + LTTNG_ASSERT(ev); + LTTNG_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("Failed to add callsite to userspace probe event '%s'", + ev->name); + } + +end: + return ret; +} + +/* + * Extract the offsets of the instrumentation point for the different look-up + * methods. + */ +static int userspace_probe_event_rule_add_callsites( + const struct lttng_event_rule *rule, + const struct lttng_credentials *creds, + int fd) +{ + int ret; + enum lttng_event_rule_status status; + enum lttng_event_rule_type event_rule_type; + const struct lttng_userspace_probe_location *location = NULL; + + LTTNG_ASSERT(rule); + LTTNG_ASSERT(creds); + + event_rule_type = lttng_event_rule_get_type(rule); + LTTNG_ASSERT(event_rule_type == LTTNG_EVENT_RULE_TYPE_KERNEL_UPROBE); + + status = lttng_event_rule_kernel_uprobe_get_location(rule, &location); + if (status != LTTNG_EVENT_RULE_STATUS_OK || !location) { + ret = -1; + goto end; + } + + ret = userspace_probe_add_callsite(location, + lttng_credentials_get_uid(creds), + lttng_credentials_get_gid(creds), fd); + if (ret) { + WARN("Failed to add callsite to user space probe object: fd = %d", + 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. + * We own filter_expression and filter. + */ +int kernel_create_event(struct lttng_event *ev, + struct ltt_kernel_channel *channel, + char *filter_expression, + struct lttng_bytecode *filter) +{ + int err, fd; + enum lttng_error_code ret; + struct ltt_kernel_event *event; + + LTTNG_ASSERT(ev); + LTTNG_ASSERT(channel); + + /* We pass ownership of filter_expression and filter */ + ret = trace_kernel_create_event(ev, filter_expression, + filter, &event); + if (ret != LTTNG_OK) { + goto error; + } + + fd = kernctl_create_event(channel->fd, event->event); + if (fd < 0) { + switch (-fd) { + case EEXIST: + ret = LTTNG_ERR_KERN_EVENT_EXIST; + break; + case ENOSYS: + WARN("Event type not implemented"); + ret = LTTNG_ERR_KERN_EVENT_ENOSYS; + break; + case ENOENT: + WARN("Event %s not found!", ev->name); + ret = LTTNG_ERR_KERN_ENABLE_FAIL; + break; + default: + ret = LTTNG_ERR_KERN_ENABLE_FAIL; + PERROR("create event ioctl"); + } + goto free_event; + } + + event->type = ev->type; + event->fd = fd; + /* Prevent fd duplication after execlp() */ + err = fcntl(event->fd, F_SETFD, FD_CLOEXEC); + if (err < 0) { + PERROR("fcntl session fd"); + } + + if (filter) { + err = kernctl_filter(event->fd, 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 (ev->type == LTTNG_EVENT_USERSPACE_PROBE) { + ret = (lttng_error_code) userspace_probe_event_add_callsites(ev, channel->session, + 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 event"); + ret = LTTNG_ERR_KERN_ENABLE_FAIL; + break; + } + goto enable_error; + } + + /* Add event to event list */ + cds_list_add(&event->list, &channel->events_list.head); + channel->event_count++; + + DBG("Event %s created (fd: %d)", ev->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; +} + +/* + * Disable a kernel channel. + */ +int kernel_disable_channel(struct ltt_kernel_channel *chan) +{ + int ret; + + LTTNG_ASSERT(chan); + + ret = kernctl_disable(chan->fd); + if (ret < 0) { + PERROR("disable chan ioctl"); + goto error; + } + + chan->enabled = 0; + DBG("Kernel channel %s disabled (fd: %d, key: %" PRIu64 ")", + chan->channel->name, chan->fd, chan->key); + + return 0; + +error: + return ret; +} + +/* + * Enable a kernel channel. + */ +int kernel_enable_channel(struct ltt_kernel_channel *chan) +{ + int ret; + + LTTNG_ASSERT(chan); + + ret = kernctl_enable(chan->fd); + if (ret < 0 && ret != -EEXIST) { + PERROR("Enable kernel chan"); + goto error; + } + + chan->enabled = 1; + DBG("Kernel channel %s enabled (fd: %d, key: %" PRIu64 ")", + chan->channel->name, chan->fd, chan->key); + + return 0; + +error: + return ret; +} + +/* + * Enable a kernel event. + */ +int kernel_enable_event(struct ltt_kernel_event *event) +{ + int ret; + + LTTNG_ASSERT(event); + + ret = kernctl_enable(event->fd); + if (ret < 0) { + switch (-ret) { + case EEXIST: + ret = LTTNG_ERR_KERN_EVENT_EXIST; + break; + default: + PERROR("enable kernel event"); + break; + } + goto error; + } + + event->enabled = 1; + DBG("Kernel event %s enabled (fd: %d)", event->event->name, event->fd); + + return 0; + +error: + return ret; +} + +/* + * Disable a kernel event. + */ +int kernel_disable_event(struct ltt_kernel_event *event) +{ + int ret; + + LTTNG_ASSERT(event); + + ret = kernctl_disable(event->fd); + if (ret < 0) { + PERROR("Failed to disable kernel event: name = '%s', fd = %d", + event->event->name, event->fd); + goto error; + } + + event->enabled = 0; + DBG("Kernel event %s disabled (fd: %d)", event->event->name, event->fd); + + return 0; + +error: + return ret; +} + +/* + * Disable a kernel event notifier. + */ +static +int kernel_disable_event_notifier_rule(struct ltt_kernel_event_notifier_rule *event) +{ + int ret; + + LTTNG_ASSERT(event); + + rcu_read_lock(); + cds_lfht_del(kernel_token_to_event_notifier_rule_ht, &event->ht_node); + rcu_read_unlock(); + + ret = kernctl_disable(event->fd); + if (ret < 0) { + PERROR("Failed to disable kernel event notifier: fd = %d, token = %" PRIu64, + event->fd, event->token); + goto error; + } + + event->enabled = 0; + DBG("Disabled kernel event notifier: fd = %d, token = %" PRIu64, + event->fd, event->token); + +error: + return ret; +} + +static +struct process_attr_tracker *_kernel_get_process_attr_tracker( + struct ltt_kernel_session *session, + enum lttng_process_attr process_attr) +{ + switch (process_attr) { + case LTTNG_PROCESS_ATTR_PROCESS_ID: + return session->tracker_pid; + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + return session->tracker_vpid; + case LTTNG_PROCESS_ATTR_USER_ID: + return session->tracker_uid; + case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: + return session->tracker_vuid; + case LTTNG_PROCESS_ATTR_GROUP_ID: + return session->tracker_gid; + case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: + return session->tracker_vgid; + default: + return NULL; + } +} + +const struct process_attr_tracker *kernel_get_process_attr_tracker( + struct ltt_kernel_session *session, + enum lttng_process_attr process_attr) +{ + return (const struct process_attr_tracker *) + _kernel_get_process_attr_tracker(session, process_attr); +} + +enum lttng_error_code kernel_process_attr_tracker_set_tracking_policy( + struct ltt_kernel_session *session, + enum lttng_process_attr process_attr, + enum lttng_tracking_policy policy) +{ + int ret; + enum lttng_error_code ret_code = LTTNG_OK; + struct process_attr_tracker *tracker = + _kernel_get_process_attr_tracker(session, process_attr); + enum lttng_tracking_policy previous_policy; + + if (!tracker) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + previous_policy = process_attr_tracker_get_tracking_policy(tracker); + ret = process_attr_tracker_set_tracking_policy(tracker, policy); + if (ret) { + ret_code = LTTNG_ERR_UNK; + goto end; + } + + if (previous_policy == policy) { + goto end; + } + + switch (policy) { + case LTTNG_TRACKING_POLICY_INCLUDE_ALL: + if (process_attr == LTTNG_PROCESS_ATTR_PROCESS_ID) { + /* + * Maintain a special case for the process ID process + * attribute tracker as it was the only supported + * attribute prior to 2.12. + */ + ret = kernctl_track_pid(session->fd, -1); + } else { + ret = kernctl_track_id(session->fd, process_attr, -1); + } + break; + case LTTNG_TRACKING_POLICY_EXCLUDE_ALL: + case LTTNG_TRACKING_POLICY_INCLUDE_SET: + /* fall-through. */ + if (process_attr == LTTNG_PROCESS_ATTR_PROCESS_ID) { + /* + * Maintain a special case for the process ID process + * attribute tracker as it was the only supported + * attribute prior to 2.12. + */ + ret = kernctl_untrack_pid(session->fd, -1); + } else { + ret = kernctl_untrack_id(session->fd, process_attr, -1); + } + break; + default: + abort(); + } + /* kern-ctl error handling */ + switch (-ret) { + case 0: + ret_code = LTTNG_OK; + break; + case EINVAL: + ret_code = LTTNG_ERR_INVALID; + break; + case ENOMEM: + ret_code = LTTNG_ERR_NOMEM; + break; + case EEXIST: + ret_code = LTTNG_ERR_PROCESS_ATTR_EXISTS; + break; + default: + ret_code = LTTNG_ERR_UNK; + break; + } +end: + return ret_code; +} + +enum lttng_error_code kernel_process_attr_tracker_inclusion_set_add_value( + struct ltt_kernel_session *session, + enum lttng_process_attr process_attr, + const struct process_attr_value *value) +{ + int ret, integral_value; + enum lttng_error_code ret_code; + struct process_attr_tracker *tracker; + enum process_attr_tracker_status status; + + /* + * Convert process attribute tracker value to the integral + * representation required by the kern-ctl API. + */ + switch (process_attr) { + case LTTNG_PROCESS_ATTR_PROCESS_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + integral_value = (int) value->value.pid; + break; + case LTTNG_PROCESS_ATTR_USER_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: + if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME) { + uid_t uid; + + ret_code = utils_user_id_from_name( + value->value.user_name, &uid); + if (ret_code != LTTNG_OK) { + goto end; + } + integral_value = (int) uid; + } else { + integral_value = (int) value->value.uid; + } + break; + case LTTNG_PROCESS_ATTR_GROUP_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: + if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME) { + gid_t gid; + + ret_code = utils_group_id_from_name( + value->value.group_name, &gid); + if (ret_code != LTTNG_OK) { + goto end; + } + integral_value = (int) gid; + } else { + integral_value = (int) value->value.gid; + } + break; + default: + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + tracker = _kernel_get_process_attr_tracker(session, process_attr); + if (!tracker) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + status = process_attr_tracker_inclusion_set_add_value(tracker, value); + if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { + switch (status) { + case PROCESS_ATTR_TRACKER_STATUS_EXISTS: + ret_code = LTTNG_ERR_PROCESS_ATTR_EXISTS; + break; + case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: + ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; + break; + case PROCESS_ATTR_TRACKER_STATUS_ERROR: + default: + ret_code = LTTNG_ERR_UNK; + break; + } + goto end; + } + + DBG("Kernel track %s %d for session id %" PRIu64, + lttng_process_attr_to_string(process_attr), + integral_value, session->id); + if (process_attr == LTTNG_PROCESS_ATTR_PROCESS_ID) { + /* + * Maintain a special case for the process ID process attribute + * tracker as it was the only supported attribute prior to 2.12. + */ + ret = kernctl_track_pid(session->fd, integral_value); + } else { + ret = kernctl_track_id( + session->fd, process_attr, integral_value); + } + if (ret == 0) { + ret_code = LTTNG_OK; + goto end; + } + + kernel_wait_quiescent(); + + /* kern-ctl error handling */ + switch (-ret) { + case 0: + ret_code = LTTNG_OK; + break; + case EINVAL: + ret_code = LTTNG_ERR_INVALID; + break; + case ENOMEM: + ret_code = LTTNG_ERR_NOMEM; + break; + case EEXIST: + ret_code = LTTNG_ERR_PROCESS_ATTR_EXISTS; + break; + default: + ret_code = LTTNG_ERR_UNK; + break; + } + + /* Attempt to remove the value from the tracker. */ + status = process_attr_tracker_inclusion_set_remove_value( + tracker, value); + if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { + ERR("Failed to roll-back the tracking of kernel %s process attribute %d while handling a kern-ctl error", + lttng_process_attr_to_string(process_attr), + integral_value); + } +end: + return ret_code; +} + +enum lttng_error_code kernel_process_attr_tracker_inclusion_set_remove_value( + struct ltt_kernel_session *session, + enum lttng_process_attr process_attr, + const struct process_attr_value *value) +{ + int ret, integral_value; + enum lttng_error_code ret_code; + struct process_attr_tracker *tracker; + enum process_attr_tracker_status status; + + /* + * Convert process attribute tracker value to the integral + * representation required by the kern-ctl API. + */ + switch (process_attr) { + case LTTNG_PROCESS_ATTR_PROCESS_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + integral_value = (int) value->value.pid; + break; + case LTTNG_PROCESS_ATTR_USER_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: + if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME) { + uid_t uid; + + ret_code = utils_user_id_from_name( + value->value.user_name, &uid); + if (ret_code != LTTNG_OK) { + goto end; + } + integral_value = (int) uid; + } else { + integral_value = (int) value->value.uid; + } + break; + case LTTNG_PROCESS_ATTR_GROUP_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: + if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME) { + gid_t gid; + + ret_code = utils_group_id_from_name( + value->value.group_name, &gid); + if (ret_code != LTTNG_OK) { + goto end; + } + integral_value = (int) gid; + } else { + integral_value = (int) value->value.gid; + } + break; + default: + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + tracker = _kernel_get_process_attr_tracker(session, process_attr); + if (!tracker) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + status = process_attr_tracker_inclusion_set_remove_value( + tracker, value); + if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { + switch (status) { + case PROCESS_ATTR_TRACKER_STATUS_MISSING: + ret_code = LTTNG_ERR_PROCESS_ATTR_MISSING; + break; + case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: + ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; + break; + case PROCESS_ATTR_TRACKER_STATUS_ERROR: + default: + ret_code = LTTNG_ERR_UNK; + break; + } + goto end; + } + + DBG("Kernel track %s %d for session id %" PRIu64, + lttng_process_attr_to_string(process_attr), + integral_value, session->id); + if (process_attr == LTTNG_PROCESS_ATTR_PROCESS_ID) { + /* + * Maintain a special case for the process ID process attribute + * tracker as it was the only supported attribute prior to 2.12. + */ + ret = kernctl_untrack_pid(session->fd, integral_value); + } else { + ret = kernctl_untrack_id( + session->fd, process_attr, integral_value); + } + if (ret == 0) { + ret_code = LTTNG_OK; + goto end; + } + kernel_wait_quiescent(); + + /* kern-ctl error handling */ + switch (-ret) { + case 0: + ret_code = LTTNG_OK; + break; + case EINVAL: + ret_code = LTTNG_ERR_INVALID; + break; + case ENOMEM: + ret_code = LTTNG_ERR_NOMEM; + break; + case ENOENT: + ret_code = LTTNG_ERR_PROCESS_ATTR_MISSING; + break; + default: + ret_code = LTTNG_ERR_UNK; + break; + } + + /* Attempt to add the value to the tracker. */ + status = process_attr_tracker_inclusion_set_add_value( + tracker, value); + if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { + ERR("Failed to roll-back the tracking of kernel %s process attribute %d while handling a kern-ctl error", + lttng_process_attr_to_string(process_attr), + integral_value); + } +end: + return ret_code; +} + +/* + * Create kernel metadata, open from the kernel tracer and add it to the + * kernel session. + */ +int kernel_open_metadata(struct ltt_kernel_session *session) +{ + int ret; + struct ltt_kernel_metadata *lkm = NULL; + + LTTNG_ASSERT(session); + + /* Allocate kernel metadata */ + lkm = trace_kernel_create_metadata(); + if (lkm == NULL) { + goto error; + } + + /* Kernel tracer metadata creation */ + ret = kernctl_open_metadata(session->fd, &lkm->conf->attr); + if (ret < 0) { + goto error_open; + } + + lkm->fd = ret; + lkm->key = ++next_kernel_channel_key; + /* Prevent fd duplication after execlp() */ + ret = fcntl(lkm->fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("fcntl session fd"); + } + + session->metadata = lkm; + + DBG("Kernel metadata opened (fd: %d)", lkm->fd); + + return 0; + +error_open: + trace_kernel_destroy_metadata(lkm); +error: + return -1; +} + +/* + * Start tracing session. + */ +int kernel_start_session(struct ltt_kernel_session *session) +{ + int ret; + + LTTNG_ASSERT(session); + + ret = kernctl_start_session(session->fd); + if (ret < 0) { + PERROR("ioctl start session"); + goto error; + } + + DBG("Kernel session started"); + + return 0; + +error: + return ret; +} + +/* + * Make a kernel wait to make sure in-flight probe have completed. + */ +void kernel_wait_quiescent(void) +{ + int ret; + int fd = kernel_tracer_fd; + + DBG("Kernel quiescent wait on %d", fd); + + ret = kernctl_wait_quiescent(fd); + if (ret < 0) { + PERROR("wait quiescent ioctl"); + ERR("Kernel quiescent wait failed"); + } +} + +/* + * Force flush buffer of metadata. + */ +int kernel_metadata_flush_buffer(int fd) +{ + int ret; + + DBG("Kernel flushing metadata buffer on fd %d", fd); + + ret = kernctl_buffer_flush(fd); + if (ret < 0) { + ERR("Fail to flush metadata buffers %d (ret: %d)", fd, ret); + } + + return 0; +} + +/* + * Force flush buffer for channel. + */ +int kernel_flush_buffer(struct ltt_kernel_channel *channel) +{ + int ret; + struct ltt_kernel_stream *stream; + + LTTNG_ASSERT(channel); + + DBG("Flush buffer for channel %s", channel->channel->name); + + cds_list_for_each_entry(stream, &channel->stream_list.head, list) { + DBG("Flushing channel stream %d", stream->fd); + ret = kernctl_buffer_flush(stream->fd); + if (ret < 0) { + PERROR("ioctl"); + ERR("Fail to flush buffer for stream %d (ret: %d)", + stream->fd, ret); + } + } + + return 0; +} + +/* + * Stop tracing session. + */ +int kernel_stop_session(struct ltt_kernel_session *session) +{ + int ret; + + LTTNG_ASSERT(session); + + ret = kernctl_stop_session(session->fd); + if (ret < 0) { + goto error; + } + + DBG("Kernel session stopped"); + + return 0; + +error: + return ret; +} + +/* + * Open stream of channel, register it to the kernel tracer and add it + * to the stream list of the channel. + * + * Note: given that the streams may appear in random order wrt CPU + * number (e.g. cpu hotplug), the index value of the stream number in + * the stream name is not necessarily linked to the CPU number. + * + * Return the number of created stream. Else, a negative value. + */ +int kernel_open_channel_stream(struct ltt_kernel_channel *channel) +{ + int ret; + struct ltt_kernel_stream *lks; + + LTTNG_ASSERT(channel); + + while ((ret = kernctl_create_stream(channel->fd)) >= 0) { + lks = trace_kernel_create_stream(channel->channel->name, + channel->stream_count); + if (lks == NULL) { + ret = close(ret); + if (ret) { + PERROR("close"); + } + goto error; + } + + lks->fd = ret; + /* Prevent fd duplication after execlp() */ + ret = fcntl(lks->fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("fcntl session fd"); + } + + lks->tracefile_size = channel->channel->attr.tracefile_size; + lks->tracefile_count = channel->channel->attr.tracefile_count; + + /* Add stream to channel stream list */ + cds_list_add(&lks->list, &channel->stream_list.head); + channel->stream_count++; + + DBG("Kernel stream %s created (fd: %d, state: %d)", lks->name, lks->fd, + lks->state); + } + + return channel->stream_count; + +error: + return -1; +} + +/* + * Open the metadata stream and set it to the kernel session. + */ +int kernel_open_metadata_stream(struct ltt_kernel_session *session) +{ + int ret; + + LTTNG_ASSERT(session); + + ret = kernctl_create_stream(session->metadata->fd); + if (ret < 0) { + PERROR("kernel create metadata stream"); + goto error; + } + + DBG("Kernel metadata stream created (fd: %d)", ret); + session->metadata_stream_fd = ret; + /* Prevent fd duplication after execlp() */ + ret = fcntl(session->metadata_stream_fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("fcntl session fd"); + } + + return 0; + +error: + return -1; +} + +/* + * Get the event list from the kernel tracer and return the number of elements. + */ +ssize_t kernel_list_events(struct lttng_event **events) +{ + int fd, ret; + char *event; + size_t nbmem, count = 0; + FILE *fp; + struct lttng_event *elist; + + LTTNG_ASSERT(events); + + fd = kernctl_tracepoint_list(kernel_tracer_fd); + if (fd < 0) { + PERROR("kernel tracepoint list"); + goto error; + } + + fp = fdopen(fd, "r"); + if (fp == NULL) { + PERROR("kernel tracepoint list fdopen"); + goto error_fp; + } + + /* + * Init memory size counter + * See kernel-ctl.h for explanation of this value + */ + nbmem = KERNEL_EVENT_INIT_LIST_SIZE; + elist = (lttng_event *) zmalloc(sizeof(struct lttng_event) * nbmem); + if (elist == NULL) { + PERROR("alloc list events"); + count = -ENOMEM; + goto end; + } + + while (fscanf(fp, "event { name = %m[^;]; };\n", &event) == 1) { + if (count >= nbmem) { + struct lttng_event *new_elist; + size_t new_nbmem; + + new_nbmem = nbmem << 1; + DBG("Reallocating event list from %zu to %zu bytes", + nbmem, new_nbmem); + new_elist = (lttng_event *) realloc(elist, new_nbmem * sizeof(struct lttng_event)); + if (new_elist == NULL) { + PERROR("realloc list events"); + free(event); + free(elist); + count = -ENOMEM; + goto end; + } + /* Zero the new memory */ + memset(new_elist + nbmem, 0, + (new_nbmem - nbmem) * sizeof(struct lttng_event)); + nbmem = new_nbmem; + elist = new_elist; + } + strncpy(elist[count].name, event, LTTNG_SYMBOL_NAME_LEN); + elist[count].name[LTTNG_SYMBOL_NAME_LEN - 1] = '\0'; + elist[count].enabled = -1; + count++; + free(event); + } + + *events = elist; + DBG("Kernel list events done (%zu events)", count); +end: + ret = fclose(fp); /* closes both fp and fd */ + if (ret) { + PERROR("fclose"); + } + return count; + +error_fp: + ret = close(fd); + if (ret) { + PERROR("close"); + } +error: + return -1; +} + +/* + * Get kernel version and validate it. + */ +int kernel_validate_version(struct lttng_kernel_abi_tracer_version *version, + struct lttng_kernel_abi_tracer_abi_version *abi_version) +{ + int ret; + + ret = kernctl_tracer_version(kernel_tracer_fd, version); + if (ret < 0) { + ERR("Failed to retrieve the lttng-modules version"); + goto error; + } + + /* Validate version */ + if (version->major != VERSION_MAJOR) { + ERR("Kernel tracer major version (%d) is not compatible with lttng-tools major version (%d)", + version->major, VERSION_MAJOR); + goto error_version; + } + ret = kernctl_tracer_abi_version(kernel_tracer_fd, abi_version); + if (ret < 0) { + ERR("Failed to retrieve lttng-modules ABI version"); + goto error; + } + if (abi_version->major != LTTNG_KERNEL_ABI_MAJOR_VERSION) { + ERR("Kernel tracer ABI version (%d.%d) does not match the expected ABI major version (%d.*)", + abi_version->major, abi_version->minor, + LTTNG_KERNEL_ABI_MAJOR_VERSION); + goto error; + } + DBG2("Kernel tracer version validated (%d.%d, ABI %d.%d)", + version->major, version->minor, + abi_version->major, abi_version->minor); + return 0; + +error_version: + ret = -1; + +error: + ERR("Kernel tracer version check failed; kernel tracing will not be available"); + return ret; +} + +/* + * Kernel work-arounds called at the start of sessiond main(). + */ +int init_kernel_workarounds(void) +{ + int ret; + FILE *fp; + + /* + * boot_id needs to be read once before being used concurrently + * to deal with a Linux kernel race. A fix is proposed for + * upstream, but the work-around is needed for older kernels. + */ + fp = fopen("/proc/sys/kernel/random/boot_id", "r"); + if (!fp) { + goto end_boot_id; + } + while (!feof(fp)) { + char buf[37] = ""; + + ret = fread(buf, 1, sizeof(buf), fp); + if (ret < 0) { + /* Ignore error, we don't really care */ + } + } + ret = fclose(fp); + if (ret) { + PERROR("fclose"); + } +end_boot_id: + return 0; +} + +/* + * Teardown of a kernel session, keeping data required by destroy notifiers. + */ +void kernel_destroy_session(struct ltt_kernel_session *ksess) +{ + struct lttng_trace_chunk *trace_chunk; + + if (ksess == NULL) { + DBG3("No kernel session when tearing down session"); + return; + } + + DBG("Tearing down kernel session"); + trace_chunk = ksess->current_trace_chunk; + + /* + * Destroy channels on the consumer if at least one FD has been sent and we + * are in no output mode because the streams are in *no* monitor mode so we + * have to send a command to clean them up or else they leaked. + */ + if (!ksess->output_traces && ksess->consumer_fds_sent) { + int ret; + struct consumer_socket *socket; + struct lttng_ht_iter iter; + + /* For each consumer socket. */ + rcu_read_lock(); + cds_lfht_for_each_entry(ksess->consumer->socks->ht, &iter.iter, + socket, node.node) { + struct ltt_kernel_channel *chan; + + /* For each channel, ask the consumer to destroy it. */ + cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { + ret = kernel_consumer_destroy_channel(socket, chan); + if (ret < 0) { + /* Consumer is probably dead. Use next socket. */ + continue; + } + } + } + rcu_read_unlock(); + } + + /* Close any relayd session */ + consumer_output_send_destroy_relayd(ksess->consumer); + + trace_kernel_destroy_session(ksess); + lttng_trace_chunk_put(trace_chunk); +} + +/* Teardown of data required by destroy notifiers. */ +void kernel_free_session(struct ltt_kernel_session *ksess) +{ + if (ksess == NULL) { + return; + } + trace_kernel_free_session(ksess); +} + +/* + * Destroy a kernel channel object. It does not do anything on the tracer side. + */ +void kernel_destroy_channel(struct ltt_kernel_channel *kchan) +{ + struct ltt_kernel_session *ksess = NULL; + + LTTNG_ASSERT(kchan); + LTTNG_ASSERT(kchan->channel); + + DBG3("Kernel destroy channel %s", kchan->channel->name); + + /* Update channel count of associated session. */ + if (kchan->session) { + /* Keep pointer reference so we can update it after the destroy. */ + ksess = kchan->session; + } + + trace_kernel_destroy_channel(kchan); + + /* + * At this point the kernel channel is not visible anymore. This is safe + * since in order to work on a visible kernel session, the tracing session + * lock (ltt_session.lock) MUST be acquired. + */ + if (ksess) { + ksess->channel_count--; + } +} + +/* + * Take a snapshot for a given kernel session. + * + * Return LTTNG_OK on success or else return a LTTNG_ERR code. + */ +enum lttng_error_code kernel_snapshot_record( + struct ltt_kernel_session *ksess, + const struct consumer_output *output, int wait, + uint64_t nb_packets_per_stream) +{ + int err, ret, saved_metadata_fd; + enum lttng_error_code status = LTTNG_OK; + struct consumer_socket *socket; + struct lttng_ht_iter iter; + struct ltt_kernel_metadata *saved_metadata; + char *trace_path = NULL; + size_t consumer_path_offset = 0; + + LTTNG_ASSERT(ksess); + LTTNG_ASSERT(ksess->consumer); + LTTNG_ASSERT(output); + + DBG("Kernel snapshot record started"); + + /* Save current metadata since the following calls will change it. */ + saved_metadata = ksess->metadata; + saved_metadata_fd = ksess->metadata_stream_fd; + + rcu_read_lock(); + + ret = kernel_open_metadata(ksess); + if (ret < 0) { + status = LTTNG_ERR_KERN_META_FAIL; + goto error; + } + + ret = kernel_open_metadata_stream(ksess); + if (ret < 0) { + status = LTTNG_ERR_KERN_META_FAIL; + goto error_open_stream; + } + + trace_path = setup_channel_trace_path(ksess->consumer, + "", &consumer_path_offset); + if (!trace_path) { + status = LTTNG_ERR_INVALID; + goto error; + } + /* Send metadata to consumer and snapshot everything. */ + cds_lfht_for_each_entry(output->socks->ht, &iter.iter, + socket, node.node) { + struct ltt_kernel_channel *chan; + + pthread_mutex_lock(socket->lock); + /* This stream must not be monitored by the consumer. */ + ret = kernel_consumer_add_metadata(socket, ksess, 0); + pthread_mutex_unlock(socket->lock); + if (ret < 0) { + status = LTTNG_ERR_KERN_META_FAIL; + goto error_consumer; + } + + /* For each channel, ask the consumer to snapshot it. */ + cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { + status = consumer_snapshot_channel(socket, chan->key, output, 0, + ksess->uid, ksess->gid, + &trace_path[consumer_path_offset], wait, + nb_packets_per_stream); + if (status != LTTNG_OK) { + (void) kernel_consumer_destroy_metadata(socket, + ksess->metadata); + goto error_consumer; + } + } + + /* Snapshot metadata, */ + status = consumer_snapshot_channel(socket, ksess->metadata->key, output, + 1, ksess->uid, ksess->gid, &trace_path[consumer_path_offset], + wait, 0); + if (status != LTTNG_OK) { + goto error_consumer; + } + + /* + * The metadata snapshot is done, ask the consumer to destroy it since + * it's not monitored on the consumer side. + */ + (void) kernel_consumer_destroy_metadata(socket, ksess->metadata); + } + +error_consumer: + /* Close newly opened metadata stream. It's now on the consumer side. */ + err = close(ksess->metadata_stream_fd); + if (err < 0) { + PERROR("close snapshot kernel"); + } + +error_open_stream: + trace_kernel_destroy_metadata(ksess->metadata); +error: + /* Restore metadata state.*/ + ksess->metadata = saved_metadata; + ksess->metadata_stream_fd = saved_metadata_fd; + rcu_read_unlock(); + free(trace_path); + return status; +} + +/* + * Get the syscall mask array from the kernel tracer. + * + * Return 0 on success else a negative value. In both case, syscall_mask should + * be freed. + */ +int kernel_syscall_mask(int chan_fd, char **syscall_mask, uint32_t *nr_bits) +{ + LTTNG_ASSERT(syscall_mask); + LTTNG_ASSERT(nr_bits); + + return kernctl_syscall_mask(chan_fd, syscall_mask, nr_bits); +} + +static +int kernel_tracer_abi_greater_or_equal(unsigned int major, unsigned int minor) +{ + int ret; + struct lttng_kernel_abi_tracer_abi_version abi; + + ret = kernctl_tracer_abi_version(kernel_tracer_fd, &abi); + if (ret < 0) { + ERR("Failed to retrieve lttng-modules ABI version"); + goto error; + } + + ret = abi.major > major || (abi.major == major && abi.minor >= minor); +error: + return ret; +} + +/* + * Check for the support of the RING_BUFFER_SNAPSHOT_SAMPLE_POSITIONS via abi + * version number. + * + * Return 1 on success, 0 when feature is not supported, negative value in case + * of errors. + */ +int kernel_supports_ring_buffer_snapshot_sample_positions(void) +{ + /* + * RING_BUFFER_SNAPSHOT_SAMPLE_POSITIONS was introduced in 2.3 + */ + return kernel_tracer_abi_greater_or_equal(2, 3); +} + +/* + * Check for the support of the packet sequence number via abi version number. + * + * Return 1 on success, 0 when feature is not supported, negative value in case + * of errors. + */ +int kernel_supports_ring_buffer_packet_sequence_number(void) +{ + /* + * Packet sequence number was introduced in LTTng 2.8, + * lttng-modules ABI 2.1. + */ + return kernel_tracer_abi_greater_or_equal(2, 1); +} + +/* + * Check for the support of event notifiers via abi version number. + * + * Return 1 on success, 0 when feature is not supported, negative value in case + * of errors. + */ +int kernel_supports_event_notifiers(void) +{ + /* + * Event notifiers were introduced in LTTng 2.13, lttng-modules ABI 2.6. + */ + return kernel_tracer_abi_greater_or_equal(2, 6); +} + +/* + * Rotate a kernel session. + * + * Return LTTNG_OK on success or else an LTTng error code. + */ +enum lttng_error_code kernel_rotate_session(struct ltt_session *session) +{ + int ret; + enum lttng_error_code status = LTTNG_OK; + struct consumer_socket *socket; + struct lttng_ht_iter iter; + struct ltt_kernel_session *ksess = session->kernel_session; + + LTTNG_ASSERT(ksess); + LTTNG_ASSERT(ksess->consumer); + + DBG("Rotate kernel session %s started (session %" PRIu64 ")", + session->name, session->id); + + rcu_read_lock(); + + /* + * Note that this loop will end after one iteration given that there is + * only one kernel consumer. + */ + cds_lfht_for_each_entry(ksess->consumer->socks->ht, &iter.iter, + socket, node.node) { + struct ltt_kernel_channel *chan; + + /* For each channel, ask the consumer to rotate it. */ + cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { + DBG("Rotate kernel channel %" PRIu64 ", session %s", + chan->key, session->name); + ret = consumer_rotate_channel(socket, chan->key, + ksess->uid, ksess->gid, ksess->consumer, + /* is_metadata_channel */ false); + if (ret < 0) { + status = LTTNG_ERR_ROTATION_FAIL_CONSUMER; + goto error; + } + } + + /* + * Rotate the metadata channel. + */ + ret = consumer_rotate_channel(socket, ksess->metadata->key, + ksess->uid, ksess->gid, ksess->consumer, + /* is_metadata_channel */ true); + if (ret < 0) { + status = LTTNG_ERR_ROTATION_FAIL_CONSUMER; + goto error; + } + } + +error: + rcu_read_unlock(); + return status; +} + +enum lttng_error_code kernel_create_channel_subdirectories( + const struct ltt_kernel_session *ksess) +{ + enum lttng_error_code ret = LTTNG_OK; + enum lttng_trace_chunk_status chunk_status; + + rcu_read_lock(); + LTTNG_ASSERT(ksess->current_trace_chunk); + + /* + * Create the index subdirectory which will take care + * of implicitly creating the channel's path. + */ + chunk_status = lttng_trace_chunk_create_subdirectory( + ksess->current_trace_chunk, + DEFAULT_KERNEL_TRACE_DIR "/" DEFAULT_INDEX_DIR); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = LTTNG_ERR_CREATE_DIR_FAIL; + goto error; + } +error: + rcu_read_unlock(); + return ret; +} + +/* + * Setup necessary data for kernel tracer action. + */ +int init_kernel_tracer(void) +{ + int ret; + bool is_root = !getuid(); + + /* Modprobe lttng kernel modules */ + ret = modprobe_lttng_control(); + if (ret < 0) { + goto error; + } + + /* Open debugfs lttng */ + kernel_tracer_fd = open(module_proc_lttng, O_RDWR); + if (kernel_tracer_fd < 0) { + DBG("Failed to open %s", module_proc_lttng); + goto error_open; + } + + /* Validate kernel version */ + ret = kernel_validate_version(&the_kernel_tracer_version, + &the_kernel_tracer_abi_version); + if (ret < 0) { + goto error_version; + } + + ret = modprobe_lttng_data(); + if (ret < 0) { + goto error_modules; + } + + ret = kernel_supports_ring_buffer_snapshot_sample_positions(); + 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_supports_event_notifiers(); + if (ret < 0) { + ERR("Failed to check for kernel tracer event notifier support"); + goto error_modules; + } + ret = kernel_create_event_notifier_group(&kernel_tracer_event_notifier_group_fd); + if (ret < 0) { + /* This is not fatal. */ + WARN("Failed to create kernel event notifier group"); + kernel_tracer_event_notifier_group_fd = -1; + } else { + enum event_notifier_error_accounting_status error_accounting_status; + enum lttng_error_code error_code_ret = + kernel_create_event_notifier_group_notification_fd( + &kernel_tracer_event_notifier_group_notification_fd); + + if (error_code_ret != LTTNG_OK) { + goto error_modules; + } + + error_accounting_status = event_notifier_error_accounting_register_kernel( + kernel_tracer_event_notifier_group_fd); + if (error_accounting_status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + ERR("Failed to initialize event notifier error accounting for kernel tracer"); + error_code_ret = LTTNG_ERR_EVENT_NOTIFIER_ERROR_ACCOUNTING; + goto error_modules; + } + + kernel_token_to_event_notifier_rule_ht = cds_lfht_new( + DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, + NULL); + if (!kernel_token_to_event_notifier_rule_ht) { + goto error_token_ht; + } + } + + DBG("Kernel tracer initialized: kernel tracer fd = %d, event notifier group fd = %d, event notifier group notification fd = %d", + kernel_tracer_fd, kernel_tracer_event_notifier_group_fd, + kernel_tracer_event_notifier_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: + modprobe_remove_lttng_control(); + ret = close(kernel_tracer_fd); + if (ret) { + PERROR("Failed to close kernel tracer file descriptor: fd = %d", + kernel_tracer_fd); + } + + kernel_tracer_fd = -1; + return LTTNG_ERR_KERN_VERSION; + + +error_token_ht: + ret = close(kernel_tracer_event_notifier_group_notification_fd); + if (ret) { + PERROR("Failed to close kernel tracer event notifier group notification file descriptor: fd = %d", + kernel_tracer_event_notifier_group_notification_fd); + } + + kernel_tracer_event_notifier_group_notification_fd = -1; + +error_modules: + ret = close(kernel_tracer_event_notifier_group_fd); + if (ret) { + PERROR("Failed to close kernel tracer event notifier group file descriptor: fd = %d", + kernel_tracer_event_notifier_group_fd); + } + + kernel_tracer_event_notifier_group_fd = -1; + + ret = close(kernel_tracer_fd); + if (ret) { + PERROR("Failed to close kernel tracer file descriptor: fd = %d", + kernel_tracer_fd); + } + + kernel_tracer_fd = -1; + +error_open: + modprobe_remove_lttng_control(); + +error: + WARN("No kernel tracer available"); + kernel_tracer_fd = -1; + if (!is_root) { + return LTTNG_ERR_NEED_ROOT_SESSIOND; + } else { + return LTTNG_ERR_KERN_NA; + } +} + +void cleanup_kernel_tracer(void) +{ + DBG2("Closing kernel event notifier group notification file descriptor"); + if (kernel_tracer_event_notifier_group_notification_fd >= 0) { + int ret = notification_thread_command_remove_tracer_event_source( + the_notification_thread_handle, + kernel_tracer_event_notifier_group_notification_fd); + if (ret != LTTNG_OK) { + ERR("Failed to remove kernel event notifier notification from notification thread"); + } + + ret = close(kernel_tracer_event_notifier_group_notification_fd); + if (ret) { + PERROR("Failed to close kernel event notifier group notification file descriptor: fd = %d", + kernel_tracer_event_notifier_group_notification_fd); + } + + kernel_tracer_event_notifier_group_notification_fd = -1; + } + + if (kernel_token_to_event_notifier_rule_ht) { + const int ret = cds_lfht_destroy( + kernel_token_to_event_notifier_rule_ht, NULL); + LTTNG_ASSERT(ret == 0); + } + + DBG2("Closing kernel event notifier group file descriptor"); + if (kernel_tracer_event_notifier_group_fd >= 0) { + const int ret = close(kernel_tracer_event_notifier_group_fd); + + if (ret) { + PERROR("Failed to close kernel event notifier group file descriptor: fd = %d", + kernel_tracer_event_notifier_group_fd); + } + + kernel_tracer_event_notifier_group_fd = -1; + } + + DBG2("Closing kernel fd"); + if (kernel_tracer_fd >= 0) { + const int ret = close(kernel_tracer_fd); + + if (ret) { + PERROR("Failed to close kernel tracer file descriptor: fd = %d", + kernel_tracer_fd); + } + + kernel_tracer_fd = -1; + } + + free(syscall_table); +} + +bool kernel_tracer_is_initialized(void) +{ + return kernel_tracer_fd >= 0; +} + +/* + * Clear a kernel session. + * + * Return LTTNG_OK on success or else an LTTng error code. + */ +enum lttng_error_code kernel_clear_session(struct ltt_session *session) +{ + int ret; + enum lttng_error_code status = LTTNG_OK; + struct consumer_socket *socket; + struct lttng_ht_iter iter; + struct ltt_kernel_session *ksess = session->kernel_session; + + LTTNG_ASSERT(ksess); + LTTNG_ASSERT(ksess->consumer); + + DBG("Clear kernel session %s (session %" PRIu64 ")", + session->name, session->id); + + rcu_read_lock(); + + if (ksess->active) { + ERR("Expecting inactive session %s (%" PRIu64 ")", session->name, session->id); + status = LTTNG_ERR_FATAL; + goto end; + } + + /* + * Note that this loop will end after one iteration given that there is + * only one kernel consumer. + */ + cds_lfht_for_each_entry(ksess->consumer->socks->ht, &iter.iter, + socket, node.node) { + struct ltt_kernel_channel *chan; + + /* For each channel, ask the consumer to clear it. */ + cds_list_for_each_entry(chan, &ksess->channel_list.head, list) { + DBG("Clear kernel channel %" PRIu64 ", session %s", + chan->key, session->name); + ret = consumer_clear_channel(socket, chan->key); + if (ret < 0) { + goto error; + } + } + + if (!ksess->metadata) { + /* + * Nothing to do for the metadata. + * This is a snapshot session. + * The metadata is genererated on the fly. + */ + continue; + } + + /* + * Clear the metadata channel. + * Metadata channel is not cleared per se but we still need to + * perform a rotation operation on it behind the scene. + */ + ret = consumer_clear_channel(socket, ksess->metadata->key); + if (ret < 0) { + goto error; + } + } + + goto end; +error: + switch (-ret) { + case LTTCOMM_CONSUMERD_RELAYD_CLEAR_DISALLOWED: + status = LTTNG_ERR_CLEAR_RELAY_DISALLOWED; + break; + default: + status = LTTNG_ERR_CLEAR_FAIL_CONSUMER; + break; + } +end: + rcu_read_unlock(); + return status; +} + +enum lttng_error_code kernel_create_event_notifier_group_notification_fd( + int *event_notifier_group_notification_fd) +{ + int local_fd = -1, ret; + enum lttng_error_code error_code_ret; + + LTTNG_ASSERT(event_notifier_group_notification_fd); + + ret = kernctl_create_event_notifier_group_notification_fd( + kernel_tracer_event_notifier_group_fd); + if (ret < 0) { + PERROR("Failed to create kernel event notifier group notification file descriptor"); + error_code_ret = LTTNG_ERR_EVENT_NOTIFIER_GROUP_NOTIFICATION_FD; + goto error; + } + + local_fd = ret; + + /* Prevent fd duplication after execlp(). */ + ret = fcntl(local_fd, F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("Failed to set FD_CLOEXEC on kernel event notifier group notification file descriptor: fd = %d", + local_fd); + error_code_ret = LTTNG_ERR_EVENT_NOTIFIER_GROUP_NOTIFICATION_FD; + goto error; + } + + DBG("Created kernel notifier group notification file descriptor: fd = %d", + local_fd); + error_code_ret = LTTNG_OK; + *event_notifier_group_notification_fd = local_fd; + local_fd = -1; + +error: + if (local_fd >= 0) { + ret = close(local_fd); + if (ret) { + PERROR("Failed to close kernel event notifier group notification file descriptor: fd = %d", + local_fd); + } + } + + return error_code_ret; +} + +enum lttng_error_code kernel_destroy_event_notifier_group_notification_fd( + int event_notifier_group_notification_fd) +{ + enum lttng_error_code ret_code = LTTNG_OK; + + DBG("Closing event notifier group notification file descriptor: fd = %d", + event_notifier_group_notification_fd); + if (event_notifier_group_notification_fd >= 0) { + const int ret = close(event_notifier_group_notification_fd); + if (ret) { + PERROR("Failed to close event notifier group notification file descriptor: fd = %d", + event_notifier_group_notification_fd); + } + } + + return ret_code; +} + +static +unsigned long hash_trigger(const struct lttng_trigger *trigger) +{ + const struct lttng_condition *condition = + lttng_trigger_get_const_condition(trigger); + + return lttng_condition_hash(condition); +} + +static +int match_trigger(struct cds_lfht_node *node, const void *key) +{ + const struct ltt_kernel_event_notifier_rule *event_notifier_rule; + const struct lttng_trigger *trigger = (lttng_trigger *) key; + + event_notifier_rule = caa_container_of(node, + const struct ltt_kernel_event_notifier_rule, ht_node); + + return lttng_trigger_is_equal(trigger, event_notifier_rule->trigger); +} + +static enum lttng_error_code kernel_create_event_notifier_rule( + struct lttng_trigger *trigger, + const struct lttng_credentials *creds, uint64_t token) +{ + int err, fd, ret = 0; + enum lttng_error_code error_code_ret; + enum lttng_condition_status condition_status; + enum lttng_condition_type condition_type; + enum lttng_event_rule_type event_rule_type; + struct ltt_kernel_event_notifier_rule *event_notifier_rule; + struct lttng_kernel_abi_event_notifier kernel_event_notifier = {}; + unsigned int capture_bytecode_count = 0, i; + const struct lttng_condition *condition = NULL; + const struct lttng_event_rule *event_rule = NULL; + enum lttng_condition_status cond_status; + + LTTNG_ASSERT(trigger); + + condition = lttng_trigger_get_const_condition(trigger); + LTTNG_ASSERT(condition); + + condition_type = lttng_condition_get_type(condition); + LTTNG_ASSERT(condition_type == LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); + + /* Does not acquire a reference. */ + condition_status = lttng_condition_event_rule_matches_get_rule( + condition, &event_rule); + LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); + LTTNG_ASSERT(event_rule); + + event_rule_type = lttng_event_rule_get_type(event_rule); + LTTNG_ASSERT(event_rule_type != LTTNG_EVENT_RULE_TYPE_UNKNOWN); + + error_code_ret = trace_kernel_create_event_notifier_rule(trigger, token, + lttng_condition_event_rule_matches_get_error_counter_index( + condition), + &event_notifier_rule); + if (error_code_ret != LTTNG_OK) { + goto error; + } + + error_code_ret = trace_kernel_init_event_notifier_from_event_rule( + event_rule, &kernel_event_notifier); + if (error_code_ret != LTTNG_OK) { + goto free_event; + } + + kernel_event_notifier.event.token = event_notifier_rule->token; + kernel_event_notifier.error_counter_idx = + lttng_condition_event_rule_matches_get_error_counter_index( + condition); + + fd = kernctl_create_event_notifier( + kernel_tracer_event_notifier_group_fd, + &kernel_event_notifier); + if (fd < 0) { + switch (-fd) { + case EEXIST: + error_code_ret = LTTNG_ERR_KERN_EVENT_EXIST; + break; + case ENOSYS: + WARN("Failed to create kernel event notifier: not notifier type not implemented"); + error_code_ret = LTTNG_ERR_KERN_EVENT_ENOSYS; + break; + case ENOENT: + WARN("Failed to create kernel event notifier: not found: name = '%s'", + kernel_event_notifier.event.name); + error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; + break; + default: + PERROR("Failed to create kernel event notifier: error code = %d, name = '%s'", + fd, kernel_event_notifier.event.name); + error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; + } + goto free_event; + } + + event_notifier_rule->fd = fd; + /* Prevent fd duplication after execlp(). */ + err = fcntl(event_notifier_rule->fd, F_SETFD, FD_CLOEXEC); + if (err < 0) { + PERROR("Failed to set FD_CLOEXEC on kernel event notifier file descriptor: fd = %d", + fd); + error_code_ret = LTTNG_ERR_FATAL; + goto set_cloexec_error; + } + + if (event_notifier_rule->filter) { + err = kernctl_filter(event_notifier_rule->fd, event_notifier_rule->filter); + if (err < 0) { + switch (-err) { + case ENOMEM: + error_code_ret = LTTNG_ERR_FILTER_NOMEM; + break; + default: + error_code_ret = LTTNG_ERR_FILTER_INVAL; + break; + } + goto filter_error; + } + } + + if (lttng_event_rule_get_type(event_rule) == + LTTNG_EVENT_RULE_TYPE_KERNEL_UPROBE) { + ret = userspace_probe_event_rule_add_callsites( + event_rule, creds, event_notifier_rule->fd); + if (ret) { + error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; + goto add_callsite_error; + } + } + + /* Set the capture bytecode if any. */ + cond_status = lttng_condition_event_rule_matches_get_capture_descriptor_count( + condition, &capture_bytecode_count); + LTTNG_ASSERT(cond_status == LTTNG_CONDITION_STATUS_OK); + + for (i = 0; i < capture_bytecode_count; i++) { + const struct lttng_bytecode *capture_bytecode = + lttng_condition_event_rule_matches_get_capture_bytecode_at_index( + condition, i); + + if (capture_bytecode == NULL) { + ERR("Unexpected NULL capture bytecode on condition"); + error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; + goto capture_error; + } + + ret = kernctl_capture(event_notifier_rule->fd, capture_bytecode); + if (ret < 0) { + ERR("Failed to set capture bytecode on event notifier rule fd: fd = %d", + event_notifier_rule->fd); + error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; + goto capture_error; + } + } + + err = kernctl_enable(event_notifier_rule->fd); + if (err < 0) { + switch (-err) { + case EEXIST: + error_code_ret = LTTNG_ERR_KERN_EVENT_EXIST; + break; + default: + PERROR("enable kernel event notifier"); + error_code_ret = LTTNG_ERR_KERN_ENABLE_FAIL; + break; + } + goto enable_error; + } + + /* Add trigger to kernel token mapping in the hash table. */ + rcu_read_lock(); + cds_lfht_add(kernel_token_to_event_notifier_rule_ht, hash_trigger(trigger), + &event_notifier_rule->ht_node); + rcu_read_unlock(); + + DBG("Created kernel event notifier: name = '%s', fd = %d", + kernel_event_notifier.event.name, + event_notifier_rule->fd); + + return LTTNG_OK; + +capture_error: +add_callsite_error: +enable_error: +set_cloexec_error: +filter_error: + { + const int close_ret = close(event_notifier_rule->fd); + + if (close_ret) { + PERROR("Failed to close kernel event notifier file descriptor: fd = %d", + event_notifier_rule->fd); + } + } +free_event: + free(event_notifier_rule); +error: + return error_code_ret; +} + +enum lttng_error_code kernel_register_event_notifier( + struct lttng_trigger *trigger, + const struct lttng_credentials *cmd_creds) +{ + enum lttng_error_code ret; + enum lttng_condition_status status; + enum lttng_domain_type domain_type; + const struct lttng_event_rule *event_rule; + const struct lttng_condition *const condition = + lttng_trigger_get_const_condition(trigger); + const uint64_t token = lttng_trigger_get_tracer_token(trigger); + + LTTNG_ASSERT(condition); + + /* Does not acquire a reference to the event rule. */ + status = lttng_condition_event_rule_matches_get_rule( + condition, &event_rule); + LTTNG_ASSERT(status == LTTNG_CONDITION_STATUS_OK); + + domain_type = lttng_event_rule_get_domain_type(event_rule); + LTTNG_ASSERT(domain_type == LTTNG_DOMAIN_KERNEL); + + ret = kernel_create_event_notifier_rule(trigger, cmd_creds, token); + if (ret != LTTNG_OK) { + ERR("Failed to create kernel event notifier rule"); + } + + return ret; +} + +enum lttng_error_code kernel_unregister_event_notifier( + const struct lttng_trigger *trigger) +{ + struct ltt_kernel_event_notifier_rule *token_event_rule_element; + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + enum lttng_error_code error_code_ret; + int ret; + + rcu_read_lock(); + + cds_lfht_lookup(kernel_token_to_event_notifier_rule_ht, + hash_trigger(trigger), match_trigger, trigger, &iter); + + node = cds_lfht_iter_get_node(&iter); + if (!node) { + error_code_ret = LTTNG_ERR_TRIGGER_NOT_FOUND; + goto error; + } + + token_event_rule_element = caa_container_of(node, + struct ltt_kernel_event_notifier_rule, ht_node); + + ret = kernel_disable_event_notifier_rule(token_event_rule_element); + if (ret) { + error_code_ret = LTTNG_ERR_FATAL; + goto error; + } + + trace_kernel_destroy_event_notifier_rule(token_event_rule_element); + error_code_ret = LTTNG_OK; + +error: + rcu_read_unlock(); + + return error_code_ret; +} + +int kernel_get_notification_fd(void) +{ + return kernel_tracer_event_notifier_group_notification_fd; +} diff --git a/src/bin/lttng-sessiond/lttng-syscall.c b/src/bin/lttng-sessiond/lttng-syscall.c deleted file mode 100644 index 06021bcf5..000000000 --- a/src/bin/lttng-sessiond/lttng-syscall.c +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (C) 2014 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include - -#include -#include - -#include "lttng-sessiond.h" -#include "kernel.h" -#include "lttng-syscall.h" -#include "utils.h" - -/* Global syscall table. */ -struct syscall *syscall_table; - -/* Number of entry in the syscall table. */ -static size_t syscall_table_nb_entry; - -/* - * Populate the system call table using the kernel tracer. - * - * Return 0 on success and the syscall table is allocated. On error, a negative - * value is returned. - */ -int syscall_init_table(int tracer_fd) -{ - int ret, fd, err; - size_t nbmem; - FILE *fp; - /* Syscall data from the kernel. */ - size_t index = 0; - bool at_least_one_syscall = false; - uint32_t bitness; - char name[SYSCALL_NAME_LEN]; - -#if (SYSCALL_NAME_LEN == 255) -#define SYSCALL_NAME_LEN_SCANF_IS_A_BROKEN_API "254" -#endif - - DBG3("Syscall init system call table"); - - fd = kernctl_syscall_list(tracer_fd); - if (fd < 0) { - ret = fd; - PERROR("kernelctl syscall list"); - goto error_ioctl; - } - - fp = fdopen(fd, "r"); - if (!fp) { - ret = -errno; - PERROR("syscall list fdopen"); - goto error_fp; - } - - nbmem = SYSCALL_TABLE_INIT_SIZE; - syscall_table = zmalloc(sizeof(struct syscall) * nbmem); - if (!syscall_table) { - ret = -errno; - PERROR("syscall list zmalloc"); - goto error; - } - - while (fscanf(fp, - "syscall { index = %zu; \ - name = %" SYSCALL_NAME_LEN_SCANF_IS_A_BROKEN_API "[^;]; \ - bitness = %u; };\n", - &index, name, &bitness) == 3) { - at_least_one_syscall = true; - if (index >= nbmem) { - struct syscall *new_list; - size_t new_nbmem; - - /* Double memory size. */ - new_nbmem = max(index + 1, nbmem << 1); - if (new_nbmem > (SIZE_MAX / sizeof(*new_list))) { - /* Overflow, stop everything, something went really wrong. */ - ERR("Syscall listing memory size overflow. Stopping"); - free(syscall_table); - syscall_table = NULL; - ret = -EINVAL; - goto error; - } - - DBG("Reallocating syscall table from %zu to %zu entries", nbmem, - new_nbmem); - new_list = realloc(syscall_table, new_nbmem * sizeof(*new_list)); - if (!new_list) { - ret = -errno; - PERROR("syscall list realloc"); - goto error; - } - - /* Zero out the new memory. */ - memset(new_list + nbmem, 0, - (new_nbmem - nbmem) * sizeof(*new_list)); - nbmem = new_nbmem; - syscall_table = new_list; - } - syscall_table[index].index = index; - syscall_table[index].bitness = bitness; - if (lttng_strncpy(syscall_table[index].name, name, - sizeof(syscall_table[index].name))) { - ret = -EINVAL; - free(syscall_table); - syscall_table = NULL; - goto error; - } - /* - DBG("Syscall name '%s' at index %" PRIu32 " of bitness %u", - syscall_table[index].name, - syscall_table[index].index, - syscall_table[index].bitness); - */ - } - - /* Index starts at 0. */ - if (at_least_one_syscall) { - syscall_table_nb_entry = index + 1; - } - - ret = 0; - -error: - err = fclose(fp); - if (err) { - PERROR("syscall list fclose"); - } - return ret; - -error_fp: - err = close(fd); - if (err) { - PERROR("syscall list close"); - } - -error_ioctl: - return ret; -} - -/* - * Helper function for the list syscalls command that empty the temporary - * syscall hashtable used to track duplicate between 32 and 64 bit arch. - * - * This empty the hash table and destroys it after. After this, the pointer is - * unsuable. RCU read side lock MUST be acquired before calling this. - */ -static void destroy_syscall_ht(struct lttng_ht *ht) -{ - struct lttng_ht_iter iter; - struct syscall *ksyscall; - - DBG3("Destroying syscall hash table."); - - if (!ht) { - return; - } - - cds_lfht_for_each_entry(ht->ht, &iter.iter, ksyscall, node.node) { - int ret; - - ret = lttng_ht_del(ht, &iter); - LTTNG_ASSERT(!ret); - free(ksyscall); - } - ht_cleanup_push(ht); -} - -/* - * Allocate the given hashtable pointer. - * - * Return 0 on success else a negative LTTNG error value. - */ -static int init_syscall_ht(struct lttng_ht **ht) -{ - int ret; - - *ht = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); - if (!*ht) { - ret = -LTTNG_ERR_NOMEM; - } else { - ret = 0; - } - - return ret; -} - -/* - * Lookup a syscall in the given hash table by name. - * - * Return syscall object if found or else NULL. - */ -static struct syscall *lookup_syscall(struct lttng_ht *ht, const char *name) -{ - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - struct syscall *ksyscall = NULL; - - LTTNG_ASSERT(ht); - LTTNG_ASSERT(name); - - lttng_ht_lookup(ht, (void *) name, &iter); - node = lttng_ht_iter_get_node_str(&iter); - if (node) { - ksyscall = caa_container_of(node, struct syscall, node); - } - - return ksyscall; -} - -/* - * Using the given syscall object in the events array with the bitness of the - * syscall at index in the syscall table. - */ -static void update_event_syscall_bitness(struct lttng_event *events, - unsigned int index, unsigned int syscall_index) -{ - LTTNG_ASSERT(events); - - if (syscall_table[index].bitness == 32) { - events[syscall_index].flags |= LTTNG_EVENT_FLAG_SYSCALL_32; - } else { - events[syscall_index].flags |= LTTNG_EVENT_FLAG_SYSCALL_64; - } -} - -/* - * Allocate and initialize syscall object and add it to the given hashtable. - * - * Return 0 on success else -LTTNG_ERR_NOMEM. - */ -static int add_syscall_to_ht(struct lttng_ht *ht, unsigned int index, - unsigned int syscall_index) -{ - int ret; - struct syscall *ksyscall; - - LTTNG_ASSERT(ht); - - ksyscall = zmalloc(sizeof(*ksyscall)); - if (!ksyscall) { - ret = -LTTNG_ERR_NOMEM; - goto error; - } - - strncpy(ksyscall->name, syscall_table[index].name, - sizeof(ksyscall->name)); - ksyscall->bitness = syscall_table[index].bitness; - ksyscall->index = syscall_index; - lttng_ht_node_init_str(&ksyscall->node, ksyscall->name); - lttng_ht_add_unique_str(ht, &ksyscall->node); - ret = 0; - -error: - return ret; -} - -/* - * List syscalls present in the kernel syscall global array, allocate and - * populate the events structure with them. Skip the empty syscall name. - * - * Return the number of entries in the array else a negative value. - */ -ssize_t syscall_table_list(struct lttng_event **_events) -{ - int i, index = 0; - ssize_t ret; - struct lttng_event *events; - /* Hash table used to filter duplicate out. */ - struct lttng_ht *syscalls_ht = NULL; - - LTTNG_ASSERT(_events); - - DBG("Syscall table listing."); - - rcu_read_lock(); - - /* - * Allocate at least the number of total syscall we have even if some of - * them might not be valid. The count below will make sure to return the - * right size of the events array. - */ - events = zmalloc(syscall_table_nb_entry * sizeof(*events)); - if (!events) { - PERROR("syscall table list zmalloc"); - ret = -LTTNG_ERR_NOMEM; - goto error; - } - - ret = init_syscall_ht(&syscalls_ht); - if (ret < 0) { - goto error; - } - - for (i = 0; i < syscall_table_nb_entry; i++) { - struct syscall *ksyscall; - - /* Skip empty syscalls. */ - if (*syscall_table[i].name == '\0') { - continue; - } - - ksyscall = lookup_syscall(syscalls_ht, syscall_table[i].name); - if (ksyscall) { - update_event_syscall_bitness(events, i, ksyscall->index); - continue; - } - - ret = add_syscall_to_ht(syscalls_ht, i, index); - if (ret < 0) { - goto error; - } - - /* Copy the event information in the event's array. */ - strncpy(events[index].name, syscall_table[i].name, - sizeof(events[index].name)); - update_event_syscall_bitness(events, i, index); - events[index].type = LTTNG_EVENT_SYSCALL; - /* This makes the command line not print the enabled/disabled field. */ - events[index].enabled = -1; - index++; - } - - destroy_syscall_ht(syscalls_ht); - *_events = events; - rcu_read_unlock(); - return index; - -error: - destroy_syscall_ht(syscalls_ht); - free(events); - rcu_read_unlock(); - return ret; -} diff --git a/src/bin/lttng-sessiond/lttng-syscall.cpp b/src/bin/lttng-sessiond/lttng-syscall.cpp new file mode 100644 index 000000000..f14563457 --- /dev/null +++ b/src/bin/lttng-sessiond/lttng-syscall.cpp @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2014 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include + +#include +#include + +#include "lttng-sessiond.h" +#include "kernel.h" +#include "lttng-syscall.h" +#include "utils.h" + +/* Global syscall table. */ +struct syscall *syscall_table; + +/* Number of entry in the syscall table. */ +static size_t syscall_table_nb_entry; + +/* + * Populate the system call table using the kernel tracer. + * + * Return 0 on success and the syscall table is allocated. On error, a negative + * value is returned. + */ +int syscall_init_table(int tracer_fd) +{ + int ret, fd, err; + size_t nbmem; + FILE *fp; + /* Syscall data from the kernel. */ + size_t index = 0; + bool at_least_one_syscall = false; + uint32_t bitness; + char name[SYSCALL_NAME_LEN]; + +#if (SYSCALL_NAME_LEN == 255) +#define SYSCALL_NAME_LEN_SCANF_IS_A_BROKEN_API "254" +#endif + + DBG3("Syscall init system call table"); + + fd = kernctl_syscall_list(tracer_fd); + if (fd < 0) { + ret = fd; + PERROR("kernelctl syscall list"); + goto error_ioctl; + } + + fp = fdopen(fd, "r"); + if (!fp) { + ret = -errno; + PERROR("syscall list fdopen"); + goto error_fp; + } + + nbmem = SYSCALL_TABLE_INIT_SIZE; + syscall_table = (struct syscall *) zmalloc(sizeof(struct syscall) * nbmem); + if (!syscall_table) { + ret = -errno; + PERROR("syscall list zmalloc"); + goto error; + } + + while (fscanf(fp, + "syscall { index = %zu; \ + name = %" SYSCALL_NAME_LEN_SCANF_IS_A_BROKEN_API "[^;]; \ + bitness = %u; };\n", + &index, name, &bitness) == 3) { + at_least_one_syscall = true; + if (index >= nbmem) { + struct syscall *new_list; + size_t new_nbmem; + + /* Double memory size. */ + new_nbmem = std::max(index + 1, nbmem << 1); + if (new_nbmem > (SIZE_MAX / sizeof(*new_list))) { + /* Overflow, stop everything, something went really wrong. */ + ERR("Syscall listing memory size overflow. Stopping"); + free(syscall_table); + syscall_table = NULL; + ret = -EINVAL; + goto error; + } + + DBG("Reallocating syscall table from %zu to %zu entries", nbmem, + new_nbmem); + new_list = (struct syscall *) realloc(syscall_table, new_nbmem * sizeof(*new_list)); + if (!new_list) { + ret = -errno; + PERROR("syscall list realloc"); + goto error; + } + + /* Zero out the new memory. */ + memset(new_list + nbmem, 0, + (new_nbmem - nbmem) * sizeof(*new_list)); + nbmem = new_nbmem; + syscall_table = new_list; + } + syscall_table[index].index = index; + syscall_table[index].bitness = bitness; + if (lttng_strncpy(syscall_table[index].name, name, + sizeof(syscall_table[index].name))) { + ret = -EINVAL; + free(syscall_table); + syscall_table = NULL; + goto error; + } + /* + DBG("Syscall name '%s' at index %" PRIu32 " of bitness %u", + syscall_table[index].name, + syscall_table[index].index, + syscall_table[index].bitness); + */ + } + + /* Index starts at 0. */ + if (at_least_one_syscall) { + syscall_table_nb_entry = index + 1; + } + + ret = 0; + +error: + err = fclose(fp); + if (err) { + PERROR("syscall list fclose"); + } + return ret; + +error_fp: + err = close(fd); + if (err) { + PERROR("syscall list close"); + } + +error_ioctl: + return ret; +} + +/* + * Helper function for the list syscalls command that empty the temporary + * syscall hashtable used to track duplicate between 32 and 64 bit arch. + * + * This empty the hash table and destroys it after. After this, the pointer is + * unsuable. RCU read side lock MUST be acquired before calling this. + */ +static void destroy_syscall_ht(struct lttng_ht *ht) +{ + struct lttng_ht_iter iter; + struct syscall *ksyscall; + + DBG3("Destroying syscall hash table."); + + if (!ht) { + return; + } + + cds_lfht_for_each_entry(ht->ht, &iter.iter, ksyscall, node.node) { + int ret; + + ret = lttng_ht_del(ht, &iter); + LTTNG_ASSERT(!ret); + free(ksyscall); + } + ht_cleanup_push(ht); +} + +/* + * Allocate the given hashtable pointer. + * + * Return 0 on success else a negative LTTNG error value. + */ +static int init_syscall_ht(struct lttng_ht **ht) +{ + int ret; + + *ht = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); + if (!*ht) { + ret = -LTTNG_ERR_NOMEM; + } else { + ret = 0; + } + + return ret; +} + +/* + * Lookup a syscall in the given hash table by name. + * + * Return syscall object if found or else NULL. + */ +static struct syscall *lookup_syscall(struct lttng_ht *ht, const char *name) +{ + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + struct syscall *ksyscall = NULL; + + LTTNG_ASSERT(ht); + LTTNG_ASSERT(name); + + lttng_ht_lookup(ht, (void *) name, &iter); + node = lttng_ht_iter_get_node_str(&iter); + if (node) { + ksyscall = caa_container_of(node, struct syscall, node); + } + + return ksyscall; +} + +/* + * Using the given syscall object in the events array with the bitness of the + * syscall at index in the syscall table. + */ +static void update_event_syscall_bitness(struct lttng_event *events, + unsigned int index, unsigned int syscall_index) +{ + LTTNG_ASSERT(events); + + if (syscall_table[index].bitness == 32) { + events[syscall_index].flags = (lttng_event_flag) (events[syscall_index].flags | LTTNG_EVENT_FLAG_SYSCALL_32); + } else { + events[syscall_index].flags = (lttng_event_flag) (events[syscall_index].flags | LTTNG_EVENT_FLAG_SYSCALL_64); + } +} + +/* + * Allocate and initialize syscall object and add it to the given hashtable. + * + * Return 0 on success else -LTTNG_ERR_NOMEM. + */ +static int add_syscall_to_ht(struct lttng_ht *ht, unsigned int index, + unsigned int syscall_index) +{ + int ret; + struct syscall *ksyscall; + + LTTNG_ASSERT(ht); + + ksyscall = (struct syscall *) zmalloc(sizeof(*ksyscall)); + if (!ksyscall) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + + strncpy(ksyscall->name, syscall_table[index].name, + sizeof(ksyscall->name)); + ksyscall->bitness = syscall_table[index].bitness; + ksyscall->index = syscall_index; + lttng_ht_node_init_str(&ksyscall->node, ksyscall->name); + lttng_ht_add_unique_str(ht, &ksyscall->node); + ret = 0; + +error: + return ret; +} + +/* + * List syscalls present in the kernel syscall global array, allocate and + * populate the events structure with them. Skip the empty syscall name. + * + * Return the number of entries in the array else a negative value. + */ +ssize_t syscall_table_list(struct lttng_event **_events) +{ + int i, index = 0; + ssize_t ret; + struct lttng_event *events; + /* Hash table used to filter duplicate out. */ + struct lttng_ht *syscalls_ht = NULL; + + LTTNG_ASSERT(_events); + + DBG("Syscall table listing."); + + rcu_read_lock(); + + /* + * Allocate at least the number of total syscall we have even if some of + * them might not be valid. The count below will make sure to return the + * right size of the events array. + */ + events = (lttng_event *) zmalloc(syscall_table_nb_entry * sizeof(*events)); + if (!events) { + PERROR("syscall table list zmalloc"); + ret = -LTTNG_ERR_NOMEM; + goto error; + } + + ret = init_syscall_ht(&syscalls_ht); + if (ret < 0) { + goto error; + } + + for (i = 0; i < syscall_table_nb_entry; i++) { + struct syscall *ksyscall; + + /* Skip empty syscalls. */ + if (*syscall_table[i].name == '\0') { + continue; + } + + ksyscall = lookup_syscall(syscalls_ht, syscall_table[i].name); + if (ksyscall) { + update_event_syscall_bitness(events, i, ksyscall->index); + continue; + } + + ret = add_syscall_to_ht(syscalls_ht, i, index); + if (ret < 0) { + goto error; + } + + /* Copy the event information in the event's array. */ + strncpy(events[index].name, syscall_table[i].name, + sizeof(events[index].name)); + update_event_syscall_bitness(events, i, index); + events[index].type = LTTNG_EVENT_SYSCALL; + /* This makes the command line not print the enabled/disabled field. */ + events[index].enabled = -1; + index++; + } + + destroy_syscall_ht(syscalls_ht); + *_events = events; + rcu_read_unlock(); + return index; + +error: + destroy_syscall_ht(syscalls_ht); + free(events); + rcu_read_unlock(); + return ret; +} diff --git a/src/bin/lttng-sessiond/main.c b/src/bin/lttng-sessiond/main.c deleted file mode 100644 index 4effb3ce9..000000000 --- a/src/bin/lttng-sessiond/main.c +++ /dev/null @@ -1,2053 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "lttng-sessiond.h" -#include "buffer-registry.h" -#include "channel.h" -#include "cmd.h" -#include "consumer.h" -#include "context.h" -#include "event.h" -#include "event-notifier-error-accounting.h" -#include "kernel.h" -#include "kernel-consumer.h" -#include "lttng-ust-ctl.h" -#include "ust-consumer.h" -#include "utils.h" -#include "fd-limit.h" -#include "health-sessiond.h" -#include "testpoint.h" -#include "notify-apps.h" -#include "agent-thread.h" -#include "save.h" -#include "notification-thread.h" -#include "notification-thread-commands.h" -#include "rotation-thread.h" -#include "agent.h" -#include "ht-cleanup.h" -#include "sessiond-config.h" -#include "timer.h" -#include "thread.h" -#include "client.h" -#include "dispatch.h" -#include "register.h" -#include "manage-apps.h" -#include "manage-kernel.h" -#include "modprobe.h" -#include "ust-sigbus.h" - -static const char *help_msg = -#ifdef LTTNG_EMBED_HELP -#include -#else -NULL -#endif -; - -#define EVENT_NOTIFIER_ERROR_COUNTER_NUMBER_OF_BUCKET_MAX 65535 -#define EVENT_NOTIFIER_ERROR_BUFFER_SIZE_BASE_OPTION_STR \ - "event-notifier-error-buffer-size" -#define EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR \ - EVENT_NOTIFIER_ERROR_BUFFER_SIZE_BASE_OPTION_STR "-kernel" -#define EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR \ - EVENT_NOTIFIER_ERROR_BUFFER_SIZE_BASE_OPTION_STR "-userspace" - - -const char *progname; -static int lockfile_fd = -1; -static int opt_print_version; - -/* Set to 1 when a SIGUSR1 signal is received. */ -static int recv_child_signal; - -/* Command line options */ -static const struct option long_options[] = { - { "client-sock", required_argument, 0, 'c' }, - { "apps-sock", required_argument, 0, 'a' }, - { "kconsumerd-cmd-sock", required_argument, 0, '\0' }, - { "kconsumerd-err-sock", required_argument, 0, '\0' }, - { "ustconsumerd32-cmd-sock", required_argument, 0, '\0' }, - { "ustconsumerd32-err-sock", required_argument, 0, '\0' }, - { "ustconsumerd64-cmd-sock", required_argument, 0, '\0' }, - { "ustconsumerd64-err-sock", required_argument, 0, '\0' }, - { "consumerd32-path", required_argument, 0, '\0' }, - { "consumerd32-libdir", required_argument, 0, '\0' }, - { "consumerd64-path", required_argument, 0, '\0' }, - { "consumerd64-libdir", required_argument, 0, '\0' }, - { "daemonize", no_argument, 0, 'd' }, - { "background", no_argument, 0, 'b' }, - { "sig-parent", no_argument, 0, 'S' }, - { "help", no_argument, 0, 'h' }, - { "group", required_argument, 0, 'g' }, - { "version", no_argument, 0, 'V' }, - { "quiet", no_argument, 0, 'q' }, - { "verbose", no_argument, 0, 'v' }, - { "verbose-consumer", no_argument, 0, '\0' }, - { "no-kernel", no_argument, 0, '\0' }, - { "pidfile", required_argument, 0, 'p' }, - { "agent-tcp-port", required_argument, 0, '\0' }, - { "config", required_argument, 0, 'f' }, - { "load", required_argument, 0, 'l' }, - { "kmod-probes", required_argument, 0, '\0' }, - { "extra-kmod-probes", required_argument, 0, '\0' }, - { EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR, required_argument, 0, '\0' }, - { EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR, required_argument, 0, '\0' }, - { NULL, 0, 0, 0 } -}; - -/* Command line options to ignore from configuration file */ -static const char *config_ignore_options[] = { "help", "version", "config" }; - -/* - * This pipe is used to inform the thread managing application communication - * that a command is queued and ready to be processed. - */ -static int apps_cmd_pipe[2] = { -1, -1 }; -static int apps_cmd_notify_pipe[2] = { -1, -1 }; - -/* - * UST registration command queue. This queue is tied with a futex and uses a N - * wakers / 1 waiter implemented and detailed in futex.c/.h - * - * The thread_registration_apps and thread_dispatch_ust_registration uses this - * queue along with the wait/wake scheme. The thread_manage_apps receives down - * the line new application socket and monitors it for any I/O error or clean - * close that triggers an unregistration of the application. - */ -static struct ust_cmd_queue ust_cmd_queue; - -/* - * Section name to look for in the daemon configuration file. - */ -static const char * const config_section_name = "sessiond"; - -/* Am I root or not. Set to 1 if the daemon is running as root */ -static int is_root; - -/* - * Stop all threads by closing the thread quit pipe. - */ -static void stop_threads(void) -{ - int ret; - - /* Stopping all threads */ - DBG("Terminating all threads"); - ret = sessiond_notify_quit_pipe(); - if (ret < 0) { - ERR("write error on thread quit pipe"); - } -} - -/* - * Close every consumer sockets. - */ -static void close_consumer_sockets(void) -{ - int ret; - - if (the_kconsumer_data.err_sock >= 0) { - ret = close(the_kconsumer_data.err_sock); - if (ret < 0) { - PERROR("kernel consumer err_sock close"); - } - } - if (the_ustconsumer32_data.err_sock >= 0) { - ret = close(the_ustconsumer32_data.err_sock); - if (ret < 0) { - PERROR("UST consumerd32 err_sock close"); - } - } - if (the_ustconsumer64_data.err_sock >= 0) { - ret = close(the_ustconsumer64_data.err_sock); - if (ret < 0) { - PERROR("UST consumerd64 err_sock close"); - } - } - if (the_kconsumer_data.cmd_sock >= 0) { - ret = close(the_kconsumer_data.cmd_sock); - if (ret < 0) { - PERROR("kernel consumer cmd_sock close"); - } - } - if (the_ustconsumer32_data.cmd_sock >= 0) { - ret = close(the_ustconsumer32_data.cmd_sock); - if (ret < 0) { - PERROR("UST consumerd32 cmd_sock close"); - } - } - if (the_ustconsumer64_data.cmd_sock >= 0) { - ret = close(the_ustconsumer64_data.cmd_sock); - if (ret < 0) { - PERROR("UST consumerd64 cmd_sock close"); - } - } - if (the_kconsumer_data.channel_monitor_pipe >= 0) { - ret = close(the_kconsumer_data.channel_monitor_pipe); - if (ret < 0) { - PERROR("kernel consumer channel monitor pipe close"); - } - } - if (the_ustconsumer32_data.channel_monitor_pipe >= 0) { - ret = close(the_ustconsumer32_data.channel_monitor_pipe); - if (ret < 0) { - PERROR("UST consumerd32 channel monitor pipe close"); - } - } - if (the_ustconsumer64_data.channel_monitor_pipe >= 0) { - ret = close(the_ustconsumer64_data.channel_monitor_pipe); - if (ret < 0) { - PERROR("UST consumerd64 channel monitor pipe close"); - } - } -} - -/* - * Wait on consumer process termination. - * - * Need to be called with the consumer data lock held or from a context - * ensuring no concurrent access to data (e.g: cleanup). - */ -static void wait_consumer(struct consumer_data *consumer_data) -{ - pid_t ret; - int status; - - if (consumer_data->pid <= 0) { - return; - } - - DBG("Waiting for complete teardown of consumerd (PID: %d)", - consumer_data->pid); - ret = waitpid(consumer_data->pid, &status, 0); - if (ret == -1) { - PERROR("consumerd waitpid pid: %d", consumer_data->pid) - } else if (!WIFEXITED(status)) { - ERR("consumerd termination with error: %d", - WEXITSTATUS(ret)); - } - consumer_data->pid = 0; -} - -/* - * Cleanup the session daemon's data structures. - */ -static void sessiond_cleanup(void) -{ - int ret; - struct ltt_session_list *session_list = session_get_list(); - - DBG("Cleanup sessiond"); - - /* - * Close the thread quit pipe. It has already done its job, - * since we are now called. - */ - sessiond_close_quit_pipe(); - utils_close_pipe(apps_cmd_pipe); - utils_close_pipe(apps_cmd_notify_pipe); - utils_close_pipe(the_kernel_poll_pipe); - - ret = remove(the_config.pid_file_path.value); - if (ret < 0) { - PERROR("remove pidfile %s", the_config.pid_file_path.value); - } - - DBG("Removing sessiond and consumerd content of directory %s", - the_config.rundir.value); - - /* sessiond */ - DBG("Removing %s", the_config.pid_file_path.value); - (void) unlink(the_config.pid_file_path.value); - - DBG("Removing %s", the_config.agent_port_file_path.value); - (void) unlink(the_config.agent_port_file_path.value); - - /* kconsumerd */ - DBG("Removing %s", the_kconsumer_data.err_unix_sock_path); - (void) unlink(the_kconsumer_data.err_unix_sock_path); - - DBG("Removing directory %s", the_config.kconsumerd_path.value); - (void) rmdir(the_config.kconsumerd_path.value); - - /* ust consumerd 32 */ - DBG("Removing %s", the_config.consumerd32_err_unix_sock_path.value); - (void) unlink(the_config.consumerd32_err_unix_sock_path.value); - - DBG("Removing directory %s", the_config.consumerd32_path.value); - (void) rmdir(the_config.consumerd32_path.value); - - /* ust consumerd 64 */ - DBG("Removing %s", the_config.consumerd64_err_unix_sock_path.value); - (void) unlink(the_config.consumerd64_err_unix_sock_path.value); - - DBG("Removing directory %s", the_config.consumerd64_path.value); - (void) rmdir(the_config.consumerd64_path.value); - - pthread_mutex_destroy(&session_list->lock); - - DBG("Cleaning up all per-event notifier domain agents"); - agent_by_event_notifier_domain_ht_destroy(); - - DBG("Cleaning up all agent apps"); - agent_app_ht_clean(); - DBG("Closing all UST sockets"); - ust_app_clean_list(); - buffer_reg_destroy_registries(); - - close_consumer_sockets(); - - wait_consumer(&the_kconsumer_data); - wait_consumer(&the_ustconsumer64_data); - wait_consumer(&the_ustconsumer32_data); - - if (is_root && !the_config.no_kernel) { - cleanup_kernel_tracer(); - } - - /* - * We do NOT rmdir rundir because there are other processes - * using it, for instance lttng-relayd, which can start in - * parallel with this teardown. - */ -} - -/* - * Cleanup the daemon's option data structures. - */ -static void sessiond_cleanup_options(void) -{ - DBG("Cleaning up options"); - - sessiond_config_fini(&the_config); - - run_as_destroy_worker(); -} - -static int string_match(const char *str1, const char *str2) -{ - return (str1 && str2) && !strcmp(str1, str2); -} - -/* - * Take an option from the getopt output and set it in the right variable to be - * used later. - * - * Return 0 on success else a negative value. - */ -static int set_option(int opt, const char *arg, const char *optname) -{ - int ret = 0; - - if (string_match(optname, "client-sock") || opt == 'c') { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "-c, --client-sock"); - } else { - config_string_set(&the_config.client_unix_sock_path, - strdup(arg)); - if (!the_config.client_unix_sock_path.value) { - ret = -ENOMEM; - PERROR("strdup"); - } - } - } else if (string_match(optname, "apps-sock") || opt == 'a') { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "-a, --apps-sock"); - } else { - config_string_set(&the_config.apps_unix_sock_path, - strdup(arg)); - if (!the_config.apps_unix_sock_path.value) { - ret = -ENOMEM; - PERROR("strdup"); - } - } - } else if (string_match(optname, "daemonize") || opt == 'd') { - the_config.daemonize = true; - } else if (string_match(optname, "background") || opt == 'b') { - the_config.background = true; - } else if (string_match(optname, "group") || opt == 'g') { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "-g, --group"); - } else { - config_string_set(&the_config.tracing_group_name, - strdup(arg)); - if (!the_config.tracing_group_name.value) { - ret = -ENOMEM; - PERROR("strdup"); - } - } - } else if (string_match(optname, "help") || opt == 'h') { - ret = utils_show_help(8, "lttng-sessiond", help_msg); - if (ret) { - ERR("Cannot show --help for `lttng-sessiond`"); - perror("exec"); - } - exit(ret ? EXIT_FAILURE : EXIT_SUCCESS); - } else if (string_match(optname, "version") || opt == 'V') { - opt_print_version = 1; - } else if (string_match(optname, "sig-parent") || opt == 'S') { - the_config.sig_parent = true; - } else if (string_match(optname, "kconsumerd-err-sock")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--kconsumerd-err-sock"); - } else { - config_string_set( - &the_config.kconsumerd_err_unix_sock_path, - strdup(arg)); - if (!the_config.kconsumerd_err_unix_sock_path.value) { - ret = -ENOMEM; - PERROR("strdup"); - } - } - } else if (string_match(optname, "kconsumerd-cmd-sock")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--kconsumerd-cmd-sock"); - } else { - config_string_set( - &the_config.kconsumerd_cmd_unix_sock_path, - strdup(arg)); - if (!the_config.kconsumerd_cmd_unix_sock_path.value) { - ret = -ENOMEM; - PERROR("strdup"); - } - } - } else if (string_match(optname, "ustconsumerd64-err-sock")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--ustconsumerd64-err-sock"); - } else { - config_string_set( - &the_config.consumerd64_err_unix_sock_path, - strdup(arg)); - if (!the_config.consumerd64_err_unix_sock_path.value) { - ret = -ENOMEM; - PERROR("strdup"); - } - } - } else if (string_match(optname, "ustconsumerd64-cmd-sock")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--ustconsumerd64-cmd-sock"); - } else { - config_string_set( - &the_config.consumerd64_cmd_unix_sock_path, - strdup(arg)); - if (!the_config.consumerd64_cmd_unix_sock_path.value) { - ret = -ENOMEM; - PERROR("strdup"); - } - } - } else if (string_match(optname, "ustconsumerd32-err-sock")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--ustconsumerd32-err-sock"); - } else { - config_string_set( - &the_config.consumerd32_err_unix_sock_path, - strdup(arg)); - if (!the_config.consumerd32_err_unix_sock_path.value) { - ret = -ENOMEM; - PERROR("strdup"); - } - } - } else if (string_match(optname, "ustconsumerd32-cmd-sock")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--ustconsumerd32-cmd-sock"); - } else { - config_string_set( - &the_config.consumerd32_cmd_unix_sock_path, - strdup(arg)); - if (!the_config.consumerd32_cmd_unix_sock_path.value) { - ret = -ENOMEM; - PERROR("strdup"); - } - } - } else if (string_match(optname, "no-kernel")) { - the_config.no_kernel = true; - } else if (string_match(optname, "quiet") || opt == 'q') { - the_config.quiet = true; - } else if (string_match(optname, "verbose") || opt == 'v') { - /* Verbose level can increase using multiple -v */ - if (arg) { - /* Value obtained from config file */ - the_config.verbose = config_parse_value(arg); - } else { - /* -v used on command line */ - the_config.verbose++; - } - /* Clamp value to [0, 3] */ - the_config.verbose = the_config.verbose < 0 ? - 0 : - (the_config.verbose <= 3 ? the_config.verbose : - 3); - } else if (string_match(optname, "verbose-consumer")) { - if (arg) { - the_config.verbose_consumer = config_parse_value(arg); - } else { - the_config.verbose_consumer++; - } - } else if (string_match(optname, "consumerd32-path")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--consumerd32-path"); - } else { - config_string_set(&the_config.consumerd32_bin_path, - strdup(arg)); - if (!the_config.consumerd32_bin_path.value) { - PERROR("strdup"); - ret = -ENOMEM; - } - } - } else if (string_match(optname, "consumerd32-libdir")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--consumerd32-libdir"); - } else { - config_string_set(&the_config.consumerd32_lib_dir, - strdup(arg)); - if (!the_config.consumerd32_lib_dir.value) { - PERROR("strdup"); - ret = -ENOMEM; - } - } - } else if (string_match(optname, "consumerd64-path")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--consumerd64-path"); - } else { - config_string_set(&the_config.consumerd64_bin_path, - strdup(arg)); - if (!the_config.consumerd64_bin_path.value) { - PERROR("strdup"); - ret = -ENOMEM; - } - } - } else if (string_match(optname, "consumerd64-libdir")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--consumerd64-libdir"); - } else { - config_string_set(&the_config.consumerd64_lib_dir, - strdup(arg)); - if (!the_config.consumerd64_lib_dir.value) { - PERROR("strdup"); - ret = -ENOMEM; - } - } - } else if (string_match(optname, "pidfile") || opt == 'p') { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "-p, --pidfile"); - } else { - config_string_set( - &the_config.pid_file_path, strdup(arg)); - if (!the_config.pid_file_path.value) { - PERROR("strdup"); - ret = -ENOMEM; - } - } - } else if (string_match(optname, "agent-tcp-port")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--agent-tcp-port"); - } else { - unsigned long v; - - errno = 0; - v = strtoul(arg, NULL, 0); - if (errno != 0 || !isdigit(arg[0])) { - ERR("Wrong value in --agent-tcp-port parameter: %s", arg); - return -1; - } - if (v == 0 || v >= 65535) { - ERR("Port overflow in --agent-tcp-port parameter: %s", arg); - return -1; - } - the_config.agent_tcp_port.begin = - the_config.agent_tcp_port.end = (int) v; - DBG3("Agent TCP port set to non default: %i", (int) v); - } - } else if (string_match(optname, "load") || opt == 'l') { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "-l, --load"); - } else { - config_string_set(&the_config.load_session_path, - strdup(arg)); - if (!the_config.load_session_path.value) { - PERROR("strdup"); - ret = -ENOMEM; - } - } - } else if (string_match(optname, "kmod-probes")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--kmod-probes"); - } else { - config_string_set(&the_config.kmod_probes_list, - strdup(arg)); - if (!the_config.kmod_probes_list.value) { - PERROR("strdup"); - ret = -ENOMEM; - } - } - } else if (string_match(optname, "extra-kmod-probes")) { - if (!arg || *arg == '\0') { - ret = -EINVAL; - goto end; - } - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "--extra-kmod-probes"); - } else { - config_string_set(&the_config.kmod_extra_probes_list, - strdup(arg)); - if (!the_config.kmod_extra_probes_list.value) { - PERROR("strdup"); - ret = -ENOMEM; - } - } - } else if (string_match(optname, EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR)) { - unsigned long v; - - errno = 0; - v = strtoul(arg, NULL, 0); - if (errno != 0 || !isdigit(arg[0])) { - ERR("Wrong value in --%s parameter: %s", - EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR, arg); - return -1; - } - if (v == 0 || v >= EVENT_NOTIFIER_ERROR_COUNTER_NUMBER_OF_BUCKET_MAX) { - ERR("Value out of range for --%s parameter: %s", - EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR, arg); - return -1; - } - the_config.event_notifier_buffer_size_kernel = (int) v; - DBG3("Number of event notifier error buffer kernel size to non default: %i", - the_config.event_notifier_buffer_size_kernel); - goto end; - } else if (string_match(optname, EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR)) { - unsigned long v; - - errno = 0; - v = strtoul(arg, NULL, 0); - if (errno != 0 || !isdigit(arg[0])) { - ERR("Wrong value in --%s parameter: %s", - EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR, arg); - return -1; - } - if (v == 0 || v >= EVENT_NOTIFIER_ERROR_COUNTER_NUMBER_OF_BUCKET_MAX) { - ERR("Value out of range for --%s parameter: %s", - EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR, arg); - return -1; - } - the_config.event_notifier_buffer_size_userspace = (int) v; - DBG3("Number of event notifier error buffer userspace size to non default: %i", - the_config.event_notifier_buffer_size_userspace); - goto end; - } else if (string_match(optname, "config") || opt == 'f') { - /* This is handled in set_options() thus silent skip. */ - goto end; - } else { - /* Unknown option or other error. - * Error is printed by getopt, just return */ - ret = -1; - } - -end: - if (ret == -EINVAL) { - const char *opt_name = "unknown"; - int i; - - for (i = 0; i < sizeof(long_options) / sizeof(struct option); - i++) { - if (opt == long_options[i].val) { - opt_name = long_options[i].name; - break; - } - } - - WARN("Invalid argument provided for option \"%s\", using default value.", - opt_name); - } - - return ret; -} - -/* - * config_entry_handler_cb used to handle options read from a config file. - * See config_entry_handler_cb comment in common/config/session-config.h for the - * return value conventions. - */ -static int config_entry_handler(const struct config_entry *entry, void *unused) -{ - int ret = 0, i; - - if (!entry || !entry->name || !entry->value) { - ret = -EINVAL; - goto end; - } - - /* Check if the option is to be ignored */ - for (i = 0; i < sizeof(config_ignore_options) / sizeof(char *); i++) { - if (!strcmp(entry->name, config_ignore_options[i])) { - goto end; - } - } - - for (i = 0; i < (sizeof(long_options) / sizeof(struct option)) - 1; - i++) { - - /* Ignore if not fully matched. */ - if (strcmp(entry->name, long_options[i].name)) { - continue; - } - - /* - * If the option takes no argument on the command line, we have to - * check if the value is "true". We support non-zero numeric values, - * true, on and yes. - */ - if (!long_options[i].has_arg) { - ret = config_parse_value(entry->value); - if (ret <= 0) { - if (ret) { - WARN("Invalid configuration value \"%s\" for option %s", - entry->value, entry->name); - } - /* False, skip boolean config option. */ - goto end; - } - } - - ret = set_option(long_options[i].val, entry->value, entry->name); - goto end; - } - - WARN("Unrecognized option \"%s\" in daemon configuration file.", entry->name); - -end: - return ret; -} - -static void print_version(void) { - fprintf(stdout, "%s\n", VERSION); -} - -/* - * daemon configuration loading and argument parsing - */ -static int set_options(int argc, char **argv) -{ - int ret = 0, c = 0, option_index = 0; - int orig_optopt = optopt, orig_optind = optind; - char *optstring; - char *config_path = NULL; - - optstring = utils_generate_optstring(long_options, - sizeof(long_options) / sizeof(struct option)); - if (!optstring) { - ret = -ENOMEM; - goto end; - } - - /* Check for the --config option */ - while ((c = getopt_long(argc, argv, optstring, long_options, - &option_index)) != -1) { - if (c == '?') { - ret = -EINVAL; - goto end; - } else if (c != 'f') { - /* if not equal to --config option. */ - continue; - } - - if (lttng_is_setuid_setgid()) { - WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", - "-f, --config"); - } else { - free(config_path); - config_path = utils_expand_path(optarg); - if (!config_path) { - ERR("Failed to resolve path: %s", optarg); - } - } - } - - ret = config_get_section_entries(config_path, config_section_name, - config_entry_handler, NULL); - if (ret) { - if (ret > 0) { - ERR("Invalid configuration option at line %i", ret); - ret = -1; - } - goto end; - } - - /* Reset getopt's global state */ - optopt = orig_optopt; - optind = orig_optind; - while (1) { - option_index = -1; - /* - * getopt_long() will not set option_index if it encounters a - * short option. - */ - c = getopt_long(argc, argv, optstring, long_options, - &option_index); - if (c == -1) { - break; - } - - /* - * Pass NULL as the long option name if popt left the index - * unset. - */ - ret = set_option(c, optarg, - option_index < 0 ? NULL : - long_options[option_index].name); - if (ret < 0) { - break; - } - } - -end: - free(config_path); - free(optstring); - return ret; -} - -/* - * Create lockfile using the rundir and return its fd. - */ -static int create_lockfile(void) -{ - return utils_create_lock_file(the_config.lock_file_path.value); -} - -/* - * Check if the global socket is available, and if a daemon is answering at the - * other side. If yes, error is returned. - * - * Also attempts to create and hold the lock file. - */ -static int check_existing_daemon(void) -{ - int ret = 0; - - /* Is there anybody out there ? */ - if (lttng_session_daemon_alive()) { - ret = -EEXIST; - goto end; - } - - lockfile_fd = create_lockfile(); - if (lockfile_fd < 0) { - ret = -EEXIST; - goto end; - } -end: - return ret; -} - -static void sessiond_cleanup_lock_file(void) -{ - int ret; - - /* - * Cleanup lock file by deleting it and finaly closing it which will - * release the file system lock. - */ - if (lockfile_fd >= 0) { - ret = remove(the_config.lock_file_path.value); - if (ret < 0) { - PERROR("remove lock file"); - } - ret = close(lockfile_fd); - if (ret < 0) { - PERROR("close lock file"); - } - } -} - -/* - * Set the tracing group gid onto the client socket. - * - * Race window between mkdir and chown is OK because we are going from more - * permissive (root.root) to less permissive (root.tracing). - */ -static int set_permissions(char *rundir) -{ - int ret; - gid_t gid; - - ret = utils_get_group_id( - the_config.tracing_group_name.value, true, &gid); - if (ret) { - /* Default to root group. */ - gid = 0; - } - - /* Set lttng run dir */ - ret = chown(rundir, 0, gid); - if (ret < 0) { - ERR("Unable to set group on %s", rundir); - PERROR("chown"); - } - - /* - * Ensure all applications and tracing group can search the run - * dir. Allow everyone to read the directory, since it does not - * buy us anything to hide its content. - */ - ret = chmod(rundir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); - if (ret < 0) { - ERR("Unable to set permissions on %s", rundir); - PERROR("chmod"); - } - - /* lttng client socket path */ - ret = chown(the_config.client_unix_sock_path.value, 0, gid); - if (ret < 0) { - ERR("Unable to set group on %s", - the_config.client_unix_sock_path.value); - PERROR("chown"); - } - - /* kconsumer error socket path */ - ret = chown(the_kconsumer_data.err_unix_sock_path, 0, 0); - if (ret < 0) { - ERR("Unable to set group on %s", - the_kconsumer_data.err_unix_sock_path); - PERROR("chown"); - } - - /* 64-bit ustconsumer error socket path */ - ret = chown(the_ustconsumer64_data.err_unix_sock_path, 0, 0); - if (ret < 0) { - ERR("Unable to set group on %s", - the_ustconsumer64_data.err_unix_sock_path); - PERROR("chown"); - } - - /* 32-bit ustconsumer compat32 error socket path */ - ret = chown(the_ustconsumer32_data.err_unix_sock_path, 0, 0); - if (ret < 0) { - ERR("Unable to set group on %s", - the_ustconsumer32_data.err_unix_sock_path); - PERROR("chown"); - } - - DBG("All permissions are set"); - - return ret; -} - -/* - * Create the lttng run directory needed for all global sockets and pipe. - */ -static int create_lttng_rundir(void) -{ - int ret; - - DBG3("Creating LTTng run directory: %s", the_config.rundir.value); - - ret = mkdir(the_config.rundir.value, S_IRWXU); - if (ret < 0) { - if (errno != EEXIST) { - ERR("Unable to create %s", the_config.rundir.value); - goto error; - } else { - ret = 0; - } - } - -error: - return ret; -} - -/* - * Setup sockets and directory needed by the consumerds' communication with the - * session daemon. - */ -static int set_consumer_sockets(struct consumer_data *consumer_data) -{ - int ret; - char *path = NULL; - - switch (consumer_data->type) { - case LTTNG_CONSUMER_KERNEL: - path = the_config.kconsumerd_path.value; - break; - case LTTNG_CONSUMER64_UST: - path = the_config.consumerd64_path.value; - break; - case LTTNG_CONSUMER32_UST: - path = the_config.consumerd32_path.value; - break; - default: - ERR("Consumer type unknown"); - ret = -EINVAL; - goto error; - } - LTTNG_ASSERT(path); - - DBG2("Creating consumer directory: %s", path); - - ret = mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP); - if (ret < 0 && errno != EEXIST) { - PERROR("mkdir"); - ERR("Failed to create %s", path); - goto error; - } - if (is_root) { - gid_t gid; - - ret = utils_get_group_id(the_config.tracing_group_name.value, - true, &gid); - if (ret) { - /* Default to root group. */ - gid = 0; - } - - ret = chown(path, 0, gid); - if (ret < 0) { - ERR("Unable to set group on %s", path); - PERROR("chown"); - goto error; - } - } - - /* Create the consumerd error unix socket */ - consumer_data->err_sock = - lttcomm_create_unix_sock(consumer_data->err_unix_sock_path); - if (consumer_data->err_sock < 0) { - ERR("Create unix sock failed: %s", consumer_data->err_unix_sock_path); - ret = -1; - goto error; - } - - /* - * Set the CLOEXEC flag. Return code is useless because either way, the - * show must go on. - */ - ret = utils_set_fd_cloexec(consumer_data->err_sock); - if (ret < 0) { - PERROR("utils_set_fd_cloexec"); - /* continue anyway */ - } - - /* File permission MUST be 660 */ - ret = chmod(consumer_data->err_unix_sock_path, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); - if (ret < 0) { - ERR("Set file permissions failed: %s", consumer_data->err_unix_sock_path); - PERROR("chmod"); - goto error; - } - -error: - return ret; -} - -/* - * Signal handler for the daemon - * - * Simply stop all worker threads, leaving main() return gracefully after - * joining all threads and calling cleanup(). - */ -static void sighandler(int sig, siginfo_t *siginfo, void *arg) -{ - switch (sig) { - case SIGINT: - DBG("SIGINT caught"); - stop_threads(); - break; - case SIGTERM: - DBG("SIGTERM caught"); - stop_threads(); - break; - case SIGUSR1: - CMM_STORE_SHARED(recv_child_signal, 1); - break; - case SIGBUS: - { - int write_ret; - const char msg[] = "Received SIGBUS, aborting program.\n"; - - lttng_ust_handle_sigbus(siginfo->si_addr); - /* - * If ustctl did not catch this signal (triggering a - * siglongjmp), abort the program. Otherwise, the execution - * will resume from the ust-ctl call which caused this error. - * - * The return value is ignored since the program aborts anyhow. - */ - write_ret = write(STDERR_FILENO, msg, sizeof(msg)); - (void) write_ret; - abort(); - } - default: - break; - } -} - -/* - * Setup signal handler for : - * SIGINT, SIGTERM, SIGPIPE - */ -static int set_signal_handler(void) -{ - int ret = 0; - struct sigaction sa; - sigset_t sigset; - - if ((ret = sigemptyset(&sigset)) < 0) { - PERROR("sigemptyset"); - return ret; - } - - sa.sa_mask = sigset; - sa.sa_flags = SA_SIGINFO; - - sa.sa_sigaction = sighandler; - if ((ret = sigaction(SIGTERM, &sa, NULL)) < 0) { - PERROR("sigaction"); - return ret; - } - - if ((ret = sigaction(SIGINT, &sa, NULL)) < 0) { - PERROR("sigaction"); - return ret; - } - - if ((ret = sigaction(SIGUSR1, &sa, NULL)) < 0) { - PERROR("sigaction"); - return ret; - } - - if ((ret = sigaction(SIGBUS, &sa, NULL)) < 0) { - PERROR("sigaction"); - return ret; - } - - sa.sa_flags = 0; - sa.sa_handler = SIG_IGN; - if ((ret = sigaction(SIGPIPE, &sa, NULL)) < 0) { - PERROR("sigaction"); - return ret; - } - - DBG("Signal handler set for SIGTERM, SIGUSR1, SIGPIPE, SIGINT, and SIGBUS"); - - return ret; -} - -/* - * Set open files limit to unlimited. This daemon can open a large number of - * file descriptors in order to consume multiple kernel traces. - */ -static void set_ulimit(void) -{ - int ret; - struct rlimit lim; - - /* The kernel does not allow an infinite limit for open files */ - lim.rlim_cur = 65535; - lim.rlim_max = 65535; - - ret = setrlimit(RLIMIT_NOFILE, &lim); - if (ret < 0) { - PERROR("failed to set open files limit"); - } -} - -static int write_pidfile(void) -{ - return utils_create_pid_file(getpid(), the_config.pid_file_path.value); -} - -static int set_clock_plugin_env(void) -{ - int ret = 0; - char *env_value = NULL; - - if (!the_config.lttng_ust_clock_plugin.value) { - goto end; - } - - ret = asprintf(&env_value, "LTTNG_UST_CLOCK_PLUGIN=%s", - the_config.lttng_ust_clock_plugin.value); - if (ret < 0) { - PERROR("asprintf"); - goto end; - } - - ret = putenv(env_value); - if (ret) { - free(env_value); - PERROR("putenv of LTTNG_UST_CLOCK_PLUGIN"); - goto end; - } - - DBG("Updated LTTNG_UST_CLOCK_PLUGIN environment variable to \"%s\"", - the_config.lttng_ust_clock_plugin.value); -end: - return ret; -} - -static void destroy_all_sessions_and_wait(void) -{ - struct ltt_session *session, *tmp; - struct ltt_session_list *session_list; - - session_list = session_get_list(); - DBG("Initiating destruction of all sessions"); - - if (!session_list) { - return; - } - - session_lock_list(); - /* Initiate the destruction of all sessions. */ - cds_list_for_each_entry_safe(session, tmp, - &session_list->head, list) { - if (!session_get(session)) { - continue; - } - - session_lock(session); - if (session->destroyed) { - goto unlock_session; - } - (void) cmd_stop_trace(session); - (void) cmd_destroy_session( - session, the_notification_thread_handle, NULL); - unlock_session: - session_unlock(session); - session_put(session); - } - session_unlock_list(); - - /* Wait for the destruction of all sessions to complete. */ - DBG("Waiting for the destruction of all sessions to complete"); - session_list_wait_empty(); - DBG("Destruction of all sessions completed"); -} - -static void unregister_all_triggers(void) -{ - enum lttng_error_code ret_code; - enum lttng_trigger_status trigger_status; - struct lttng_triggers *triggers = NULL; - unsigned int trigger_count, i; - const struct lttng_credentials creds = { - .uid = LTTNG_OPTIONAL_INIT_VALUE(0), - }; - - DBG("Unregistering all triggers"); - - /* - * List all triggers as "root" since we wish to unregister all triggers. - */ - ret_code = notification_thread_command_list_triggers( - the_notification_thread_handle, creds.uid.value, - &triggers); - if (ret_code != LTTNG_OK) { - ERR("Failed to list triggers while unregistering all triggers"); - goto end; - } - - trigger_status = lttng_triggers_get_count(triggers, &trigger_count); - LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); - - for (i = 0; i < trigger_count; i++) { - uid_t trigger_owner; - const char *trigger_name; - const struct lttng_trigger *trigger = - lttng_triggers_get_at_index(triggers, i); - - LTTNG_ASSERT(trigger); - - trigger_status = lttng_trigger_get_owner_uid( - trigger, &trigger_owner); - LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); - - trigger_status = lttng_trigger_get_name(trigger, &trigger_name); - trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? - trigger_name : "(anonymous)"; - - DBG("Unregistering trigger: trigger owner uid = %d, trigger name = '%s'", - (int) trigger_owner, trigger_name); - - ret_code = cmd_unregister_trigger(&creds, trigger, - the_notification_thread_handle); - if (ret_code != LTTNG_OK) { - ERR("Failed to unregister trigger: trigger owner uid = %d, trigger name = '%s', error: '%s'", - (int) trigger_owner, trigger_name, - lttng_strerror(-ret_code)); - /* Continue to unregister the remaining triggers. */ - } - } -end: - lttng_triggers_destroy(triggers); -} - -static int run_as_worker_post_fork_cleanup(void *data) -{ - struct sessiond_config *sessiond_config = data; - - sessiond_config_fini(sessiond_config); - return 0; -} - -static int launch_run_as_worker(const char *procname) -{ - /* - * Clean-up before forking the run-as worker. Any dynamically - * allocated memory of which the worker is not aware will - * be leaked as the process forks a run-as worker (and performs - * no exec*()). The same would apply to any opened fd. - */ - return run_as_create_worker( - procname, run_as_worker_post_fork_cleanup, &the_config); -} - -static void sessiond_uuid_log(void) -{ - char uuid_str[LTTNG_UUID_STR_LEN]; - - lttng_uuid_to_str(the_sessiond_uuid, uuid_str); - DBG("Starting lttng-sessiond {%s}", uuid_str); -} - -/* - * main - */ -int main(int argc, char **argv) -{ - int ret = 0, retval = 0; - const char *env_app_timeout; - struct lttng_pipe *ust32_channel_monitor_pipe = NULL, - *ust64_channel_monitor_pipe = NULL, - *kernel_channel_monitor_pipe = NULL; - struct lttng_thread *ht_cleanup_thread = NULL; - struct timer_thread_parameters timer_thread_parameters; - /* Rotation thread handle. */ - struct rotation_thread_handle *rotation_thread_handle = NULL; - /* Queue of rotation jobs populated by the sessiond-timer. */ - struct rotation_thread_timer_queue *rotation_timer_queue = NULL; - struct lttng_thread *client_thread = NULL; - struct lttng_thread *notification_thread = NULL; - struct lttng_thread *register_apps_thread = NULL; - enum event_notifier_error_accounting_status event_notifier_error_accounting_status; - - logger_set_thread_name("Main", false); - init_kernel_workarounds(); - - rcu_register_thread(); - - if (set_signal_handler()) { - retval = -1; - goto exit_set_signal_handler; - } - - if (timer_signal_init()) { - retval = -1; - goto exit_set_signal_handler; - } - - the_page_size = sysconf(_SC_PAGE_SIZE); - if (the_page_size < 0) { - PERROR("sysconf _SC_PAGE_SIZE"); - the_page_size = LONG_MAX; - WARN("Fallback page size to %ld", the_page_size); - } - - ret = sessiond_config_init(&the_config); - if (ret) { - retval = -1; - goto exit_set_signal_handler; - } - - /* - * Init config from environment variables. - * Command line option override env configuration per-doc. Do env first. - */ - sessiond_config_apply_env_config(&the_config); - - /* - * Parse arguments and load the daemon configuration file. - * - * We have an exit_options exit path to free memory reserved by - * set_options. This is needed because the rest of sessiond_cleanup() - * depends on ht_cleanup_thread, which depends on lttng_daemonize, which - * depends on set_options. - */ - progname = argv[0]; - if (set_options(argc, argv)) { - retval = -1; - goto exit_options; - } - - /* - * Resolve all paths received as arguments, configuration option, or - * through environment variable as absolute paths. This is necessary - * since daemonizing causes the sessiond's current working directory - * to '/'. - */ - ret = sessiond_config_resolve_paths(&the_config); - if (ret) { - goto exit_options; - } - - /* Apply config. */ - lttng_opt_verbose = the_config.verbose; - lttng_opt_quiet = the_config.quiet; - the_kconsumer_data.err_unix_sock_path = - the_config.kconsumerd_err_unix_sock_path.value; - the_kconsumer_data.cmd_unix_sock_path = - the_config.kconsumerd_cmd_unix_sock_path.value; - the_ustconsumer32_data.err_unix_sock_path = - the_config.consumerd32_err_unix_sock_path.value; - the_ustconsumer32_data.cmd_unix_sock_path = - the_config.consumerd32_cmd_unix_sock_path.value; - the_ustconsumer64_data.err_unix_sock_path = - the_config.consumerd64_err_unix_sock_path.value; - the_ustconsumer64_data.cmd_unix_sock_path = - the_config.consumerd64_cmd_unix_sock_path.value; - set_clock_plugin_env(); - - sessiond_config_log(&the_config); - sessiond_uuid_log(); - - if (opt_print_version) { - print_version(); - retval = 0; - goto exit_options; - } - - if (create_lttng_rundir()) { - retval = -1; - goto exit_options; - } - - /* Abort launch if a session daemon is already running. */ - if (check_existing_daemon()) { - ERR("A session daemon is already running."); - retval = -1; - goto exit_options; - } - - /* Daemonize */ - if (the_config.daemonize || the_config.background) { - int i; - - ret = lttng_daemonize(&the_child_ppid, &recv_child_signal, - !the_config.background); - if (ret < 0) { - retval = -1; - goto exit_options; - } - - /* - * We are in the child. Make sure all other file descriptors are - * closed, in case we are called with more opened file - * descriptors than the standard ones and the lock file. - */ - for (i = 3; i < sysconf(_SC_OPEN_MAX); i++) { - if (i == lockfile_fd) { - continue; - } - (void) close(i); - } - } - - if (launch_run_as_worker(argv[0]) < 0) { - goto exit_create_run_as_worker_cleanup; - } - - /* - * Starting from here, we can create threads. This needs to be after - * lttng_daemonize due to RCU. - */ - - /* - * Initialize the health check subsystem. This call should set the - * appropriate time values. - */ - the_health_sessiond = health_app_create(NR_HEALTH_SESSIOND_TYPES); - if (!the_health_sessiond) { - PERROR("health_app_create error"); - retval = -1; - goto stop_threads; - } - - /* Create thread to clean up RCU hash tables */ - ht_cleanup_thread = launch_ht_cleanup_thread(); - if (!ht_cleanup_thread) { - retval = -1; - goto stop_threads; - } - - /* Create thread quit pipe */ - if (sessiond_init_thread_quit_pipe()) { - retval = -1; - goto stop_threads; - } - - /* Check if daemon is UID = 0 */ - is_root = !getuid(); - if (is_root) { - /* Create global run dir with root access */ - - kernel_channel_monitor_pipe = lttng_pipe_open(0); - if (!kernel_channel_monitor_pipe) { - ERR("Failed to create kernel consumer channel monitor pipe"); - retval = -1; - goto stop_threads; - } - the_kconsumer_data.channel_monitor_pipe = - lttng_pipe_release_writefd( - kernel_channel_monitor_pipe); - if (the_kconsumer_data.channel_monitor_pipe < 0) { - retval = -1; - goto stop_threads; - } - } - - /* Set consumer initial state */ - the_kernel_consumerd_state = CONSUMER_STOPPED; - the_ust_consumerd_state = CONSUMER_STOPPED; - - ust32_channel_monitor_pipe = lttng_pipe_open(0); - if (!ust32_channel_monitor_pipe) { - ERR("Failed to create 32-bit user space consumer channel monitor pipe"); - retval = -1; - goto stop_threads; - } - the_ustconsumer32_data.channel_monitor_pipe = - lttng_pipe_release_writefd(ust32_channel_monitor_pipe); - if (the_ustconsumer32_data.channel_monitor_pipe < 0) { - retval = -1; - goto stop_threads; - } - - /* - * The rotation_thread_timer_queue structure is shared between the - * sessiond timer thread and the rotation thread. The main thread keeps - * its ownership and destroys it when both threads have been joined. - */ - rotation_timer_queue = rotation_thread_timer_queue_create(); - if (!rotation_timer_queue) { - retval = -1; - goto stop_threads; - } - timer_thread_parameters.rotation_thread_job_queue = - rotation_timer_queue; - - ust64_channel_monitor_pipe = lttng_pipe_open(0); - if (!ust64_channel_monitor_pipe) { - ERR("Failed to create 64-bit user space consumer channel monitor pipe"); - retval = -1; - goto stop_threads; - } - the_ustconsumer64_data.channel_monitor_pipe = - lttng_pipe_release_writefd(ust64_channel_monitor_pipe); - if (the_ustconsumer64_data.channel_monitor_pipe < 0) { - retval = -1; - goto stop_threads; - } - - /* - * Init UST app hash table. Alloc hash table before this point since - * cleanup() can get called after that point. - */ - if (ust_app_ht_alloc()) { - ERR("Failed to allocate UST app hash table"); - retval = -1; - goto stop_threads; - } - - event_notifier_error_accounting_status = event_notifier_error_accounting_init( - the_config.event_notifier_buffer_size_kernel, - the_config.event_notifier_buffer_size_userspace); - if (event_notifier_error_accounting_status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - ERR("Failed to initialize event notifier error accounting system"); - retval = -1; - goto stop_threads; - } - - /* - * Initialize agent app hash table. We allocate the hash table here - * since cleanup() can get called after this point. - */ - if (agent_app_ht_alloc()) { - ERR("Failed to allocate Agent app hash table"); - retval = -1; - goto stop_threads; - } - - if (agent_by_event_notifier_domain_ht_create()) { - ERR("Failed to allocate per-event notifier domain 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 - * those paths *before* trying to set the kernel consumer sockets and init - * kernel tracer. - */ - if (is_root) { - if (set_consumer_sockets(&the_kconsumer_data)) { - retval = -1; - goto stop_threads; - } - - /* Setup kernel tracer */ - if (!the_config.no_kernel) { - init_kernel_tracer(); - } - - /* Set ulimit for open files */ - set_ulimit(); - } - /* init lttng_fd tracking must be done after set_ulimit. */ - lttng_fd_init(); - - if (set_consumer_sockets(&the_ustconsumer64_data)) { - retval = -1; - goto stop_threads; - } - - if (set_consumer_sockets(&the_ustconsumer32_data)) { - retval = -1; - goto stop_threads; - } - - /* Get parent pid if -S, --sig-parent is specified. */ - if (the_config.sig_parent) { - the_ppid = getppid(); - } - - /* Setup the kernel pipe for waking up the kernel thread */ - if (is_root && !the_config.no_kernel) { - if (utils_create_pipe_cloexec(the_kernel_poll_pipe)) { - retval = -1; - goto stop_threads; - } - } - - /* Setup the thread apps communication pipe. */ - if (utils_create_pipe_cloexec(apps_cmd_pipe)) { - retval = -1; - goto stop_threads; - } - - /* Setup the thread apps notify communication pipe. */ - if (utils_create_pipe_cloexec(apps_cmd_notify_pipe)) { - retval = -1; - goto stop_threads; - } - - /* Initialize global buffer per UID and PID registry. */ - buffer_reg_init_uid_registry(); - buffer_reg_init_pid_registry(); - - /* Init UST command queue. */ - cds_wfcq_init(&ust_cmd_queue.head, &ust_cmd_queue.tail); - - cmd_init(); - - /* Check for the application socket timeout env variable. */ - env_app_timeout = getenv(DEFAULT_APP_SOCKET_TIMEOUT_ENV); - if (env_app_timeout) { - the_config.app_socket_timeout = atoi(env_app_timeout); - } else { - the_config.app_socket_timeout = DEFAULT_APP_SOCKET_RW_TIMEOUT; - } - - ret = write_pidfile(); - if (ret) { - ERR("Error in write_pidfile"); - retval = -1; - goto stop_threads; - } - - /* Initialize communication library */ - lttcomm_init(); - /* Initialize TCP timeout values */ - lttcomm_inet_init(); - - /* Create health-check thread. */ - if (!launch_health_management_thread()) { - retval = -1; - goto stop_threads; - } - - /* notification_thread_data acquires the pipes' read side. */ - the_notification_thread_handle = notification_thread_handle_create( - ust32_channel_monitor_pipe, ust64_channel_monitor_pipe, - kernel_channel_monitor_pipe); - if (!the_notification_thread_handle) { - retval = -1; - ERR("Failed to create notification thread shared data"); - goto stop_threads; - } - - /* Create notification thread. */ - notification_thread = launch_notification_thread( - the_notification_thread_handle); - if (!notification_thread) { - retval = -1; - goto stop_threads; - } - - /* Create timer thread. */ - if (!launch_timer_thread(&timer_thread_parameters)) { - retval = -1; - goto stop_threads; - } - - /* rotation_thread_data acquires the pipes' read side. */ - rotation_thread_handle = rotation_thread_handle_create( - rotation_timer_queue, the_notification_thread_handle); - if (!rotation_thread_handle) { - retval = -1; - ERR("Failed to create rotation thread shared data"); - stop_threads(); - goto stop_threads; - } - - /* Create rotation thread. */ - if (!launch_rotation_thread(rotation_thread_handle)) { - retval = -1; - goto stop_threads; - } - - /* Create thread to manage the client socket */ - client_thread = launch_client_thread(); - if (!client_thread) { - retval = -1; - goto stop_threads; - } - - /* Set credentials of the client socket and rundir */ - if (is_root && set_permissions(the_config.rundir.value)) { - retval = -1; - goto stop_threads; - } - - if (!launch_ust_dispatch_thread(&ust_cmd_queue, apps_cmd_pipe[1], - apps_cmd_notify_pipe[1])) { - retval = -1; - goto stop_threads; - } - - /* Create thread to manage application registration. */ - register_apps_thread = launch_application_registration_thread( - &ust_cmd_queue); - if (!register_apps_thread) { - retval = -1; - goto stop_threads; - } - - /* Create thread to manage application socket */ - if (!launch_application_management_thread(apps_cmd_pipe[0])) { - retval = -1; - goto stop_threads; - } - - /* Create thread to manage application notify socket */ - if (!launch_application_notification_thread(apps_cmd_notify_pipe[0])) { - retval = -1; - goto stop_threads; - } - - /* Create agent management thread. */ - if (!launch_agent_management_thread()) { - retval = -1; - goto stop_threads; - } - - /* Don't start this thread if kernel tracing is not requested nor root */ - if (is_root && !the_config.no_kernel) { - /* Create kernel thread to manage kernel event */ - if (!launch_kernel_management_thread(the_kernel_poll_pipe[0])) { - retval = -1; - goto stop_threads; - } - - if (kernel_get_notification_fd() >= 0) { - ret = notification_thread_command_add_tracer_event_source( - the_notification_thread_handle, - kernel_get_notification_fd(), - LTTNG_DOMAIN_KERNEL); - if (ret != LTTNG_OK) { - ERR("Failed to add kernel trigger event source to notification thread"); - retval = -1; - goto stop_threads; - } - } - } - - /* Load sessions. */ - ret = config_load_session( - the_config.load_session_path.value, NULL, 1, 1, NULL); - if (ret) { - ERR("Session load failed: %s", error_get_str(ret)); - retval = -1; - goto stop_threads; - } - - /* Initialization completed. */ - sessiond_signal_parents(); - - /* - * This is where we start awaiting program completion (e.g. through - * signal that asks threads to teardown). - */ - - /* Initiate teardown once activity occurs on the quit pipe. */ - sessiond_wait_for_quit_pipe(-1); - -stop_threads: - - /* - * Ensure that the client thread is no longer accepting new commands, - * which could cause new sessions to be created. - */ - if (client_thread) { - lttng_thread_shutdown(client_thread); - lttng_thread_put(client_thread); - } - - destroy_all_sessions_and_wait(); - - /* - * At this point no new trigger can be registered (no sessions are - * running/rotating) and clients can't connect to the session daemon - * anymore. Unregister all triggers. - */ - unregister_all_triggers(); - - if (register_apps_thread) { - lttng_thread_shutdown(register_apps_thread); - lttng_thread_put(register_apps_thread); - } - lttng_thread_list_shutdown_orphans(); - - /* - * Wait for all pending call_rcu work to complete before tearing - * down data structures. call_rcu worker may be trying to - * perform lookups in those structures. - */ - rcu_barrier(); - /* - * sessiond_cleanup() is called when no other thread is running, except - * the ht_cleanup thread, which is needed to destroy the hash tables. - */ - rcu_thread_online(); - sessiond_cleanup(); - - /* - * Wait for all pending call_rcu work to complete before shutting down - * the notification thread. This call_rcu work includes shutting down - * UST apps and event notifier pipes. - */ - rcu_barrier(); - - if (notification_thread) { - lttng_thread_shutdown(notification_thread); - lttng_thread_put(notification_thread); - } - - /* - * Error accounting teardown has to be done after the teardown of all - * event notifier pipes to ensure that no tracer may try to use the - * error accounting facilities. - */ - event_notifier_error_accounting_fini(); - - /* - * Unloading the kernel modules needs to be done after all kernel - * ressources have been released. In our case, this includes the - * notification fd, the event notifier group fd, error accounting fd, - * all event and event notifier fds, etc. - * - * In short, at this point, we need to have called close() on all fds - * received from the kernel tracer. - */ - if (is_root && !the_config.no_kernel) { - DBG("Unloading kernel modules"); - modprobe_remove_lttng_all(); - } - - /* - * Ensure all prior call_rcu are done. call_rcu callbacks may push - * hash tables to the ht_cleanup thread. Therefore, we ensure that - * the queue is empty before shutting down the clean-up thread. - */ - rcu_barrier(); - - if (ht_cleanup_thread) { - lttng_thread_shutdown(ht_cleanup_thread); - lttng_thread_put(ht_cleanup_thread); - } - - rcu_thread_offline(); - rcu_unregister_thread(); - - if (rotation_thread_handle) { - rotation_thread_handle_destroy(rotation_thread_handle); - } - - /* - * After the rotation and timer thread have quit, we can safely destroy - * the rotation_timer_queue. - */ - rotation_thread_timer_queue_destroy(rotation_timer_queue); - /* - * The teardown of the notification system is performed after the - * session daemon's teardown in order to allow it to be notified - * of the active session and channels at the moment of the teardown. - */ - if (the_notification_thread_handle) { - notification_thread_handle_destroy( - the_notification_thread_handle); - } - lttng_pipe_destroy(ust32_channel_monitor_pipe); - lttng_pipe_destroy(ust64_channel_monitor_pipe); - lttng_pipe_destroy(kernel_channel_monitor_pipe); - - if (the_health_sessiond) { - health_app_destroy(the_health_sessiond); - } -exit_create_run_as_worker_cleanup: -exit_options: - sessiond_cleanup_lock_file(); - sessiond_cleanup_options(); - -exit_set_signal_handler: - if (!retval) { - exit(EXIT_SUCCESS); - } else { - exit(EXIT_FAILURE); - } -} diff --git a/src/bin/lttng-sessiond/main.cpp b/src/bin/lttng-sessiond/main.cpp new file mode 100644 index 000000000..e5e718891 --- /dev/null +++ b/src/bin/lttng-sessiond/main.cpp @@ -0,0 +1,2053 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lttng-sessiond.h" +#include "buffer-registry.h" +#include "channel.h" +#include "cmd.h" +#include "consumer.h" +#include "context.h" +#include "event.h" +#include "event-notifier-error-accounting.h" +#include "kernel.h" +#include "kernel-consumer.h" +#include "lttng-ust-ctl.h" +#include "ust-consumer.h" +#include "utils.h" +#include "fd-limit.h" +#include "health-sessiond.h" +#include "testpoint.h" +#include "notify-apps.h" +#include "agent-thread.h" +#include "save.h" +#include "notification-thread.h" +#include "notification-thread-commands.h" +#include "rotation-thread.h" +#include "agent.h" +#include "ht-cleanup.h" +#include "sessiond-config.h" +#include "timer.h" +#include "thread.h" +#include "client.h" +#include "dispatch.h" +#include "register.h" +#include "manage-apps.h" +#include "manage-kernel.h" +#include "modprobe.h" +#include "ust-sigbus.h" + +static const char *help_msg = +#ifdef LTTNG_EMBED_HELP +#include +#else +NULL +#endif +; + +#define EVENT_NOTIFIER_ERROR_COUNTER_NUMBER_OF_BUCKET_MAX 65535 +#define EVENT_NOTIFIER_ERROR_BUFFER_SIZE_BASE_OPTION_STR \ + "event-notifier-error-buffer-size" +#define EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR \ + EVENT_NOTIFIER_ERROR_BUFFER_SIZE_BASE_OPTION_STR "-kernel" +#define EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR \ + EVENT_NOTIFIER_ERROR_BUFFER_SIZE_BASE_OPTION_STR "-userspace" + + +const char *progname; +static int lockfile_fd = -1; +static int opt_print_version; + +/* Set to 1 when a SIGUSR1 signal is received. */ +static int recv_child_signal; + +/* Command line options */ +static const struct option long_options[] = { + { "client-sock", required_argument, 0, 'c' }, + { "apps-sock", required_argument, 0, 'a' }, + { "kconsumerd-cmd-sock", required_argument, 0, '\0' }, + { "kconsumerd-err-sock", required_argument, 0, '\0' }, + { "ustconsumerd32-cmd-sock", required_argument, 0, '\0' }, + { "ustconsumerd32-err-sock", required_argument, 0, '\0' }, + { "ustconsumerd64-cmd-sock", required_argument, 0, '\0' }, + { "ustconsumerd64-err-sock", required_argument, 0, '\0' }, + { "consumerd32-path", required_argument, 0, '\0' }, + { "consumerd32-libdir", required_argument, 0, '\0' }, + { "consumerd64-path", required_argument, 0, '\0' }, + { "consumerd64-libdir", required_argument, 0, '\0' }, + { "daemonize", no_argument, 0, 'd' }, + { "background", no_argument, 0, 'b' }, + { "sig-parent", no_argument, 0, 'S' }, + { "help", no_argument, 0, 'h' }, + { "group", required_argument, 0, 'g' }, + { "version", no_argument, 0, 'V' }, + { "quiet", no_argument, 0, 'q' }, + { "verbose", no_argument, 0, 'v' }, + { "verbose-consumer", no_argument, 0, '\0' }, + { "no-kernel", no_argument, 0, '\0' }, + { "pidfile", required_argument, 0, 'p' }, + { "agent-tcp-port", required_argument, 0, '\0' }, + { "config", required_argument, 0, 'f' }, + { "load", required_argument, 0, 'l' }, + { "kmod-probes", required_argument, 0, '\0' }, + { "extra-kmod-probes", required_argument, 0, '\0' }, + { EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR, required_argument, 0, '\0' }, + { EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR, required_argument, 0, '\0' }, + { NULL, 0, 0, 0 } +}; + +/* Command line options to ignore from configuration file */ +static const char *config_ignore_options[] = { "help", "version", "config" }; + +/* + * This pipe is used to inform the thread managing application communication + * that a command is queued and ready to be processed. + */ +static int apps_cmd_pipe[2] = { -1, -1 }; +static int apps_cmd_notify_pipe[2] = { -1, -1 }; + +/* + * UST registration command queue. This queue is tied with a futex and uses a N + * wakers / 1 waiter implemented and detailed in futex.c/.h + * + * The thread_registration_apps and thread_dispatch_ust_registration uses this + * queue along with the wait/wake scheme. The thread_manage_apps receives down + * the line new application socket and monitors it for any I/O error or clean + * close that triggers an unregistration of the application. + */ +static struct ust_cmd_queue ust_cmd_queue; + +/* + * Section name to look for in the daemon configuration file. + */ +static const char * const config_section_name = "sessiond"; + +/* Am I root or not. Set to 1 if the daemon is running as root */ +static int is_root; + +/* + * Stop all threads by closing the thread quit pipe. + */ +static void stop_threads(void) +{ + int ret; + + /* Stopping all threads */ + DBG("Terminating all threads"); + ret = sessiond_notify_quit_pipe(); + if (ret < 0) { + ERR("write error on thread quit pipe"); + } +} + +/* + * Close every consumer sockets. + */ +static void close_consumer_sockets(void) +{ + int ret; + + if (the_kconsumer_data.err_sock >= 0) { + ret = close(the_kconsumer_data.err_sock); + if (ret < 0) { + PERROR("kernel consumer err_sock close"); + } + } + if (the_ustconsumer32_data.err_sock >= 0) { + ret = close(the_ustconsumer32_data.err_sock); + if (ret < 0) { + PERROR("UST consumerd32 err_sock close"); + } + } + if (the_ustconsumer64_data.err_sock >= 0) { + ret = close(the_ustconsumer64_data.err_sock); + if (ret < 0) { + PERROR("UST consumerd64 err_sock close"); + } + } + if (the_kconsumer_data.cmd_sock >= 0) { + ret = close(the_kconsumer_data.cmd_sock); + if (ret < 0) { + PERROR("kernel consumer cmd_sock close"); + } + } + if (the_ustconsumer32_data.cmd_sock >= 0) { + ret = close(the_ustconsumer32_data.cmd_sock); + if (ret < 0) { + PERROR("UST consumerd32 cmd_sock close"); + } + } + if (the_ustconsumer64_data.cmd_sock >= 0) { + ret = close(the_ustconsumer64_data.cmd_sock); + if (ret < 0) { + PERROR("UST consumerd64 cmd_sock close"); + } + } + if (the_kconsumer_data.channel_monitor_pipe >= 0) { + ret = close(the_kconsumer_data.channel_monitor_pipe); + if (ret < 0) { + PERROR("kernel consumer channel monitor pipe close"); + } + } + if (the_ustconsumer32_data.channel_monitor_pipe >= 0) { + ret = close(the_ustconsumer32_data.channel_monitor_pipe); + if (ret < 0) { + PERROR("UST consumerd32 channel monitor pipe close"); + } + } + if (the_ustconsumer64_data.channel_monitor_pipe >= 0) { + ret = close(the_ustconsumer64_data.channel_monitor_pipe); + if (ret < 0) { + PERROR("UST consumerd64 channel monitor pipe close"); + } + } +} + +/* + * Wait on consumer process termination. + * + * Need to be called with the consumer data lock held or from a context + * ensuring no concurrent access to data (e.g: cleanup). + */ +static void wait_consumer(struct consumer_data *consumer_data) +{ + pid_t ret; + int status; + + if (consumer_data->pid <= 0) { + return; + } + + DBG("Waiting for complete teardown of consumerd (PID: %d)", + consumer_data->pid); + ret = waitpid(consumer_data->pid, &status, 0); + if (ret == -1) { + PERROR("consumerd waitpid pid: %d", consumer_data->pid) + } else if (!WIFEXITED(status)) { + ERR("consumerd termination with error: %d", + WEXITSTATUS(ret)); + } + consumer_data->pid = 0; +} + +/* + * Cleanup the session daemon's data structures. + */ +static void sessiond_cleanup(void) +{ + int ret; + struct ltt_session_list *session_list = session_get_list(); + + DBG("Cleanup sessiond"); + + /* + * Close the thread quit pipe. It has already done its job, + * since we are now called. + */ + sessiond_close_quit_pipe(); + utils_close_pipe(apps_cmd_pipe); + utils_close_pipe(apps_cmd_notify_pipe); + utils_close_pipe(the_kernel_poll_pipe); + + ret = remove(the_config.pid_file_path.value); + if (ret < 0) { + PERROR("remove pidfile %s", the_config.pid_file_path.value); + } + + DBG("Removing sessiond and consumerd content of directory %s", + the_config.rundir.value); + + /* sessiond */ + DBG("Removing %s", the_config.pid_file_path.value); + (void) unlink(the_config.pid_file_path.value); + + DBG("Removing %s", the_config.agent_port_file_path.value); + (void) unlink(the_config.agent_port_file_path.value); + + /* kconsumerd */ + DBG("Removing %s", the_kconsumer_data.err_unix_sock_path); + (void) unlink(the_kconsumer_data.err_unix_sock_path); + + DBG("Removing directory %s", the_config.kconsumerd_path.value); + (void) rmdir(the_config.kconsumerd_path.value); + + /* ust consumerd 32 */ + DBG("Removing %s", the_config.consumerd32_err_unix_sock_path.value); + (void) unlink(the_config.consumerd32_err_unix_sock_path.value); + + DBG("Removing directory %s", the_config.consumerd32_path.value); + (void) rmdir(the_config.consumerd32_path.value); + + /* ust consumerd 64 */ + DBG("Removing %s", the_config.consumerd64_err_unix_sock_path.value); + (void) unlink(the_config.consumerd64_err_unix_sock_path.value); + + DBG("Removing directory %s", the_config.consumerd64_path.value); + (void) rmdir(the_config.consumerd64_path.value); + + pthread_mutex_destroy(&session_list->lock); + + DBG("Cleaning up all per-event notifier domain agents"); + agent_by_event_notifier_domain_ht_destroy(); + + DBG("Cleaning up all agent apps"); + agent_app_ht_clean(); + DBG("Closing all UST sockets"); + ust_app_clean_list(); + buffer_reg_destroy_registries(); + + close_consumer_sockets(); + + wait_consumer(&the_kconsumer_data); + wait_consumer(&the_ustconsumer64_data); + wait_consumer(&the_ustconsumer32_data); + + if (is_root && !the_config.no_kernel) { + cleanup_kernel_tracer(); + } + + /* + * We do NOT rmdir rundir because there are other processes + * using it, for instance lttng-relayd, which can start in + * parallel with this teardown. + */ +} + +/* + * Cleanup the daemon's option data structures. + */ +static void sessiond_cleanup_options(void) +{ + DBG("Cleaning up options"); + + sessiond_config_fini(&the_config); + + run_as_destroy_worker(); +} + +static int string_match(const char *str1, const char *str2) +{ + return (str1 && str2) && !strcmp(str1, str2); +} + +/* + * Take an option from the getopt output and set it in the right variable to be + * used later. + * + * Return 0 on success else a negative value. + */ +static int set_option(int opt, const char *arg, const char *optname) +{ + int ret = 0; + + if (string_match(optname, "client-sock") || opt == 'c') { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "-c, --client-sock"); + } else { + config_string_set(&the_config.client_unix_sock_path, + strdup(arg)); + if (!the_config.client_unix_sock_path.value) { + ret = -ENOMEM; + PERROR("strdup"); + } + } + } else if (string_match(optname, "apps-sock") || opt == 'a') { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "-a, --apps-sock"); + } else { + config_string_set(&the_config.apps_unix_sock_path, + strdup(arg)); + if (!the_config.apps_unix_sock_path.value) { + ret = -ENOMEM; + PERROR("strdup"); + } + } + } else if (string_match(optname, "daemonize") || opt == 'd') { + the_config.daemonize = true; + } else if (string_match(optname, "background") || opt == 'b') { + the_config.background = true; + } else if (string_match(optname, "group") || opt == 'g') { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "-g, --group"); + } else { + config_string_set(&the_config.tracing_group_name, + strdup(arg)); + if (!the_config.tracing_group_name.value) { + ret = -ENOMEM; + PERROR("strdup"); + } + } + } else if (string_match(optname, "help") || opt == 'h') { + ret = utils_show_help(8, "lttng-sessiond", help_msg); + if (ret) { + ERR("Cannot show --help for `lttng-sessiond`"); + perror("exec"); + } + exit(ret ? EXIT_FAILURE : EXIT_SUCCESS); + } else if (string_match(optname, "version") || opt == 'V') { + opt_print_version = 1; + } else if (string_match(optname, "sig-parent") || opt == 'S') { + the_config.sig_parent = true; + } else if (string_match(optname, "kconsumerd-err-sock")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--kconsumerd-err-sock"); + } else { + config_string_set( + &the_config.kconsumerd_err_unix_sock_path, + strdup(arg)); + if (!the_config.kconsumerd_err_unix_sock_path.value) { + ret = -ENOMEM; + PERROR("strdup"); + } + } + } else if (string_match(optname, "kconsumerd-cmd-sock")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--kconsumerd-cmd-sock"); + } else { + config_string_set( + &the_config.kconsumerd_cmd_unix_sock_path, + strdup(arg)); + if (!the_config.kconsumerd_cmd_unix_sock_path.value) { + ret = -ENOMEM; + PERROR("strdup"); + } + } + } else if (string_match(optname, "ustconsumerd64-err-sock")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--ustconsumerd64-err-sock"); + } else { + config_string_set( + &the_config.consumerd64_err_unix_sock_path, + strdup(arg)); + if (!the_config.consumerd64_err_unix_sock_path.value) { + ret = -ENOMEM; + PERROR("strdup"); + } + } + } else if (string_match(optname, "ustconsumerd64-cmd-sock")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--ustconsumerd64-cmd-sock"); + } else { + config_string_set( + &the_config.consumerd64_cmd_unix_sock_path, + strdup(arg)); + if (!the_config.consumerd64_cmd_unix_sock_path.value) { + ret = -ENOMEM; + PERROR("strdup"); + } + } + } else if (string_match(optname, "ustconsumerd32-err-sock")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--ustconsumerd32-err-sock"); + } else { + config_string_set( + &the_config.consumerd32_err_unix_sock_path, + strdup(arg)); + if (!the_config.consumerd32_err_unix_sock_path.value) { + ret = -ENOMEM; + PERROR("strdup"); + } + } + } else if (string_match(optname, "ustconsumerd32-cmd-sock")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--ustconsumerd32-cmd-sock"); + } else { + config_string_set( + &the_config.consumerd32_cmd_unix_sock_path, + strdup(arg)); + if (!the_config.consumerd32_cmd_unix_sock_path.value) { + ret = -ENOMEM; + PERROR("strdup"); + } + } + } else if (string_match(optname, "no-kernel")) { + the_config.no_kernel = true; + } else if (string_match(optname, "quiet") || opt == 'q') { + the_config.quiet = true; + } else if (string_match(optname, "verbose") || opt == 'v') { + /* Verbose level can increase using multiple -v */ + if (arg) { + /* Value obtained from config file */ + the_config.verbose = config_parse_value(arg); + } else { + /* -v used on command line */ + the_config.verbose++; + } + /* Clamp value to [0, 3] */ + the_config.verbose = the_config.verbose < 0 ? + 0 : + (the_config.verbose <= 3 ? the_config.verbose : + 3); + } else if (string_match(optname, "verbose-consumer")) { + if (arg) { + the_config.verbose_consumer = config_parse_value(arg); + } else { + the_config.verbose_consumer++; + } + } else if (string_match(optname, "consumerd32-path")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--consumerd32-path"); + } else { + config_string_set(&the_config.consumerd32_bin_path, + strdup(arg)); + if (!the_config.consumerd32_bin_path.value) { + PERROR("strdup"); + ret = -ENOMEM; + } + } + } else if (string_match(optname, "consumerd32-libdir")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--consumerd32-libdir"); + } else { + config_string_set(&the_config.consumerd32_lib_dir, + strdup(arg)); + if (!the_config.consumerd32_lib_dir.value) { + PERROR("strdup"); + ret = -ENOMEM; + } + } + } else if (string_match(optname, "consumerd64-path")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--consumerd64-path"); + } else { + config_string_set(&the_config.consumerd64_bin_path, + strdup(arg)); + if (!the_config.consumerd64_bin_path.value) { + PERROR("strdup"); + ret = -ENOMEM; + } + } + } else if (string_match(optname, "consumerd64-libdir")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--consumerd64-libdir"); + } else { + config_string_set(&the_config.consumerd64_lib_dir, + strdup(arg)); + if (!the_config.consumerd64_lib_dir.value) { + PERROR("strdup"); + ret = -ENOMEM; + } + } + } else if (string_match(optname, "pidfile") || opt == 'p') { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "-p, --pidfile"); + } else { + config_string_set( + &the_config.pid_file_path, strdup(arg)); + if (!the_config.pid_file_path.value) { + PERROR("strdup"); + ret = -ENOMEM; + } + } + } else if (string_match(optname, "agent-tcp-port")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--agent-tcp-port"); + } else { + unsigned long v; + + errno = 0; + v = strtoul(arg, NULL, 0); + if (errno != 0 || !isdigit(arg[0])) { + ERR("Wrong value in --agent-tcp-port parameter: %s", arg); + return -1; + } + if (v == 0 || v >= 65535) { + ERR("Port overflow in --agent-tcp-port parameter: %s", arg); + return -1; + } + the_config.agent_tcp_port.begin = + the_config.agent_tcp_port.end = (int) v; + DBG3("Agent TCP port set to non default: %i", (int) v); + } + } else if (string_match(optname, "load") || opt == 'l') { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "-l, --load"); + } else { + config_string_set(&the_config.load_session_path, + strdup(arg)); + if (!the_config.load_session_path.value) { + PERROR("strdup"); + ret = -ENOMEM; + } + } + } else if (string_match(optname, "kmod-probes")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--kmod-probes"); + } else { + config_string_set(&the_config.kmod_probes_list, + strdup(arg)); + if (!the_config.kmod_probes_list.value) { + PERROR("strdup"); + ret = -ENOMEM; + } + } + } else if (string_match(optname, "extra-kmod-probes")) { + if (!arg || *arg == '\0') { + ret = -EINVAL; + goto end; + } + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "--extra-kmod-probes"); + } else { + config_string_set(&the_config.kmod_extra_probes_list, + strdup(arg)); + if (!the_config.kmod_extra_probes_list.value) { + PERROR("strdup"); + ret = -ENOMEM; + } + } + } else if (string_match(optname, EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR)) { + unsigned long v; + + errno = 0; + v = strtoul(arg, NULL, 0); + if (errno != 0 || !isdigit(arg[0])) { + ERR("Wrong value in --%s parameter: %s", + EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR, arg); + return -1; + } + if (v == 0 || v >= EVENT_NOTIFIER_ERROR_COUNTER_NUMBER_OF_BUCKET_MAX) { + ERR("Value out of range for --%s parameter: %s", + EVENT_NOTIFIER_ERROR_BUFFER_SIZE_KERNEL_OPTION_STR, arg); + return -1; + } + the_config.event_notifier_buffer_size_kernel = (int) v; + DBG3("Number of event notifier error buffer kernel size to non default: %i", + the_config.event_notifier_buffer_size_kernel); + goto end; + } else if (string_match(optname, EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR)) { + unsigned long v; + + errno = 0; + v = strtoul(arg, NULL, 0); + if (errno != 0 || !isdigit(arg[0])) { + ERR("Wrong value in --%s parameter: %s", + EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR, arg); + return -1; + } + if (v == 0 || v >= EVENT_NOTIFIER_ERROR_COUNTER_NUMBER_OF_BUCKET_MAX) { + ERR("Value out of range for --%s parameter: %s", + EVENT_NOTIFIER_ERROR_BUFFER_SIZE_USERSPACE_OPTION_STR, arg); + return -1; + } + the_config.event_notifier_buffer_size_userspace = (int) v; + DBG3("Number of event notifier error buffer userspace size to non default: %i", + the_config.event_notifier_buffer_size_userspace); + goto end; + } else if (string_match(optname, "config") || opt == 'f') { + /* This is handled in set_options() thus silent skip. */ + goto end; + } else { + /* Unknown option or other error. + * Error is printed by getopt, just return */ + ret = -1; + } + +end: + if (ret == -EINVAL) { + const char *opt_name = "unknown"; + int i; + + for (i = 0; i < sizeof(long_options) / sizeof(struct option); + i++) { + if (opt == long_options[i].val) { + opt_name = long_options[i].name; + break; + } + } + + WARN("Invalid argument provided for option \"%s\", using default value.", + opt_name); + } + + return ret; +} + +/* + * config_entry_handler_cb used to handle options read from a config file. + * See config_entry_handler_cb comment in common/config/session-config.h for the + * return value conventions. + */ +static int config_entry_handler(const struct config_entry *entry, void *unused) +{ + int ret = 0, i; + + if (!entry || !entry->name || !entry->value) { + ret = -EINVAL; + goto end; + } + + /* Check if the option is to be ignored */ + for (i = 0; i < sizeof(config_ignore_options) / sizeof(char *); i++) { + if (!strcmp(entry->name, config_ignore_options[i])) { + goto end; + } + } + + for (i = 0; i < (sizeof(long_options) / sizeof(struct option)) - 1; + i++) { + + /* Ignore if not fully matched. */ + if (strcmp(entry->name, long_options[i].name)) { + continue; + } + + /* + * If the option takes no argument on the command line, we have to + * check if the value is "true". We support non-zero numeric values, + * true, on and yes. + */ + if (!long_options[i].has_arg) { + ret = config_parse_value(entry->value); + if (ret <= 0) { + if (ret) { + WARN("Invalid configuration value \"%s\" for option %s", + entry->value, entry->name); + } + /* False, skip boolean config option. */ + goto end; + } + } + + ret = set_option(long_options[i].val, entry->value, entry->name); + goto end; + } + + WARN("Unrecognized option \"%s\" in daemon configuration file.", entry->name); + +end: + return ret; +} + +static void print_version(void) { + fprintf(stdout, "%s\n", VERSION); +} + +/* + * daemon configuration loading and argument parsing + */ +static int set_options(int argc, char **argv) +{ + int ret = 0, c = 0, option_index = 0; + int orig_optopt = optopt, orig_optind = optind; + char *optstring; + char *config_path = NULL; + + optstring = utils_generate_optstring(long_options, + sizeof(long_options) / sizeof(struct option)); + if (!optstring) { + ret = -ENOMEM; + goto end; + } + + /* Check for the --config option */ + while ((c = getopt_long(argc, argv, optstring, long_options, + &option_index)) != -1) { + if (c == '?') { + ret = -EINVAL; + goto end; + } else if (c != 'f') { + /* if not equal to --config option. */ + continue; + } + + if (lttng_is_setuid_setgid()) { + WARN("Getting '%s' argument from setuid/setgid binary refused for security reasons.", + "-f, --config"); + } else { + free(config_path); + config_path = utils_expand_path(optarg); + if (!config_path) { + ERR("Failed to resolve path: %s", optarg); + } + } + } + + ret = config_get_section_entries(config_path, config_section_name, + config_entry_handler, NULL); + if (ret) { + if (ret > 0) { + ERR("Invalid configuration option at line %i", ret); + ret = -1; + } + goto end; + } + + /* Reset getopt's global state */ + optopt = orig_optopt; + optind = orig_optind; + while (1) { + option_index = -1; + /* + * getopt_long() will not set option_index if it encounters a + * short option. + */ + c = getopt_long(argc, argv, optstring, long_options, + &option_index); + if (c == -1) { + break; + } + + /* + * Pass NULL as the long option name if popt left the index + * unset. + */ + ret = set_option(c, optarg, + option_index < 0 ? NULL : + long_options[option_index].name); + if (ret < 0) { + break; + } + } + +end: + free(config_path); + free(optstring); + return ret; +} + +/* + * Create lockfile using the rundir and return its fd. + */ +static int create_lockfile(void) +{ + return utils_create_lock_file(the_config.lock_file_path.value); +} + +/* + * Check if the global socket is available, and if a daemon is answering at the + * other side. If yes, error is returned. + * + * Also attempts to create and hold the lock file. + */ +static int check_existing_daemon(void) +{ + int ret = 0; + + /* Is there anybody out there ? */ + if (lttng_session_daemon_alive()) { + ret = -EEXIST; + goto end; + } + + lockfile_fd = create_lockfile(); + if (lockfile_fd < 0) { + ret = -EEXIST; + goto end; + } +end: + return ret; +} + +static void sessiond_cleanup_lock_file(void) +{ + int ret; + + /* + * Cleanup lock file by deleting it and finaly closing it which will + * release the file system lock. + */ + if (lockfile_fd >= 0) { + ret = remove(the_config.lock_file_path.value); + if (ret < 0) { + PERROR("remove lock file"); + } + ret = close(lockfile_fd); + if (ret < 0) { + PERROR("close lock file"); + } + } +} + +/* + * Set the tracing group gid onto the client socket. + * + * Race window between mkdir and chown is OK because we are going from more + * permissive (root.root) to less permissive (root.tracing). + */ +static int set_permissions(char *rundir) +{ + int ret; + gid_t gid; + + ret = utils_get_group_id( + the_config.tracing_group_name.value, true, &gid); + if (ret) { + /* Default to root group. */ + gid = 0; + } + + /* Set lttng run dir */ + ret = chown(rundir, 0, gid); + if (ret < 0) { + ERR("Unable to set group on %s", rundir); + PERROR("chown"); + } + + /* + * Ensure all applications and tracing group can search the run + * dir. Allow everyone to read the directory, since it does not + * buy us anything to hide its content. + */ + ret = chmod(rundir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + if (ret < 0) { + ERR("Unable to set permissions on %s", rundir); + PERROR("chmod"); + } + + /* lttng client socket path */ + ret = chown(the_config.client_unix_sock_path.value, 0, gid); + if (ret < 0) { + ERR("Unable to set group on %s", + the_config.client_unix_sock_path.value); + PERROR("chown"); + } + + /* kconsumer error socket path */ + ret = chown(the_kconsumer_data.err_unix_sock_path, 0, 0); + if (ret < 0) { + ERR("Unable to set group on %s", + the_kconsumer_data.err_unix_sock_path); + PERROR("chown"); + } + + /* 64-bit ustconsumer error socket path */ + ret = chown(the_ustconsumer64_data.err_unix_sock_path, 0, 0); + if (ret < 0) { + ERR("Unable to set group on %s", + the_ustconsumer64_data.err_unix_sock_path); + PERROR("chown"); + } + + /* 32-bit ustconsumer compat32 error socket path */ + ret = chown(the_ustconsumer32_data.err_unix_sock_path, 0, 0); + if (ret < 0) { + ERR("Unable to set group on %s", + the_ustconsumer32_data.err_unix_sock_path); + PERROR("chown"); + } + + DBG("All permissions are set"); + + return ret; +} + +/* + * Create the lttng run directory needed for all global sockets and pipe. + */ +static int create_lttng_rundir(void) +{ + int ret; + + DBG3("Creating LTTng run directory: %s", the_config.rundir.value); + + ret = mkdir(the_config.rundir.value, S_IRWXU); + if (ret < 0) { + if (errno != EEXIST) { + ERR("Unable to create %s", the_config.rundir.value); + goto error; + } else { + ret = 0; + } + } + +error: + return ret; +} + +/* + * Setup sockets and directory needed by the consumerds' communication with the + * session daemon. + */ +static int set_consumer_sockets(struct consumer_data *consumer_data) +{ + int ret; + char *path = NULL; + + switch (consumer_data->type) { + case LTTNG_CONSUMER_KERNEL: + path = the_config.kconsumerd_path.value; + break; + case LTTNG_CONSUMER64_UST: + path = the_config.consumerd64_path.value; + break; + case LTTNG_CONSUMER32_UST: + path = the_config.consumerd32_path.value; + break; + default: + ERR("Consumer type unknown"); + ret = -EINVAL; + goto error; + } + LTTNG_ASSERT(path); + + DBG2("Creating consumer directory: %s", path); + + ret = mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP); + if (ret < 0 && errno != EEXIST) { + PERROR("mkdir"); + ERR("Failed to create %s", path); + goto error; + } + if (is_root) { + gid_t gid; + + ret = utils_get_group_id(the_config.tracing_group_name.value, + true, &gid); + if (ret) { + /* Default to root group. */ + gid = 0; + } + + ret = chown(path, 0, gid); + if (ret < 0) { + ERR("Unable to set group on %s", path); + PERROR("chown"); + goto error; + } + } + + /* Create the consumerd error unix socket */ + consumer_data->err_sock = + lttcomm_create_unix_sock(consumer_data->err_unix_sock_path); + if (consumer_data->err_sock < 0) { + ERR("Create unix sock failed: %s", consumer_data->err_unix_sock_path); + ret = -1; + goto error; + } + + /* + * Set the CLOEXEC flag. Return code is useless because either way, the + * show must go on. + */ + ret = utils_set_fd_cloexec(consumer_data->err_sock); + if (ret < 0) { + PERROR("utils_set_fd_cloexec"); + /* continue anyway */ + } + + /* File permission MUST be 660 */ + ret = chmod(consumer_data->err_unix_sock_path, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if (ret < 0) { + ERR("Set file permissions failed: %s", consumer_data->err_unix_sock_path); + PERROR("chmod"); + goto error; + } + +error: + return ret; +} + +/* + * Signal handler for the daemon + * + * Simply stop all worker threads, leaving main() return gracefully after + * joining all threads and calling cleanup(). + */ +static void sighandler(int sig, siginfo_t *siginfo, void *arg) +{ + switch (sig) { + case SIGINT: + DBG("SIGINT caught"); + stop_threads(); + break; + case SIGTERM: + DBG("SIGTERM caught"); + stop_threads(); + break; + case SIGUSR1: + CMM_STORE_SHARED(recv_child_signal, 1); + break; + case SIGBUS: + { + int write_ret; + const char msg[] = "Received SIGBUS, aborting program.\n"; + + lttng_ust_handle_sigbus(siginfo->si_addr); + /* + * If ustctl did not catch this signal (triggering a + * siglongjmp), abort the program. Otherwise, the execution + * will resume from the ust-ctl call which caused this error. + * + * The return value is ignored since the program aborts anyhow. + */ + write_ret = write(STDERR_FILENO, msg, sizeof(msg)); + (void) write_ret; + abort(); + } + default: + break; + } +} + +/* + * Setup signal handler for : + * SIGINT, SIGTERM, SIGPIPE + */ +static int set_signal_handler(void) +{ + int ret = 0; + struct sigaction sa; + sigset_t sigset; + + if ((ret = sigemptyset(&sigset)) < 0) { + PERROR("sigemptyset"); + return ret; + } + + sa.sa_mask = sigset; + sa.sa_flags = SA_SIGINFO; + + sa.sa_sigaction = sighandler; + if ((ret = sigaction(SIGTERM, &sa, NULL)) < 0) { + PERROR("sigaction"); + return ret; + } + + if ((ret = sigaction(SIGINT, &sa, NULL)) < 0) { + PERROR("sigaction"); + return ret; + } + + if ((ret = sigaction(SIGUSR1, &sa, NULL)) < 0) { + PERROR("sigaction"); + return ret; + } + + if ((ret = sigaction(SIGBUS, &sa, NULL)) < 0) { + PERROR("sigaction"); + return ret; + } + + sa.sa_flags = 0; + sa.sa_handler = SIG_IGN; + if ((ret = sigaction(SIGPIPE, &sa, NULL)) < 0) { + PERROR("sigaction"); + return ret; + } + + DBG("Signal handler set for SIGTERM, SIGUSR1, SIGPIPE, SIGINT, and SIGBUS"); + + return ret; +} + +/* + * Set open files limit to unlimited. This daemon can open a large number of + * file descriptors in order to consume multiple kernel traces. + */ +static void set_ulimit(void) +{ + int ret; + struct rlimit lim; + + /* The kernel does not allow an infinite limit for open files */ + lim.rlim_cur = 65535; + lim.rlim_max = 65535; + + ret = setrlimit(RLIMIT_NOFILE, &lim); + if (ret < 0) { + PERROR("failed to set open files limit"); + } +} + +static int write_pidfile(void) +{ + return utils_create_pid_file(getpid(), the_config.pid_file_path.value); +} + +static int set_clock_plugin_env(void) +{ + int ret = 0; + char *env_value = NULL; + + if (!the_config.lttng_ust_clock_plugin.value) { + goto end; + } + + ret = asprintf(&env_value, "LTTNG_UST_CLOCK_PLUGIN=%s", + the_config.lttng_ust_clock_plugin.value); + if (ret < 0) { + PERROR("asprintf"); + goto end; + } + + ret = putenv(env_value); + if (ret) { + free(env_value); + PERROR("putenv of LTTNG_UST_CLOCK_PLUGIN"); + goto end; + } + + DBG("Updated LTTNG_UST_CLOCK_PLUGIN environment variable to \"%s\"", + the_config.lttng_ust_clock_plugin.value); +end: + return ret; +} + +static void destroy_all_sessions_and_wait(void) +{ + struct ltt_session *session, *tmp; + struct ltt_session_list *session_list; + + session_list = session_get_list(); + DBG("Initiating destruction of all sessions"); + + if (!session_list) { + return; + } + + session_lock_list(); + /* Initiate the destruction of all sessions. */ + cds_list_for_each_entry_safe(session, tmp, + &session_list->head, list) { + if (!session_get(session)) { + continue; + } + + session_lock(session); + if (session->destroyed) { + goto unlock_session; + } + (void) cmd_stop_trace(session); + (void) cmd_destroy_session( + session, the_notification_thread_handle, NULL); + unlock_session: + session_unlock(session); + session_put(session); + } + session_unlock_list(); + + /* Wait for the destruction of all sessions to complete. */ + DBG("Waiting for the destruction of all sessions to complete"); + session_list_wait_empty(); + DBG("Destruction of all sessions completed"); +} + +static void unregister_all_triggers(void) +{ + enum lttng_error_code ret_code; + enum lttng_trigger_status trigger_status; + struct lttng_triggers *triggers = NULL; + unsigned int trigger_count, i; + const struct lttng_credentials creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(0), + }; + + DBG("Unregistering all triggers"); + + /* + * List all triggers as "root" since we wish to unregister all triggers. + */ + ret_code = notification_thread_command_list_triggers( + the_notification_thread_handle, creds.uid.value, + &triggers); + if (ret_code != LTTNG_OK) { + ERR("Failed to list triggers while unregistering all triggers"); + goto end; + } + + trigger_status = lttng_triggers_get_count(triggers, &trigger_count); + LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + for (i = 0; i < trigger_count; i++) { + uid_t trigger_owner; + const char *trigger_name; + const struct lttng_trigger *trigger = + lttng_triggers_get_at_index(triggers, i); + + LTTNG_ASSERT(trigger); + + trigger_status = lttng_trigger_get_owner_uid( + trigger, &trigger_owner); + LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); + + trigger_status = lttng_trigger_get_name(trigger, &trigger_name); + trigger_name = trigger_status == LTTNG_TRIGGER_STATUS_OK ? + trigger_name : "(anonymous)"; + + DBG("Unregistering trigger: trigger owner uid = %d, trigger name = '%s'", + (int) trigger_owner, trigger_name); + + ret_code = cmd_unregister_trigger(&creds, trigger, + the_notification_thread_handle); + if (ret_code != LTTNG_OK) { + ERR("Failed to unregister trigger: trigger owner uid = %d, trigger name = '%s', error: '%s'", + (int) trigger_owner, trigger_name, + lttng_strerror(-ret_code)); + /* Continue to unregister the remaining triggers. */ + } + } +end: + lttng_triggers_destroy(triggers); +} + +static int run_as_worker_post_fork_cleanup(void *data) +{ + struct sessiond_config *sessiond_config = (struct sessiond_config *) data; + + sessiond_config_fini(sessiond_config); + return 0; +} + +static int launch_run_as_worker(const char *procname) +{ + /* + * Clean-up before forking the run-as worker. Any dynamically + * allocated memory of which the worker is not aware will + * be leaked as the process forks a run-as worker (and performs + * no exec*()). The same would apply to any opened fd. + */ + return run_as_create_worker( + procname, run_as_worker_post_fork_cleanup, &the_config); +} + +static void sessiond_uuid_log(void) +{ + char uuid_str[LTTNG_UUID_STR_LEN]; + + lttng_uuid_to_str(the_sessiond_uuid, uuid_str); + DBG("Starting lttng-sessiond {%s}", uuid_str); +} + +/* + * main + */ +int main(int argc, char **argv) +{ + int ret = 0, retval = 0; + const char *env_app_timeout; + struct lttng_pipe *ust32_channel_monitor_pipe = NULL, + *ust64_channel_monitor_pipe = NULL, + *kernel_channel_monitor_pipe = NULL; + struct lttng_thread *ht_cleanup_thread = NULL; + struct timer_thread_parameters timer_thread_parameters; + /* Rotation thread handle. */ + struct rotation_thread_handle *rotation_thread_handle = NULL; + /* Queue of rotation jobs populated by the sessiond-timer. */ + struct rotation_thread_timer_queue *rotation_timer_queue = NULL; + struct lttng_thread *client_thread = NULL; + struct lttng_thread *notification_thread = NULL; + struct lttng_thread *register_apps_thread = NULL; + enum event_notifier_error_accounting_status event_notifier_error_accounting_status; + + logger_set_thread_name("Main", false); + init_kernel_workarounds(); + + rcu_register_thread(); + + if (set_signal_handler()) { + retval = -1; + goto exit_set_signal_handler; + } + + if (timer_signal_init()) { + retval = -1; + goto exit_set_signal_handler; + } + + the_page_size = sysconf(_SC_PAGE_SIZE); + if (the_page_size < 0) { + PERROR("sysconf _SC_PAGE_SIZE"); + the_page_size = LONG_MAX; + WARN("Fallback page size to %ld", the_page_size); + } + + ret = sessiond_config_init(&the_config); + if (ret) { + retval = -1; + goto exit_set_signal_handler; + } + + /* + * Init config from environment variables. + * Command line option override env configuration per-doc. Do env first. + */ + sessiond_config_apply_env_config(&the_config); + + /* + * Parse arguments and load the daemon configuration file. + * + * We have an exit_options exit path to free memory reserved by + * set_options. This is needed because the rest of sessiond_cleanup() + * depends on ht_cleanup_thread, which depends on lttng_daemonize, which + * depends on set_options. + */ + progname = argv[0]; + if (set_options(argc, argv)) { + retval = -1; + goto exit_options; + } + + /* + * Resolve all paths received as arguments, configuration option, or + * through environment variable as absolute paths. This is necessary + * since daemonizing causes the sessiond's current working directory + * to '/'. + */ + ret = sessiond_config_resolve_paths(&the_config); + if (ret) { + goto exit_options; + } + + /* Apply config. */ + lttng_opt_verbose = the_config.verbose; + lttng_opt_quiet = the_config.quiet; + the_kconsumer_data.err_unix_sock_path = + the_config.kconsumerd_err_unix_sock_path.value; + the_kconsumer_data.cmd_unix_sock_path = + the_config.kconsumerd_cmd_unix_sock_path.value; + the_ustconsumer32_data.err_unix_sock_path = + the_config.consumerd32_err_unix_sock_path.value; + the_ustconsumer32_data.cmd_unix_sock_path = + the_config.consumerd32_cmd_unix_sock_path.value; + the_ustconsumer64_data.err_unix_sock_path = + the_config.consumerd64_err_unix_sock_path.value; + the_ustconsumer64_data.cmd_unix_sock_path = + the_config.consumerd64_cmd_unix_sock_path.value; + set_clock_plugin_env(); + + sessiond_config_log(&the_config); + sessiond_uuid_log(); + + if (opt_print_version) { + print_version(); + retval = 0; + goto exit_options; + } + + if (create_lttng_rundir()) { + retval = -1; + goto exit_options; + } + + /* Abort launch if a session daemon is already running. */ + if (check_existing_daemon()) { + ERR("A session daemon is already running."); + retval = -1; + goto exit_options; + } + + /* Daemonize */ + if (the_config.daemonize || the_config.background) { + int i; + + ret = lttng_daemonize(&the_child_ppid, &recv_child_signal, + !the_config.background); + if (ret < 0) { + retval = -1; + goto exit_options; + } + + /* + * We are in the child. Make sure all other file descriptors are + * closed, in case we are called with more opened file + * descriptors than the standard ones and the lock file. + */ + for (i = 3; i < sysconf(_SC_OPEN_MAX); i++) { + if (i == lockfile_fd) { + continue; + } + (void) close(i); + } + } + + if (launch_run_as_worker(argv[0]) < 0) { + goto exit_create_run_as_worker_cleanup; + } + + /* + * Starting from here, we can create threads. This needs to be after + * lttng_daemonize due to RCU. + */ + + /* + * Initialize the health check subsystem. This call should set the + * appropriate time values. + */ + the_health_sessiond = health_app_create(NR_HEALTH_SESSIOND_TYPES); + if (!the_health_sessiond) { + PERROR("health_app_create error"); + retval = -1; + goto stop_threads; + } + + /* Create thread to clean up RCU hash tables */ + ht_cleanup_thread = launch_ht_cleanup_thread(); + if (!ht_cleanup_thread) { + retval = -1; + goto stop_threads; + } + + /* Create thread quit pipe */ + if (sessiond_init_thread_quit_pipe()) { + retval = -1; + goto stop_threads; + } + + /* Check if daemon is UID = 0 */ + is_root = !getuid(); + if (is_root) { + /* Create global run dir with root access */ + + kernel_channel_monitor_pipe = lttng_pipe_open(0); + if (!kernel_channel_monitor_pipe) { + ERR("Failed to create kernel consumer channel monitor pipe"); + retval = -1; + goto stop_threads; + } + the_kconsumer_data.channel_monitor_pipe = + lttng_pipe_release_writefd( + kernel_channel_monitor_pipe); + if (the_kconsumer_data.channel_monitor_pipe < 0) { + retval = -1; + goto stop_threads; + } + } + + /* Set consumer initial state */ + the_kernel_consumerd_state = CONSUMER_STOPPED; + the_ust_consumerd_state = CONSUMER_STOPPED; + + ust32_channel_monitor_pipe = lttng_pipe_open(0); + if (!ust32_channel_monitor_pipe) { + ERR("Failed to create 32-bit user space consumer channel monitor pipe"); + retval = -1; + goto stop_threads; + } + the_ustconsumer32_data.channel_monitor_pipe = + lttng_pipe_release_writefd(ust32_channel_monitor_pipe); + if (the_ustconsumer32_data.channel_monitor_pipe < 0) { + retval = -1; + goto stop_threads; + } + + /* + * The rotation_thread_timer_queue structure is shared between the + * sessiond timer thread and the rotation thread. The main thread keeps + * its ownership and destroys it when both threads have been joined. + */ + rotation_timer_queue = rotation_thread_timer_queue_create(); + if (!rotation_timer_queue) { + retval = -1; + goto stop_threads; + } + timer_thread_parameters.rotation_thread_job_queue = + rotation_timer_queue; + + ust64_channel_monitor_pipe = lttng_pipe_open(0); + if (!ust64_channel_monitor_pipe) { + ERR("Failed to create 64-bit user space consumer channel monitor pipe"); + retval = -1; + goto stop_threads; + } + the_ustconsumer64_data.channel_monitor_pipe = + lttng_pipe_release_writefd(ust64_channel_monitor_pipe); + if (the_ustconsumer64_data.channel_monitor_pipe < 0) { + retval = -1; + goto stop_threads; + } + + /* + * Init UST app hash table. Alloc hash table before this point since + * cleanup() can get called after that point. + */ + if (ust_app_ht_alloc()) { + ERR("Failed to allocate UST app hash table"); + retval = -1; + goto stop_threads; + } + + event_notifier_error_accounting_status = event_notifier_error_accounting_init( + the_config.event_notifier_buffer_size_kernel, + the_config.event_notifier_buffer_size_userspace); + if (event_notifier_error_accounting_status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + ERR("Failed to initialize event notifier error accounting system"); + retval = -1; + goto stop_threads; + } + + /* + * Initialize agent app hash table. We allocate the hash table here + * since cleanup() can get called after this point. + */ + if (agent_app_ht_alloc()) { + ERR("Failed to allocate Agent app hash table"); + retval = -1; + goto stop_threads; + } + + if (agent_by_event_notifier_domain_ht_create()) { + ERR("Failed to allocate per-event notifier domain 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 + * those paths *before* trying to set the kernel consumer sockets and init + * kernel tracer. + */ + if (is_root) { + if (set_consumer_sockets(&the_kconsumer_data)) { + retval = -1; + goto stop_threads; + } + + /* Setup kernel tracer */ + if (!the_config.no_kernel) { + init_kernel_tracer(); + } + + /* Set ulimit for open files */ + set_ulimit(); + } + /* init lttng_fd tracking must be done after set_ulimit. */ + lttng_fd_init(); + + if (set_consumer_sockets(&the_ustconsumer64_data)) { + retval = -1; + goto stop_threads; + } + + if (set_consumer_sockets(&the_ustconsumer32_data)) { + retval = -1; + goto stop_threads; + } + + /* Get parent pid if -S, --sig-parent is specified. */ + if (the_config.sig_parent) { + the_ppid = getppid(); + } + + /* Setup the kernel pipe for waking up the kernel thread */ + if (is_root && !the_config.no_kernel) { + if (utils_create_pipe_cloexec(the_kernel_poll_pipe)) { + retval = -1; + goto stop_threads; + } + } + + /* Setup the thread apps communication pipe. */ + if (utils_create_pipe_cloexec(apps_cmd_pipe)) { + retval = -1; + goto stop_threads; + } + + /* Setup the thread apps notify communication pipe. */ + if (utils_create_pipe_cloexec(apps_cmd_notify_pipe)) { + retval = -1; + goto stop_threads; + } + + /* Initialize global buffer per UID and PID registry. */ + buffer_reg_init_uid_registry(); + buffer_reg_init_pid_registry(); + + /* Init UST command queue. */ + cds_wfcq_init(&ust_cmd_queue.head, &ust_cmd_queue.tail); + + cmd_init(); + + /* Check for the application socket timeout env variable. */ + env_app_timeout = getenv(DEFAULT_APP_SOCKET_TIMEOUT_ENV); + if (env_app_timeout) { + the_config.app_socket_timeout = atoi(env_app_timeout); + } else { + the_config.app_socket_timeout = DEFAULT_APP_SOCKET_RW_TIMEOUT; + } + + ret = write_pidfile(); + if (ret) { + ERR("Error in write_pidfile"); + retval = -1; + goto stop_threads; + } + + /* Initialize communication library */ + lttcomm_init(); + /* Initialize TCP timeout values */ + lttcomm_inet_init(); + + /* Create health-check thread. */ + if (!launch_health_management_thread()) { + retval = -1; + goto stop_threads; + } + + /* notification_thread_data acquires the pipes' read side. */ + the_notification_thread_handle = notification_thread_handle_create( + ust32_channel_monitor_pipe, ust64_channel_monitor_pipe, + kernel_channel_monitor_pipe); + if (!the_notification_thread_handle) { + retval = -1; + ERR("Failed to create notification thread shared data"); + goto stop_threads; + } + + /* Create notification thread. */ + notification_thread = launch_notification_thread( + the_notification_thread_handle); + if (!notification_thread) { + retval = -1; + goto stop_threads; + } + + /* Create timer thread. */ + if (!launch_timer_thread(&timer_thread_parameters)) { + retval = -1; + goto stop_threads; + } + + /* rotation_thread_data acquires the pipes' read side. */ + rotation_thread_handle = rotation_thread_handle_create( + rotation_timer_queue, the_notification_thread_handle); + if (!rotation_thread_handle) { + retval = -1; + ERR("Failed to create rotation thread shared data"); + stop_threads(); + goto stop_threads; + } + + /* Create rotation thread. */ + if (!launch_rotation_thread(rotation_thread_handle)) { + retval = -1; + goto stop_threads; + } + + /* Create thread to manage the client socket */ + client_thread = launch_client_thread(); + if (!client_thread) { + retval = -1; + goto stop_threads; + } + + /* Set credentials of the client socket and rundir */ + if (is_root && set_permissions(the_config.rundir.value)) { + retval = -1; + goto stop_threads; + } + + if (!launch_ust_dispatch_thread(&ust_cmd_queue, apps_cmd_pipe[1], + apps_cmd_notify_pipe[1])) { + retval = -1; + goto stop_threads; + } + + /* Create thread to manage application registration. */ + register_apps_thread = launch_application_registration_thread( + &ust_cmd_queue); + if (!register_apps_thread) { + retval = -1; + goto stop_threads; + } + + /* Create thread to manage application socket */ + if (!launch_application_management_thread(apps_cmd_pipe[0])) { + retval = -1; + goto stop_threads; + } + + /* Create thread to manage application notify socket */ + if (!launch_application_notification_thread(apps_cmd_notify_pipe[0])) { + retval = -1; + goto stop_threads; + } + + /* Create agent management thread. */ + if (!launch_agent_management_thread()) { + retval = -1; + goto stop_threads; + } + + /* Don't start this thread if kernel tracing is not requested nor root */ + if (is_root && !the_config.no_kernel) { + /* Create kernel thread to manage kernel event */ + if (!launch_kernel_management_thread(the_kernel_poll_pipe[0])) { + retval = -1; + goto stop_threads; + } + + if (kernel_get_notification_fd() >= 0) { + ret = notification_thread_command_add_tracer_event_source( + the_notification_thread_handle, + kernel_get_notification_fd(), + LTTNG_DOMAIN_KERNEL); + if (ret != LTTNG_OK) { + ERR("Failed to add kernel trigger event source to notification thread"); + retval = -1; + goto stop_threads; + } + } + } + + /* Load sessions. */ + ret = config_load_session( + the_config.load_session_path.value, NULL, 1, 1, NULL); + if (ret) { + ERR("Session load failed: %s", error_get_str(ret)); + retval = -1; + goto stop_threads; + } + + /* Initialization completed. */ + sessiond_signal_parents(); + + /* + * This is where we start awaiting program completion (e.g. through + * signal that asks threads to teardown). + */ + + /* Initiate teardown once activity occurs on the quit pipe. */ + sessiond_wait_for_quit_pipe(-1); + +stop_threads: + + /* + * Ensure that the client thread is no longer accepting new commands, + * which could cause new sessions to be created. + */ + if (client_thread) { + lttng_thread_shutdown(client_thread); + lttng_thread_put(client_thread); + } + + destroy_all_sessions_and_wait(); + + /* + * At this point no new trigger can be registered (no sessions are + * running/rotating) and clients can't connect to the session daemon + * anymore. Unregister all triggers. + */ + unregister_all_triggers(); + + if (register_apps_thread) { + lttng_thread_shutdown(register_apps_thread); + lttng_thread_put(register_apps_thread); + } + lttng_thread_list_shutdown_orphans(); + + /* + * Wait for all pending call_rcu work to complete before tearing + * down data structures. call_rcu worker may be trying to + * perform lookups in those structures. + */ + rcu_barrier(); + /* + * sessiond_cleanup() is called when no other thread is running, except + * the ht_cleanup thread, which is needed to destroy the hash tables. + */ + rcu_thread_online(); + sessiond_cleanup(); + + /* + * Wait for all pending call_rcu work to complete before shutting down + * the notification thread. This call_rcu work includes shutting down + * UST apps and event notifier pipes. + */ + rcu_barrier(); + + if (notification_thread) { + lttng_thread_shutdown(notification_thread); + lttng_thread_put(notification_thread); + } + + /* + * Error accounting teardown has to be done after the teardown of all + * event notifier pipes to ensure that no tracer may try to use the + * error accounting facilities. + */ + event_notifier_error_accounting_fini(); + + /* + * Unloading the kernel modules needs to be done after all kernel + * ressources have been released. In our case, this includes the + * notification fd, the event notifier group fd, error accounting fd, + * all event and event notifier fds, etc. + * + * In short, at this point, we need to have called close() on all fds + * received from the kernel tracer. + */ + if (is_root && !the_config.no_kernel) { + DBG("Unloading kernel modules"); + modprobe_remove_lttng_all(); + } + + /* + * Ensure all prior call_rcu are done. call_rcu callbacks may push + * hash tables to the ht_cleanup thread. Therefore, we ensure that + * the queue is empty before shutting down the clean-up thread. + */ + rcu_barrier(); + + if (ht_cleanup_thread) { + lttng_thread_shutdown(ht_cleanup_thread); + lttng_thread_put(ht_cleanup_thread); + } + + rcu_thread_offline(); + rcu_unregister_thread(); + + if (rotation_thread_handle) { + rotation_thread_handle_destroy(rotation_thread_handle); + } + + /* + * After the rotation and timer thread have quit, we can safely destroy + * the rotation_timer_queue. + */ + rotation_thread_timer_queue_destroy(rotation_timer_queue); + /* + * The teardown of the notification system is performed after the + * session daemon's teardown in order to allow it to be notified + * of the active session and channels at the moment of the teardown. + */ + if (the_notification_thread_handle) { + notification_thread_handle_destroy( + the_notification_thread_handle); + } + lttng_pipe_destroy(ust32_channel_monitor_pipe); + lttng_pipe_destroy(ust64_channel_monitor_pipe); + lttng_pipe_destroy(kernel_channel_monitor_pipe); + + if (the_health_sessiond) { + health_app_destroy(the_health_sessiond); + } +exit_create_run_as_worker_cleanup: +exit_options: + sessiond_cleanup_lock_file(); + sessiond_cleanup_options(); + +exit_set_signal_handler: + if (!retval) { + exit(EXIT_SUCCESS); + } else { + exit(EXIT_FAILURE); + } +} diff --git a/src/bin/lttng-sessiond/manage-apps.c b/src/bin/lttng-sessiond/manage-apps.c deleted file mode 100644 index e2f3ed26a..000000000 --- a/src/bin/lttng-sessiond/manage-apps.c +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "manage-apps.h" -#include "testpoint.h" -#include "health-sessiond.h" -#include "utils.h" -#include "thread.h" - -struct thread_notifiers { - struct lttng_pipe *quit_pipe; - int apps_cmd_pipe_read_fd; -}; - -static void cleanup_application_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - - lttng_pipe_destroy(notifiers->quit_pipe); - free(notifiers); -} - -/* - * This thread receives application command sockets (FDs) on the - * apps_cmd_pipe and waits (polls) on them until they are closed - * or an error occurs. - * - * At that point, it flushes the data (tracing and metadata) associated - * with this application and tears down ust app sessions and other - * associated data structures through ust_app_unregister(). - * - * Note that this thread never sends commands to the applications - * through the command sockets; it merely listens for hang-ups - * and errors on those sockets and cleans-up as they occur. - */ -static void *thread_application_management(void *data) -{ - int i, ret, pollfd, err = -1; - ssize_t size_ret; - uint32_t revents, nb_fd; - struct lttng_poll_event events; - struct thread_notifiers *notifiers = data; - const int quit_pipe_read_fd = lttng_pipe_get_readfd( - notifiers->quit_pipe); - - DBG("[thread] Manage application started"); - - rcu_register_thread(); - rcu_thread_online(); - - health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_APP_MANAGE); - - if (testpoint(sessiond_thread_manage_apps)) { - goto error_testpoint; - } - - health_code_update(); - - ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); - if (ret < 0) { - goto error_poll_create; - } - - ret = lttng_poll_add(&events, notifiers->apps_cmd_pipe_read_fd, - LPOLLIN | LPOLLRDHUP); - if (ret < 0) { - goto error; - } - - ret = lttng_poll_add(&events, quit_pipe_read_fd, LPOLLIN | LPOLLERR); - if (ret < 0) { - goto error; - } - - if (testpoint(sessiond_thread_manage_apps_before_loop)) { - goto error; - } - - health_code_update(); - - while (1) { - DBG("Apps thread polling"); - - /* Inifinite blocking call, waiting for transmission */ - restart: - health_poll_entry(); - ret = lttng_poll_wait(&events, -1); - DBG("Apps thread return from poll on %d fds", - LTTNG_POLL_GETNB(&events)); - health_poll_exit(); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - goto restart; - } - goto error; - } - - nb_fd = ret; - - for (i = 0; i < nb_fd; i++) { - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - health_code_update(); - - if (pollfd == quit_pipe_read_fd) { - err = 0; - goto exit; - } else if (pollfd == notifiers->apps_cmd_pipe_read_fd) { - /* Inspect the apps cmd pipe */ - if (revents & LPOLLIN) { - int sock; - - /* Empty pipe */ - size_ret = lttng_read( - notifiers->apps_cmd_pipe_read_fd, - &sock, sizeof(sock)); - if (size_ret < sizeof(sock)) { - PERROR("read apps cmd pipe"); - goto error; - } - - health_code_update(); - - /* - * Since this is a command socket (write then read), - * we only monitor the error events of the socket. - */ - ret = lttng_poll_add(&events, sock, - LPOLLERR | LPOLLHUP | LPOLLRDHUP); - if (ret < 0) { - goto error; - } - - DBG("Apps with sock %d added to poll set", sock); - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ERR("Apps command pipe error"); - goto error; - } else { - ERR("Unknown poll events %u for sock %d", revents, pollfd); - goto error; - } - } else { - /* - * At this point, we know that a registered application made - * the event at poll_wait. - */ - if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - /* Removing from the poll set */ - ret = lttng_poll_del(&events, pollfd); - if (ret < 0) { - goto error; - } - - /* Socket closed on remote end. */ - ust_app_unregister(pollfd); - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - } - - health_code_update(); - } - } - -exit: -error: - lttng_poll_clean(&events); -error_poll_create: -error_testpoint: - - /* - * We don't clean the UST app hash table here since already registered - * applications can still be controlled so let them be until the session - * daemon dies or the applications stop. - */ - - if (err) { - health_error(); - ERR("Health error occurred in %s", __func__); - } - health_unregister(the_health_sessiond); - DBG("Application communication apps thread cleanup complete"); - rcu_thread_offline(); - rcu_unregister_thread(); - return NULL; -} - -static bool shutdown_application_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); - - return notify_thread_pipe(write_fd) == 1; -} - -bool launch_application_management_thread(int apps_cmd_pipe_read_fd) -{ - struct lttng_pipe *quit_pipe; - struct thread_notifiers *notifiers = NULL; - struct lttng_thread *thread; - - notifiers = zmalloc(sizeof(*notifiers)); - if (!notifiers) { - goto error_alloc; - } - quit_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!quit_pipe) { - goto error; - } - notifiers->quit_pipe = quit_pipe; - notifiers->apps_cmd_pipe_read_fd = apps_cmd_pipe_read_fd; - - thread = lttng_thread_create("UST application management", - thread_application_management, - shutdown_application_management_thread, - cleanup_application_management_thread, - notifiers); - if (!thread) { - goto error; - } - - lttng_thread_put(thread); - return true; -error: - cleanup_application_management_thread(notifiers); -error_alloc: - return false; -} diff --git a/src/bin/lttng-sessiond/manage-apps.cpp b/src/bin/lttng-sessiond/manage-apps.cpp new file mode 100644 index 000000000..cff56cbad --- /dev/null +++ b/src/bin/lttng-sessiond/manage-apps.cpp @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "manage-apps.h" +#include "testpoint.h" +#include "health-sessiond.h" +#include "utils.h" +#include "thread.h" + +struct thread_notifiers { + struct lttng_pipe *quit_pipe; + int apps_cmd_pipe_read_fd; +}; + +static void cleanup_application_management_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + + lttng_pipe_destroy(notifiers->quit_pipe); + free(notifiers); +} + +/* + * This thread receives application command sockets (FDs) on the + * apps_cmd_pipe and waits (polls) on them until they are closed + * or an error occurs. + * + * At that point, it flushes the data (tracing and metadata) associated + * with this application and tears down ust app sessions and other + * associated data structures through ust_app_unregister(). + * + * Note that this thread never sends commands to the applications + * through the command sockets; it merely listens for hang-ups + * and errors on those sockets and cleans-up as they occur. + */ +static void *thread_application_management(void *data) +{ + int i, ret, pollfd, err = -1; + ssize_t size_ret; + uint32_t revents, nb_fd; + struct lttng_poll_event events; + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int quit_pipe_read_fd = lttng_pipe_get_readfd( + notifiers->quit_pipe); + + DBG("[thread] Manage application started"); + + rcu_register_thread(); + rcu_thread_online(); + + health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_APP_MANAGE); + + if (testpoint(sessiond_thread_manage_apps)) { + goto error_testpoint; + } + + health_code_update(); + + ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); + if (ret < 0) { + goto error_poll_create; + } + + ret = lttng_poll_add(&events, notifiers->apps_cmd_pipe_read_fd, + LPOLLIN | LPOLLRDHUP); + if (ret < 0) { + goto error; + } + + ret = lttng_poll_add(&events, quit_pipe_read_fd, LPOLLIN | LPOLLERR); + if (ret < 0) { + goto error; + } + + if (testpoint(sessiond_thread_manage_apps_before_loop)) { + goto error; + } + + health_code_update(); + + while (1) { + DBG("Apps thread polling"); + + /* Inifinite blocking call, waiting for transmission */ + restart: + health_poll_entry(); + ret = lttng_poll_wait(&events, -1); + DBG("Apps thread return from poll on %d fds", + LTTNG_POLL_GETNB(&events)); + health_poll_exit(); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + goto restart; + } + goto error; + } + + nb_fd = ret; + + for (i = 0; i < nb_fd; i++) { + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + health_code_update(); + + if (pollfd == quit_pipe_read_fd) { + err = 0; + goto exit; + } else if (pollfd == notifiers->apps_cmd_pipe_read_fd) { + /* Inspect the apps cmd pipe */ + if (revents & LPOLLIN) { + int sock; + + /* Empty pipe */ + size_ret = lttng_read( + notifiers->apps_cmd_pipe_read_fd, + &sock, sizeof(sock)); + if (size_ret < sizeof(sock)) { + PERROR("read apps cmd pipe"); + goto error; + } + + health_code_update(); + + /* + * Since this is a command socket (write then read), + * we only monitor the error events of the socket. + */ + ret = lttng_poll_add(&events, sock, + LPOLLERR | LPOLLHUP | LPOLLRDHUP); + if (ret < 0) { + goto error; + } + + DBG("Apps with sock %d added to poll set", sock); + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ERR("Apps command pipe error"); + goto error; + } else { + ERR("Unknown poll events %u for sock %d", revents, pollfd); + goto error; + } + } else { + /* + * At this point, we know that a registered application made + * the event at poll_wait. + */ + if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + /* Removing from the poll set */ + ret = lttng_poll_del(&events, pollfd); + if (ret < 0) { + goto error; + } + + /* Socket closed on remote end. */ + ust_app_unregister(pollfd); + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + } + + health_code_update(); + } + } + +exit: +error: + lttng_poll_clean(&events); +error_poll_create: +error_testpoint: + + /* + * We don't clean the UST app hash table here since already registered + * applications can still be controlled so let them be until the session + * daemon dies or the applications stop. + */ + + if (err) { + health_error(); + ERR("Health error occurred in %s", __func__); + } + health_unregister(the_health_sessiond); + DBG("Application communication apps thread cleanup complete"); + rcu_thread_offline(); + rcu_unregister_thread(); + return NULL; +} + +static bool shutdown_application_management_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); + + return notify_thread_pipe(write_fd) == 1; +} + +bool launch_application_management_thread(int apps_cmd_pipe_read_fd) +{ + struct lttng_pipe *quit_pipe; + struct thread_notifiers *notifiers = NULL; + struct lttng_thread *thread; + + notifiers = (thread_notifiers *) zmalloc(sizeof(*notifiers)); + if (!notifiers) { + goto error_alloc; + } + quit_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!quit_pipe) { + goto error; + } + notifiers->quit_pipe = quit_pipe; + notifiers->apps_cmd_pipe_read_fd = apps_cmd_pipe_read_fd; + + thread = lttng_thread_create("UST application management", + thread_application_management, + shutdown_application_management_thread, + cleanup_application_management_thread, + notifiers); + if (!thread) { + goto error; + } + + lttng_thread_put(thread); + return true; +error: + cleanup_application_management_thread(notifiers); +error_alloc: + return false; +} diff --git a/src/bin/lttng-sessiond/manage-consumer.c b/src/bin/lttng-sessiond/manage-consumer.c deleted file mode 100644 index 719d42b18..000000000 --- a/src/bin/lttng-sessiond/manage-consumer.c +++ /dev/null @@ -1,471 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include - -#include -#include - -#include "manage-consumer.h" -#include "testpoint.h" -#include "health-sessiond.h" -#include "utils.h" -#include "thread.h" -#include "ust-consumer.h" - -struct thread_notifiers { - struct lttng_pipe *quit_pipe; - struct consumer_data *consumer_data; - sem_t ready; - int initialization_result; -}; - -static void mark_thread_as_ready(struct thread_notifiers *notifiers) -{ - DBG("Marking consumer management thread as ready"); - notifiers->initialization_result = 0; - sem_post(¬ifiers->ready); -} - -static void mark_thread_intialization_as_failed( - struct thread_notifiers *notifiers) -{ - ERR("Consumer management thread entering error state"); - notifiers->initialization_result = -1; - sem_post(¬ifiers->ready); -} - -static void wait_until_thread_is_ready(struct thread_notifiers *notifiers) -{ - DBG("Waiting for consumer management thread to be ready"); - sem_wait(¬ifiers->ready); - DBG("Consumer management thread is ready"); -} - -/* - * This thread manage the consumer error sent back to the session daemon. - */ -static void *thread_consumer_management(void *data) -{ - int sock = -1, i, ret, pollfd, err = -1, should_quit = 0; - uint32_t revents, nb_fd; - enum lttcomm_return_code code; - struct lttng_poll_event events; - struct thread_notifiers *notifiers = data; - struct consumer_data *consumer_data = notifiers->consumer_data; - const int quit_pipe_read_fd = lttng_pipe_get_readfd(notifiers->quit_pipe); - struct consumer_socket *cmd_socket_wrapper = NULL; - - DBG("[thread] Manage consumer started"); - - rcu_register_thread(); - rcu_thread_online(); - - health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_CONSUMER); - - health_code_update(); - - /* - * Pass 3 as size here for the thread quit pipe, consumerd_err_sock and the - * metadata_sock. Nothing more will be added to this poll set. - */ - ret = lttng_poll_create(&events, 3, LTTNG_CLOEXEC); - if (ret < 0) { - mark_thread_intialization_as_failed(notifiers); - goto error_poll; - } - - ret = lttng_poll_add(&events, quit_pipe_read_fd, LPOLLIN | LPOLLERR); - if (ret < 0) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - /* - * The error socket here is already in a listening state which was done - * just before spawning this thread to avoid a race between the consumer - * daemon exec trying to connect and the listen() call. - */ - ret = lttng_poll_add(&events, consumer_data->err_sock, LPOLLIN | LPOLLRDHUP); - if (ret < 0) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - health_code_update(); - - /* Infinite blocking call, waiting for transmission */ - health_poll_entry(); - - if (testpoint(sessiond_thread_manage_consumer)) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - ret = lttng_poll_wait(&events, -1); - health_poll_exit(); - if (ret < 0) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - nb_fd = ret; - - for (i = 0; i < nb_fd; i++) { - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - health_code_update(); - - /* Thread quit pipe has been closed. Killing thread. */ - if (pollfd == quit_pipe_read_fd) { - err = 0; - mark_thread_intialization_as_failed(notifiers); - goto exit; - } else if (pollfd == consumer_data->err_sock) { - /* Event on the registration socket */ - if (revents & LPOLLIN) { - continue; - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ERR("consumer err socket poll error"); - mark_thread_intialization_as_failed(notifiers); - goto error; - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - mark_thread_intialization_as_failed(notifiers); - goto error; - } - } - } - - sock = lttcomm_accept_unix_sock(consumer_data->err_sock); - if (sock < 0) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - /* - * Set the CLOEXEC flag. Return code is useless because either way, the - * show must go on. - */ - (void) utils_set_fd_cloexec(sock); - - health_code_update(); - - DBG2("Receiving code from consumer err_sock"); - - /* Getting status code from kconsumerd */ - ret = lttcomm_recv_unix_sock(sock, &code, - sizeof(enum lttcomm_return_code)); - if (ret <= 0) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - health_code_update(); - if (code != LTTCOMM_CONSUMERD_COMMAND_SOCK_READY) { - ERR("consumer error when waiting for SOCK_READY : %s", - lttcomm_get_readable_code(-code)); - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - /* Connect both command and metadata sockets. */ - consumer_data->cmd_sock = - lttcomm_connect_unix_sock( - consumer_data->cmd_unix_sock_path); - consumer_data->metadata_fd = - lttcomm_connect_unix_sock( - consumer_data->cmd_unix_sock_path); - if (consumer_data->cmd_sock < 0 || consumer_data->metadata_fd < 0) { - PERROR("consumer connect cmd socket"); - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - consumer_data->metadata_sock.fd_ptr = &consumer_data->metadata_fd; - - /* Create metadata socket lock. */ - consumer_data->metadata_sock.lock = zmalloc(sizeof(pthread_mutex_t)); - if (consumer_data->metadata_sock.lock == NULL) { - PERROR("zmalloc pthread mutex"); - mark_thread_intialization_as_failed(notifiers); - goto error; - } - pthread_mutex_init(consumer_data->metadata_sock.lock, NULL); - - DBG("Consumer command socket ready (fd: %d)", consumer_data->cmd_sock); - DBG("Consumer metadata socket ready (fd: %d)", - consumer_data->metadata_fd); - - /* - * Remove the consumerd error sock since we've established a connection. - */ - ret = lttng_poll_del(&events, consumer_data->err_sock); - if (ret < 0) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - /* Add new accepted error socket. */ - ret = lttng_poll_add(&events, sock, LPOLLIN | LPOLLRDHUP); - if (ret < 0) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - /* Add metadata socket that is successfully connected. */ - ret = lttng_poll_add(&events, consumer_data->metadata_fd, - LPOLLIN | LPOLLRDHUP); - if (ret < 0) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - health_code_update(); - - /* - * Transfer the write-end of the channel monitoring pipe to the consumer - * by issuing a SET_CHANNEL_MONITOR_PIPE command. - */ - cmd_socket_wrapper = consumer_allocate_socket(&consumer_data->cmd_sock); - if (!cmd_socket_wrapper) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - cmd_socket_wrapper->lock = &consumer_data->lock; - - pthread_mutex_lock(cmd_socket_wrapper->lock); - ret = consumer_init(cmd_socket_wrapper, the_sessiond_uuid); - if (ret) { - ERR("Failed to send sessiond uuid to consumer daemon"); - mark_thread_intialization_as_failed(notifiers); - pthread_mutex_unlock(cmd_socket_wrapper->lock); - goto error; - } - pthread_mutex_unlock(cmd_socket_wrapper->lock); - - ret = consumer_send_channel_monitor_pipe(cmd_socket_wrapper, - consumer_data->channel_monitor_pipe); - if (ret) { - mark_thread_intialization_as_failed(notifiers); - goto error; - } - - /* Discard the socket wrapper as it is no longer needed. */ - consumer_destroy_socket(cmd_socket_wrapper); - cmd_socket_wrapper = NULL; - - /* The thread is completely initialized, signal that it is ready. */ - mark_thread_as_ready(notifiers); - - /* Infinite blocking call, waiting for transmission */ - while (1) { - health_code_update(); - - /* Exit the thread because the thread quit pipe has been triggered. */ - if (should_quit) { - /* Not a health error. */ - err = 0; - goto exit; - } - - health_poll_entry(); - ret = lttng_poll_wait(&events, -1); - health_poll_exit(); - if (ret < 0) { - goto error; - } - - nb_fd = ret; - - for (i = 0; i < nb_fd; i++) { - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - health_code_update(); - - /* - * Thread quit pipe has been triggered, flag that we should stop - * but continue the current loop to handle potential data from - * consumer. - */ - if (pollfd == quit_pipe_read_fd) { - should_quit = 1; - } else if (pollfd == sock) { - /* Event on the consumerd socket */ - if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP) - && !(revents & LPOLLIN)) { - ERR("consumer err socket second poll error"); - goto error; - } - health_code_update(); - /* Wait for any kconsumerd error */ - ret = lttcomm_recv_unix_sock(sock, &code, - sizeof(enum lttcomm_return_code)); - if (ret <= 0) { - ERR("consumer closed the command socket"); - goto error; - } - - ERR("consumer return code : %s", - lttcomm_get_readable_code(-code)); - - goto exit; - } else if (pollfd == consumer_data->metadata_fd) { - if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP) - && !(revents & LPOLLIN)) { - ERR("consumer err metadata socket second poll error"); - goto error; - } - /* UST metadata requests */ - ret = ust_consumer_metadata_request( - &consumer_data->metadata_sock); - if (ret < 0) { - ERR("Handling metadata request"); - goto error; - } - } - /* No need for an else branch all FDs are tested prior. */ - } - health_code_update(); - } - -exit: -error: - /* - * We lock here because we are about to close the sockets and some other - * thread might be using them so get exclusive access which will abort all - * other consumer command by other threads. - */ - pthread_mutex_lock(&consumer_data->lock); - - /* Immediately set the consumerd state to stopped */ - if (consumer_data->type == LTTNG_CONSUMER_KERNEL) { - uatomic_set(&the_kernel_consumerd_state, CONSUMER_ERROR); - } else if (consumer_data->type == LTTNG_CONSUMER64_UST || - consumer_data->type == LTTNG_CONSUMER32_UST) { - uatomic_set(&the_ust_consumerd_state, CONSUMER_ERROR); - } else { - /* Code flow error... */ - abort(); - } - - if (consumer_data->err_sock >= 0) { - ret = close(consumer_data->err_sock); - if (ret) { - PERROR("close"); - } - consumer_data->err_sock = -1; - } - if (consumer_data->cmd_sock >= 0) { - ret = close(consumer_data->cmd_sock); - if (ret) { - PERROR("close"); - } - consumer_data->cmd_sock = -1; - } - if (consumer_data->metadata_sock.fd_ptr && - *consumer_data->metadata_sock.fd_ptr >= 0) { - ret = close(*consumer_data->metadata_sock.fd_ptr); - if (ret) { - PERROR("close"); - } - } - if (sock >= 0) { - ret = close(sock); - if (ret) { - PERROR("close"); - } - } - - unlink(consumer_data->err_unix_sock_path); - unlink(consumer_data->cmd_unix_sock_path); - pthread_mutex_unlock(&consumer_data->lock); - - /* Cleanup metadata socket mutex. */ - if (consumer_data->metadata_sock.lock) { - pthread_mutex_destroy(consumer_data->metadata_sock.lock); - free(consumer_data->metadata_sock.lock); - } - lttng_poll_clean(&events); - - if (cmd_socket_wrapper) { - consumer_destroy_socket(cmd_socket_wrapper); - } -error_poll: - if (err) { - health_error(); - ERR("Health error occurred in %s", __func__); - } - health_unregister(the_health_sessiond); - DBG("consumer thread cleanup completed"); - - rcu_thread_offline(); - rcu_unregister_thread(); - - return NULL; -} - -static bool shutdown_consumer_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); - - return notify_thread_pipe(write_fd) == 1; -} - -static void cleanup_consumer_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - - lttng_pipe_destroy(notifiers->quit_pipe); - free(notifiers); -} - -bool launch_consumer_management_thread(struct consumer_data *consumer_data) -{ - struct lttng_pipe *quit_pipe; - struct thread_notifiers *notifiers = NULL; - struct lttng_thread *thread; - - notifiers = zmalloc(sizeof(*notifiers)); - if (!notifiers) { - goto error_alloc; - } - - quit_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!quit_pipe) { - goto error; - } - notifiers->quit_pipe = quit_pipe; - notifiers->consumer_data = consumer_data; - sem_init(¬ifiers->ready, 0, 0); - - thread = lttng_thread_create("Consumer management", - thread_consumer_management, - shutdown_consumer_management_thread, - cleanup_consumer_management_thread, - notifiers); - if (!thread) { - goto error; - } - wait_until_thread_is_ready(notifiers); - lttng_thread_put(thread); - if (notifiers->initialization_result) { - return false; - } - return true; -error: - cleanup_consumer_management_thread(notifiers); -error_alloc: - return false; -} diff --git a/src/bin/lttng-sessiond/manage-consumer.cpp b/src/bin/lttng-sessiond/manage-consumer.cpp new file mode 100644 index 000000000..d7df6ce8f --- /dev/null +++ b/src/bin/lttng-sessiond/manage-consumer.cpp @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include + +#include +#include + +#include "manage-consumer.h" +#include "testpoint.h" +#include "health-sessiond.h" +#include "utils.h" +#include "thread.h" +#include "ust-consumer.h" + +struct thread_notifiers { + struct lttng_pipe *quit_pipe; + struct consumer_data *consumer_data; + sem_t ready; + int initialization_result; +}; + +static void mark_thread_as_ready(struct thread_notifiers *notifiers) +{ + DBG("Marking consumer management thread as ready"); + notifiers->initialization_result = 0; + sem_post(¬ifiers->ready); +} + +static void mark_thread_intialization_as_failed( + struct thread_notifiers *notifiers) +{ + ERR("Consumer management thread entering error state"); + notifiers->initialization_result = -1; + sem_post(¬ifiers->ready); +} + +static void wait_until_thread_is_ready(struct thread_notifiers *notifiers) +{ + DBG("Waiting for consumer management thread to be ready"); + sem_wait(¬ifiers->ready); + DBG("Consumer management thread is ready"); +} + +/* + * This thread manage the consumer error sent back to the session daemon. + */ +static void *thread_consumer_management(void *data) +{ + int sock = -1, i, ret, pollfd, err = -1, should_quit = 0; + uint32_t revents, nb_fd; + enum lttcomm_return_code code; + struct lttng_poll_event events; + struct thread_notifiers *notifiers = (thread_notifiers *) data; + struct consumer_data *consumer_data = notifiers->consumer_data; + const int quit_pipe_read_fd = lttng_pipe_get_readfd(notifiers->quit_pipe); + struct consumer_socket *cmd_socket_wrapper = NULL; + + DBG("[thread] Manage consumer started"); + + rcu_register_thread(); + rcu_thread_online(); + + health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_CONSUMER); + + health_code_update(); + + /* + * Pass 3 as size here for the thread quit pipe, consumerd_err_sock and the + * metadata_sock. Nothing more will be added to this poll set. + */ + ret = lttng_poll_create(&events, 3, LTTNG_CLOEXEC); + if (ret < 0) { + mark_thread_intialization_as_failed(notifiers); + goto error_poll; + } + + ret = lttng_poll_add(&events, quit_pipe_read_fd, LPOLLIN | LPOLLERR); + if (ret < 0) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + /* + * The error socket here is already in a listening state which was done + * just before spawning this thread to avoid a race between the consumer + * daemon exec trying to connect and the listen() call. + */ + ret = lttng_poll_add(&events, consumer_data->err_sock, LPOLLIN | LPOLLRDHUP); + if (ret < 0) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + health_code_update(); + + /* Infinite blocking call, waiting for transmission */ + health_poll_entry(); + + if (testpoint(sessiond_thread_manage_consumer)) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + ret = lttng_poll_wait(&events, -1); + health_poll_exit(); + if (ret < 0) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + nb_fd = ret; + + for (i = 0; i < nb_fd; i++) { + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + health_code_update(); + + /* Thread quit pipe has been closed. Killing thread. */ + if (pollfd == quit_pipe_read_fd) { + err = 0; + mark_thread_intialization_as_failed(notifiers); + goto exit; + } else if (pollfd == consumer_data->err_sock) { + /* Event on the registration socket */ + if (revents & LPOLLIN) { + continue; + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ERR("consumer err socket poll error"); + mark_thread_intialization_as_failed(notifiers); + goto error; + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + mark_thread_intialization_as_failed(notifiers); + goto error; + } + } + } + + sock = lttcomm_accept_unix_sock(consumer_data->err_sock); + if (sock < 0) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + /* + * Set the CLOEXEC flag. Return code is useless because either way, the + * show must go on. + */ + (void) utils_set_fd_cloexec(sock); + + health_code_update(); + + DBG2("Receiving code from consumer err_sock"); + + /* Getting status code from kconsumerd */ + ret = lttcomm_recv_unix_sock(sock, &code, + sizeof(enum lttcomm_return_code)); + if (ret <= 0) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + health_code_update(); + if (code != LTTCOMM_CONSUMERD_COMMAND_SOCK_READY) { + ERR("consumer error when waiting for SOCK_READY : %s", + lttcomm_get_readable_code((lttcomm_return_code) -code)); + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + /* Connect both command and metadata sockets. */ + consumer_data->cmd_sock = + lttcomm_connect_unix_sock( + consumer_data->cmd_unix_sock_path); + consumer_data->metadata_fd = + lttcomm_connect_unix_sock( + consumer_data->cmd_unix_sock_path); + if (consumer_data->cmd_sock < 0 || consumer_data->metadata_fd < 0) { + PERROR("consumer connect cmd socket"); + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + consumer_data->metadata_sock.fd_ptr = &consumer_data->metadata_fd; + + /* Create metadata socket lock. */ + consumer_data->metadata_sock.lock = (pthread_mutex_t *) zmalloc(sizeof(pthread_mutex_t)); + if (consumer_data->metadata_sock.lock == NULL) { + PERROR("zmalloc pthread mutex"); + mark_thread_intialization_as_failed(notifiers); + goto error; + } + pthread_mutex_init(consumer_data->metadata_sock.lock, NULL); + + DBG("Consumer command socket ready (fd: %d)", consumer_data->cmd_sock); + DBG("Consumer metadata socket ready (fd: %d)", + consumer_data->metadata_fd); + + /* + * Remove the consumerd error sock since we've established a connection. + */ + ret = lttng_poll_del(&events, consumer_data->err_sock); + if (ret < 0) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + /* Add new accepted error socket. */ + ret = lttng_poll_add(&events, sock, LPOLLIN | LPOLLRDHUP); + if (ret < 0) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + /* Add metadata socket that is successfully connected. */ + ret = lttng_poll_add(&events, consumer_data->metadata_fd, + LPOLLIN | LPOLLRDHUP); + if (ret < 0) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + health_code_update(); + + /* + * Transfer the write-end of the channel monitoring pipe to the consumer + * by issuing a SET_CHANNEL_MONITOR_PIPE command. + */ + cmd_socket_wrapper = consumer_allocate_socket(&consumer_data->cmd_sock); + if (!cmd_socket_wrapper) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + cmd_socket_wrapper->lock = &consumer_data->lock; + + pthread_mutex_lock(cmd_socket_wrapper->lock); + ret = consumer_init(cmd_socket_wrapper, the_sessiond_uuid); + if (ret) { + ERR("Failed to send sessiond uuid to consumer daemon"); + mark_thread_intialization_as_failed(notifiers); + pthread_mutex_unlock(cmd_socket_wrapper->lock); + goto error; + } + pthread_mutex_unlock(cmd_socket_wrapper->lock); + + ret = consumer_send_channel_monitor_pipe(cmd_socket_wrapper, + consumer_data->channel_monitor_pipe); + if (ret) { + mark_thread_intialization_as_failed(notifiers); + goto error; + } + + /* Discard the socket wrapper as it is no longer needed. */ + consumer_destroy_socket(cmd_socket_wrapper); + cmd_socket_wrapper = NULL; + + /* The thread is completely initialized, signal that it is ready. */ + mark_thread_as_ready(notifiers); + + /* Infinite blocking call, waiting for transmission */ + while (1) { + health_code_update(); + + /* Exit the thread because the thread quit pipe has been triggered. */ + if (should_quit) { + /* Not a health error. */ + err = 0; + goto exit; + } + + health_poll_entry(); + ret = lttng_poll_wait(&events, -1); + health_poll_exit(); + if (ret < 0) { + goto error; + } + + nb_fd = ret; + + for (i = 0; i < nb_fd; i++) { + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + health_code_update(); + + /* + * Thread quit pipe has been triggered, flag that we should stop + * but continue the current loop to handle potential data from + * consumer. + */ + if (pollfd == quit_pipe_read_fd) { + should_quit = 1; + } else if (pollfd == sock) { + /* Event on the consumerd socket */ + if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP) + && !(revents & LPOLLIN)) { + ERR("consumer err socket second poll error"); + goto error; + } + health_code_update(); + /* Wait for any kconsumerd error */ + ret = lttcomm_recv_unix_sock(sock, &code, + sizeof(enum lttcomm_return_code)); + if (ret <= 0) { + ERR("consumer closed the command socket"); + goto error; + } + + ERR("consumer return code : %s", + lttcomm_get_readable_code((lttcomm_return_code) -code)); + + goto exit; + } else if (pollfd == consumer_data->metadata_fd) { + if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP) + && !(revents & LPOLLIN)) { + ERR("consumer err metadata socket second poll error"); + goto error; + } + /* UST metadata requests */ + ret = ust_consumer_metadata_request( + &consumer_data->metadata_sock); + if (ret < 0) { + ERR("Handling metadata request"); + goto error; + } + } + /* No need for an else branch all FDs are tested prior. */ + } + health_code_update(); + } + +exit: +error: + /* + * We lock here because we are about to close the sockets and some other + * thread might be using them so get exclusive access which will abort all + * other consumer command by other threads. + */ + pthread_mutex_lock(&consumer_data->lock); + + /* Immediately set the consumerd state to stopped */ + if (consumer_data->type == LTTNG_CONSUMER_KERNEL) { + uatomic_set(&the_kernel_consumerd_state, CONSUMER_ERROR); + } else if (consumer_data->type == LTTNG_CONSUMER64_UST || + consumer_data->type == LTTNG_CONSUMER32_UST) { + uatomic_set(&the_ust_consumerd_state, CONSUMER_ERROR); + } else { + /* Code flow error... */ + abort(); + } + + if (consumer_data->err_sock >= 0) { + ret = close(consumer_data->err_sock); + if (ret) { + PERROR("close"); + } + consumer_data->err_sock = -1; + } + if (consumer_data->cmd_sock >= 0) { + ret = close(consumer_data->cmd_sock); + if (ret) { + PERROR("close"); + } + consumer_data->cmd_sock = -1; + } + if (consumer_data->metadata_sock.fd_ptr && + *consumer_data->metadata_sock.fd_ptr >= 0) { + ret = close(*consumer_data->metadata_sock.fd_ptr); + if (ret) { + PERROR("close"); + } + } + if (sock >= 0) { + ret = close(sock); + if (ret) { + PERROR("close"); + } + } + + unlink(consumer_data->err_unix_sock_path); + unlink(consumer_data->cmd_unix_sock_path); + pthread_mutex_unlock(&consumer_data->lock); + + /* Cleanup metadata socket mutex. */ + if (consumer_data->metadata_sock.lock) { + pthread_mutex_destroy(consumer_data->metadata_sock.lock); + free(consumer_data->metadata_sock.lock); + } + lttng_poll_clean(&events); + + if (cmd_socket_wrapper) { + consumer_destroy_socket(cmd_socket_wrapper); + } +error_poll: + if (err) { + health_error(); + ERR("Health error occurred in %s", __func__); + } + health_unregister(the_health_sessiond); + DBG("consumer thread cleanup completed"); + + rcu_thread_offline(); + rcu_unregister_thread(); + + return NULL; +} + +static bool shutdown_consumer_management_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); + + return notify_thread_pipe(write_fd) == 1; +} + +static void cleanup_consumer_management_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + + lttng_pipe_destroy(notifiers->quit_pipe); + free(notifiers); +} + +bool launch_consumer_management_thread(struct consumer_data *consumer_data) +{ + struct lttng_pipe *quit_pipe; + struct thread_notifiers *notifiers = NULL; + struct lttng_thread *thread; + + notifiers = (thread_notifiers *) zmalloc(sizeof(*notifiers)); + if (!notifiers) { + goto error_alloc; + } + + quit_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!quit_pipe) { + goto error; + } + notifiers->quit_pipe = quit_pipe; + notifiers->consumer_data = consumer_data; + sem_init(¬ifiers->ready, 0, 0); + + thread = lttng_thread_create("Consumer management", + thread_consumer_management, + shutdown_consumer_management_thread, + cleanup_consumer_management_thread, + notifiers); + if (!thread) { + goto error; + } + wait_until_thread_is_ready(notifiers); + lttng_thread_put(thread); + if (notifiers->initialization_result) { + return false; + } + return true; +error: + cleanup_consumer_management_thread(notifiers); +error_alloc: + return false; +} diff --git a/src/bin/lttng-sessiond/manage-kernel.c b/src/bin/lttng-sessiond/manage-kernel.c deleted file mode 100644 index 55ecbf8b3..000000000 --- a/src/bin/lttng-sessiond/manage-kernel.c +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include - -#include "manage-kernel.h" -#include "testpoint.h" -#include "health-sessiond.h" -#include "utils.h" -#include "thread.h" -#include "kernel.h" -#include "kernel-consumer.h" - -struct thread_notifiers { - struct lttng_pipe *quit_pipe; - int kernel_poll_pipe_read_fd; -}; - -/* - * Update the kernel poll set of all channel fd available over all tracing - * session. Add the wakeup pipe at the end of the set. - */ -static int update_kernel_poll(struct lttng_poll_event *events) -{ - int ret; - struct ltt_kernel_channel *channel; - struct ltt_session *session; - const struct ltt_session_list *session_list = session_get_list(); - - DBG("Updating kernel poll set"); - - session_lock_list(); - cds_list_for_each_entry(session, &session_list->head, list) { - if (!session_get(session)) { - continue; - } - session_lock(session); - if (session->kernel_session == NULL) { - session_unlock(session); - session_put(session); - continue; - } - - cds_list_for_each_entry(channel, - &session->kernel_session->channel_list.head, list) { - /* Add channel fd to the kernel poll set */ - ret = lttng_poll_add(events, channel->fd, LPOLLIN | LPOLLRDNORM); - if (ret < 0) { - session_unlock(session); - session_put(session); - goto error; - } - DBG("Channel fd %d added to kernel set", channel->fd); - } - session_unlock(session); - session_put(session); - } - session_unlock_list(); - - return 0; - -error: - session_unlock_list(); - return -1; -} - -/* - * Find the channel fd from 'fd' over all tracing session. When found, check - * for new channel stream and send those stream fds to the kernel consumer. - * - * Useful for CPU hotplug feature. - */ -static int update_kernel_stream(int fd) -{ - int ret = 0; - struct ltt_session *session; - struct ltt_kernel_session *ksess; - struct ltt_kernel_channel *channel; - const struct ltt_session_list *session_list = session_get_list(); - - DBG("Updating kernel streams for channel fd %d", fd); - - session_lock_list(); - cds_list_for_each_entry(session, &session_list->head, list) { - if (!session_get(session)) { - continue; - } - session_lock(session); - if (session->kernel_session == NULL) { - session_unlock(session); - session_put(session); - continue; - } - ksess = session->kernel_session; - - cds_list_for_each_entry(channel, - &ksess->channel_list.head, list) { - struct lttng_ht_iter iter; - struct consumer_socket *socket; - - if (channel->fd != fd) { - continue; - } - DBG("Channel found, updating kernel streams"); - ret = kernel_open_channel_stream(channel); - if (ret < 0) { - goto error; - } - /* Update the stream global counter */ - ksess->stream_count_global += ret; - - /* - * Have we already sent fds to the consumer? If yes, it - * means that tracing is started so it is safe to send - * our updated stream fds. - */ - if (ksess->consumer_fds_sent != 1 - || ksess->consumer == NULL) { - ret = -1; - goto error; - } - - rcu_read_lock(); - cds_lfht_for_each_entry(ksess->consumer->socks->ht, - &iter.iter, socket, node.node) { - pthread_mutex_lock(socket->lock); - ret = kernel_consumer_send_channel_streams(socket, - channel, ksess, - session->output_traces ? 1 : 0); - pthread_mutex_unlock(socket->lock); - if (ret < 0) { - rcu_read_unlock(); - goto error; - } - } - rcu_read_unlock(); - } - session_unlock(session); - session_put(session); - } - session_unlock_list(); - return ret; - -error: - session_unlock(session); - session_put(session); - session_unlock_list(); - return ret; -} - -/* - * This thread manage event coming from the kernel. - * - * Features supported in this thread: - * -) CPU Hotplug - */ -static void *thread_kernel_management(void *data) -{ - int ret, i, pollfd, update_poll_flag = 1, err = -1; - uint32_t revents, nb_fd; - char tmp; - struct lttng_poll_event events; - struct thread_notifiers *notifiers = data; - const int quit_pipe_read_fd = lttng_pipe_get_readfd(notifiers->quit_pipe); - - DBG("[thread] Thread manage kernel started"); - - health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_KERNEL); - - /* - * This first step of the while is to clean this structure which could free - * non NULL pointers so initialize it before the loop. - */ - lttng_poll_init(&events); - - if (testpoint(sessiond_thread_manage_kernel)) { - goto error_testpoint; - } - - health_code_update(); - - if (testpoint(sessiond_thread_manage_kernel_before_loop)) { - goto error_testpoint; - } - - while (1) { - health_code_update(); - - if (update_poll_flag == 1) { - /* Clean events object. We are about to populate it again. */ - lttng_poll_clean(&events); - - ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); - if (ret < 0) { - goto error_poll_create; - } - - ret = lttng_poll_add(&events, - notifiers->kernel_poll_pipe_read_fd, - LPOLLIN); - if (ret < 0) { - goto error; - } - - ret = lttng_poll_add(&events, - quit_pipe_read_fd, - LPOLLIN); - if (ret < 0) { - goto error; - } - - /* This will add the available kernel channel if any. */ - ret = update_kernel_poll(&events); - if (ret < 0) { - goto error; - } - update_poll_flag = 0; - } - - DBG("Thread kernel polling"); - - /* Poll infinite value of time */ - restart: - health_poll_entry(); - ret = lttng_poll_wait(&events, -1); - DBG("Thread kernel return from poll on %d fds", - LTTNG_POLL_GETNB(&events)); - health_poll_exit(); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - goto restart; - } - goto error; - } else if (ret == 0) { - /* Should not happen since timeout is infinite */ - ERR("Return value of poll is 0 with an infinite timeout.\n" - "This should not have happened! Continuing..."); - continue; - } - - nb_fd = ret; - - for (i = 0; i < nb_fd; i++) { - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - health_code_update(); - - if (pollfd == quit_pipe_read_fd) { - err = 0; - goto exit; - } - - /* Check for data on kernel pipe */ - if (revents & LPOLLIN) { - if (pollfd == notifiers->kernel_poll_pipe_read_fd) { - (void) lttng_read(notifiers->kernel_poll_pipe_read_fd, - &tmp, 1); - /* - * Ret value is useless here, if this pipe gets any actions an - * update is required anyway. - */ - update_poll_flag = 1; - continue; - } else { - /* - * New CPU detected by the kernel. Adding kernel stream to - * kernel session and updating the kernel consumer - */ - ret = update_kernel_stream(pollfd); - if (ret < 0) { - continue; - } - break; - } - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - update_poll_flag = 1; - continue; - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - } - } - -exit: -error: - lttng_poll_clean(&events); -error_poll_create: -error_testpoint: - if (err) { - health_error(); - ERR("Health error occurred in %s", __func__); - WARN("Kernel thread died unexpectedly. " - "Kernel tracing can continue but CPU hotplug is disabled."); - } - health_unregister(the_health_sessiond); - DBG("Kernel thread dying"); - return NULL; -} - -static bool shutdown_kernel_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); - - return notify_thread_pipe(write_fd) == 1; -} - -static void cleanup_kernel_management_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - - lttng_pipe_destroy(notifiers->quit_pipe); - free(notifiers); -} - -bool launch_kernel_management_thread(int kernel_poll_pipe_read_fd) -{ - struct lttng_pipe *quit_pipe; - struct thread_notifiers *notifiers = NULL; - struct lttng_thread *thread; - - notifiers = zmalloc(sizeof(*notifiers)); - if (!notifiers) { - goto error_alloc; - } - quit_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!quit_pipe) { - goto error; - } - notifiers->quit_pipe = quit_pipe; - notifiers->kernel_poll_pipe_read_fd = kernel_poll_pipe_read_fd; - - thread = lttng_thread_create("Kernel management", - thread_kernel_management, - shutdown_kernel_management_thread, - cleanup_kernel_management_thread, - notifiers); - if (!thread) { - goto error; - } - lttng_thread_put(thread); - return true; -error: - cleanup_kernel_management_thread(notifiers); -error_alloc: - return false; -} diff --git a/src/bin/lttng-sessiond/manage-kernel.cpp b/src/bin/lttng-sessiond/manage-kernel.cpp new file mode 100644 index 000000000..2ffe60919 --- /dev/null +++ b/src/bin/lttng-sessiond/manage-kernel.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include + +#include "manage-kernel.h" +#include "testpoint.h" +#include "health-sessiond.h" +#include "utils.h" +#include "thread.h" +#include "kernel.h" +#include "kernel-consumer.h" + +struct thread_notifiers { + struct lttng_pipe *quit_pipe; + int kernel_poll_pipe_read_fd; +}; + +/* + * Update the kernel poll set of all channel fd available over all tracing + * session. Add the wakeup pipe at the end of the set. + */ +static int update_kernel_poll(struct lttng_poll_event *events) +{ + int ret; + struct ltt_kernel_channel *channel; + struct ltt_session *session; + const struct ltt_session_list *session_list = session_get_list(); + + DBG("Updating kernel poll set"); + + session_lock_list(); + cds_list_for_each_entry(session, &session_list->head, list) { + if (!session_get(session)) { + continue; + } + session_lock(session); + if (session->kernel_session == NULL) { + session_unlock(session); + session_put(session); + continue; + } + + cds_list_for_each_entry(channel, + &session->kernel_session->channel_list.head, list) { + /* Add channel fd to the kernel poll set */ + ret = lttng_poll_add(events, channel->fd, LPOLLIN | LPOLLRDNORM); + if (ret < 0) { + session_unlock(session); + session_put(session); + goto error; + } + DBG("Channel fd %d added to kernel set", channel->fd); + } + session_unlock(session); + session_put(session); + } + session_unlock_list(); + + return 0; + +error: + session_unlock_list(); + return -1; +} + +/* + * Find the channel fd from 'fd' over all tracing session. When found, check + * for new channel stream and send those stream fds to the kernel consumer. + * + * Useful for CPU hotplug feature. + */ +static int update_kernel_stream(int fd) +{ + int ret = 0; + struct ltt_session *session; + struct ltt_kernel_session *ksess; + struct ltt_kernel_channel *channel; + const struct ltt_session_list *session_list = session_get_list(); + + DBG("Updating kernel streams for channel fd %d", fd); + + session_lock_list(); + cds_list_for_each_entry(session, &session_list->head, list) { + if (!session_get(session)) { + continue; + } + session_lock(session); + if (session->kernel_session == NULL) { + session_unlock(session); + session_put(session); + continue; + } + ksess = session->kernel_session; + + cds_list_for_each_entry(channel, + &ksess->channel_list.head, list) { + struct lttng_ht_iter iter; + struct consumer_socket *socket; + + if (channel->fd != fd) { + continue; + } + DBG("Channel found, updating kernel streams"); + ret = kernel_open_channel_stream(channel); + if (ret < 0) { + goto error; + } + /* Update the stream global counter */ + ksess->stream_count_global += ret; + + /* + * Have we already sent fds to the consumer? If yes, it + * means that tracing is started so it is safe to send + * our updated stream fds. + */ + if (ksess->consumer_fds_sent != 1 + || ksess->consumer == NULL) { + ret = -1; + goto error; + } + + rcu_read_lock(); + cds_lfht_for_each_entry(ksess->consumer->socks->ht, + &iter.iter, socket, node.node) { + pthread_mutex_lock(socket->lock); + ret = kernel_consumer_send_channel_streams(socket, + channel, ksess, + session->output_traces ? 1 : 0); + pthread_mutex_unlock(socket->lock); + if (ret < 0) { + rcu_read_unlock(); + goto error; + } + } + rcu_read_unlock(); + } + session_unlock(session); + session_put(session); + } + session_unlock_list(); + return ret; + +error: + session_unlock(session); + session_put(session); + session_unlock_list(); + return ret; +} + +/* + * This thread manage event coming from the kernel. + * + * Features supported in this thread: + * -) CPU Hotplug + */ +static void *thread_kernel_management(void *data) +{ + int ret, i, pollfd, update_poll_flag = 1, err = -1; + uint32_t revents, nb_fd; + char tmp; + struct lttng_poll_event events; + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int quit_pipe_read_fd = lttng_pipe_get_readfd(notifiers->quit_pipe); + + DBG("[thread] Thread manage kernel started"); + + health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_KERNEL); + + /* + * This first step of the while is to clean this structure which could free + * non NULL pointers so initialize it before the loop. + */ + lttng_poll_init(&events); + + if (testpoint(sessiond_thread_manage_kernel)) { + goto error_testpoint; + } + + health_code_update(); + + if (testpoint(sessiond_thread_manage_kernel_before_loop)) { + goto error_testpoint; + } + + while (1) { + health_code_update(); + + if (update_poll_flag == 1) { + /* Clean events object. We are about to populate it again. */ + lttng_poll_clean(&events); + + ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); + if (ret < 0) { + goto error_poll_create; + } + + ret = lttng_poll_add(&events, + notifiers->kernel_poll_pipe_read_fd, + LPOLLIN); + if (ret < 0) { + goto error; + } + + ret = lttng_poll_add(&events, + quit_pipe_read_fd, + LPOLLIN); + if (ret < 0) { + goto error; + } + + /* This will add the available kernel channel if any. */ + ret = update_kernel_poll(&events); + if (ret < 0) { + goto error; + } + update_poll_flag = 0; + } + + DBG("Thread kernel polling"); + + /* Poll infinite value of time */ + restart: + health_poll_entry(); + ret = lttng_poll_wait(&events, -1); + DBG("Thread kernel return from poll on %d fds", + LTTNG_POLL_GETNB(&events)); + health_poll_exit(); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + goto restart; + } + goto error; + } else if (ret == 0) { + /* Should not happen since timeout is infinite */ + ERR("Return value of poll is 0 with an infinite timeout.\n" + "This should not have happened! Continuing..."); + continue; + } + + nb_fd = ret; + + for (i = 0; i < nb_fd; i++) { + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + health_code_update(); + + if (pollfd == quit_pipe_read_fd) { + err = 0; + goto exit; + } + + /* Check for data on kernel pipe */ + if (revents & LPOLLIN) { + if (pollfd == notifiers->kernel_poll_pipe_read_fd) { + (void) lttng_read(notifiers->kernel_poll_pipe_read_fd, + &tmp, 1); + /* + * Ret value is useless here, if this pipe gets any actions an + * update is required anyway. + */ + update_poll_flag = 1; + continue; + } else { + /* + * New CPU detected by the kernel. Adding kernel stream to + * kernel session and updating the kernel consumer + */ + ret = update_kernel_stream(pollfd); + if (ret < 0) { + continue; + } + break; + } + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + update_poll_flag = 1; + continue; + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + } + } + +exit: +error: + lttng_poll_clean(&events); +error_poll_create: +error_testpoint: + if (err) { + health_error(); + ERR("Health error occurred in %s", __func__); + WARN("Kernel thread died unexpectedly. " + "Kernel tracing can continue but CPU hotplug is disabled."); + } + health_unregister(the_health_sessiond); + DBG("Kernel thread dying"); + return NULL; +} + +static bool shutdown_kernel_management_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); + + return notify_thread_pipe(write_fd) == 1; +} + +static void cleanup_kernel_management_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + + lttng_pipe_destroy(notifiers->quit_pipe); + free(notifiers); +} + +bool launch_kernel_management_thread(int kernel_poll_pipe_read_fd) +{ + struct lttng_pipe *quit_pipe; + struct thread_notifiers *notifiers = NULL; + struct lttng_thread *thread; + + notifiers = (thread_notifiers *) zmalloc(sizeof(*notifiers)); + if (!notifiers) { + goto error_alloc; + } + quit_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!quit_pipe) { + goto error; + } + notifiers->quit_pipe = quit_pipe; + notifiers->kernel_poll_pipe_read_fd = kernel_poll_pipe_read_fd; + + thread = lttng_thread_create("Kernel management", + thread_kernel_management, + shutdown_kernel_management_thread, + cleanup_kernel_management_thread, + notifiers); + if (!thread) { + goto error; + } + lttng_thread_put(thread); + return true; +error: + cleanup_kernel_management_thread(notifiers); +error_alloc: + return false; +} diff --git a/src/bin/lttng-sessiond/modprobe.c b/src/bin/lttng-sessiond/modprobe.c deleted file mode 100644 index 24b34d094..000000000 --- a/src/bin/lttng-sessiond/modprobe.c +++ /dev/null @@ -1,776 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2014 Jan Glauber - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -/** - * @file modprobe.c - * - * @brief modprobe related functions. - * - */ - -#define _LGPL_SOURCE -#include -#include -#include - -#include -#include - -#include "modprobe.h" -#include "kern-modules.h" -#include "lttng-sessiond.h" - -/* LTTng kernel tracer mandatory core modules list */ -struct kern_modules_param kern_modules_control_core[] = { - { - .name = (char *) "lttng-ring-buffer-client-discard", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, - }, - { - .name = (char *) "lttng-ring-buffer-client-overwrite", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, - }, - { - .name = (char *) "lttng-ring-buffer-metadata-client", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, - }, - { - .name = (char *) "lttng-ring-buffer-client-mmap-discard", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, - }, - { - .name = (char *) "lttng-ring-buffer-client-mmap-overwrite", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, - }, - { - .name = (char *) "lttng-ring-buffer-metadata-mmap-client", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, - }, - { - .name = (char *) "lttng-ring-buffer-event_notifier-client", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-counter-client-percpu-64-modular", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-counter-client-percpu-32-modular", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, -}; - -/* LTTng kerneltracer probe modules list */ -struct kern_modules_param kern_modules_probes_default[] = { - { - .name = (char *) "lttng-probe-asoc", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-block", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-btrfs", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-compaction", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-ext3", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-ext4", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-gpio", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-i2c", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-irq", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-jbd", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-jbd2", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-kmem", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-kvm", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-kvm-x86", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-kvm-x86-mmu", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-lock", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-module", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-napi", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-net", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-power", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-preemptirq", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-printk", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-random", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-rcu", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-regmap", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-regulator", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-rpm", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-sched", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-scsi", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-signal", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-skb", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-sock", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-statedump", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-sunrpc", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-timer", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-udp", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-vmscan", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-v4l2", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-workqueue", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-writeback", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-x86-irq-vectors", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, - { - .name = (char *) "lttng-probe-x86-exceptions", - .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, - }, -}; - -/* dynamic probe modules list */ -static struct kern_modules_param *probes; -static int nr_probes; -static int probes_capacity; - -#if HAVE_KMOD -#include - -/** - * @brief Logging function for libkmod integration. - */ -static void log_kmod(void *data, int priority, const char *file, int line, - const char *fn, const char *format, va_list args) -{ - char *str; - - if (vasprintf(&str, format, args) < 0) { - return; - } - - DBG("libkmod: %s", str); - free(str); -} - -/** - * @brief Setup the libkmod context. - * - * Create the context, add a custom logging function and preload the - * ressources for faster operation. - * - * @returns \c 0 on success - * \c < 0 on error - */ -static int setup_kmod_ctx(struct kmod_ctx **ctx) -{ - int ret = 0; - - *ctx = kmod_new(NULL, NULL); - if (!ctx) { - PERROR("Unable to create kmod library context"); - ret = -ENOMEM; - goto error; - } - - kmod_set_log_fn(*ctx, log_kmod, NULL); - ret = kmod_load_resources(*ctx); - if (ret < 0) { - ERR("Failed to load kmod library resources"); - goto error; - } - -error: - return ret; -} - -/** - * @brief Loads the kernel modules in \p modules - * - * @param modules List of modules to load - * @param entries Number of modules in the list - * - * If the modules are required, we will return with error after the - * first failed module load, otherwise we continue loading. - * - * @returns \c 0 on success - * \c < 0 on error - */ -static int modprobe_lttng(struct kern_modules_param *modules, - int entries) -{ - int ret = 0, i; - struct kmod_ctx *ctx; - - ret = setup_kmod_ctx(&ctx); - if (ret < 0) { - goto error; - } - - for (i = 0; i < entries; i++) { - struct kmod_module *mod = NULL; - - ret = kmod_module_new_from_name(ctx, modules[i].name, &mod); - if (ret < 0) { - PERROR("Failed to create kmod module for %s", modules[i].name); - goto error; - } - - ret = kmod_module_probe_insert_module(mod, 0, - NULL, NULL, NULL, NULL); - if (ret == -EEXIST) { - DBG("Module %s is already loaded", modules[i].name); - ret = 0; - } else if (ret < 0) { - if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { - ERR("Unable to load required module %s", - modules[i].name); - goto error; - } else { - DBG("Unable to load optional module %s; continuing", - modules[i].name); - ret = 0; - } - } else { - DBG("Modprobe successfully %s", modules[i].name); - modules[i].loaded = true; - } - - kmod_module_unref(mod); - } - -error: - if (ctx) { - kmod_unref(ctx); - } - return ret; -} - -/** - * @brief Recursively unload modules. - * - * This function implements the same modules unloading behavior as - * 'modprobe -r' or rmmod, it will recursevily go trought the \p module - * dependencies and unload modules with a refcount of 0. - * - * @param mod The module to unload - * - * @returns \c 0 on success - * \c < 0 on error - */ -static int rmmod_recurse(struct kmod_module *mod) { - int ret = 0; - struct kmod_list *deps, *itr; - - if (kmod_module_get_initstate(mod) == KMOD_MODULE_BUILTIN) { - DBG("Module %s is builtin", kmod_module_get_name(mod)); - return ret; - } - - ret = kmod_module_remove_module(mod, 0); - - deps = kmod_module_get_dependencies(mod); - if (deps != NULL) { - kmod_list_foreach(itr, deps) { - struct kmod_module *dep = kmod_module_get_module(itr); - if (kmod_module_get_refcnt(dep) == 0) { - DBG("Recursive remove module %s", - kmod_module_get_name(dep)); - rmmod_recurse(dep); - } - kmod_module_unref(dep); - } - kmod_module_unref_list(deps); - } - - return ret; -} - -/** - * @brief Unloads the kernel modules in \p modules - * - * @param modules List of modules to unload - * @param entries Number of modules in the list - * - */ -static void modprobe_remove_lttng(const struct kern_modules_param *modules, - int entries) -{ - int ret = 0, i; - struct kmod_ctx *ctx; - - ret = setup_kmod_ctx(&ctx); - if (ret < 0) { - goto error; - } - - for (i = entries - 1; i >= 0; i--) { - struct kmod_module *mod = NULL; - - if (!modules[i].loaded) { - continue; - } - - ret = kmod_module_new_from_name(ctx, modules[i].name, &mod); - if (ret < 0) { - PERROR("Failed to create kmod module for %s", modules[i].name); - goto error; - } - - ret = rmmod_recurse(mod); - if (ret == -EEXIST) { - DBG("Module %s is not in kernel.", modules[i].name); - } else if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED && ret < 0) { - ERR("Unable to remove module %s", modules[i].name); - } else { - DBG("Modprobe removal successful %s", - modules[i].name); - } - - kmod_module_unref(mod); - } - -error: - if (ctx) { - kmod_unref(ctx); - } -} - -#else /* HAVE_KMOD */ - -static int modprobe_lttng(struct kern_modules_param *modules, - int entries) -{ - int ret = 0, i; - char modprobe[256]; - - for (i = 0; i < entries; i++) { - ret = snprintf(modprobe, sizeof(modprobe), - "/sbin/modprobe %s%s", - modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED ? "" : "-q ", - modules[i].name); - if (ret < 0) { - PERROR("snprintf modprobe"); - goto error; - } - modprobe[sizeof(modprobe) - 1] = '\0'; - ret = system(modprobe); - if (ret == -1) { - if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { - ERR("Unable to launch modprobe for required module %s", - modules[i].name); - goto error; - } else { - DBG("Unable to launch modprobe for optional module %s; continuing", - modules[i].name); - ret = 0; - } - } else if (WEXITSTATUS(ret) != 0) { - if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { - ERR("Unable to load required module %s", - modules[i].name); - goto error; - } else { - DBG("Unable to load optional module %s; continuing", - modules[i].name); - ret = 0; - } - } else { - DBG("Modprobe successfully %s", modules[i].name); - modules[i].loaded = true; - } - } - -error: - return ret; -} - -static void modprobe_remove_lttng(const struct kern_modules_param *modules, - int entries) -{ - int ret = 0, i; - char modprobe[256]; - - for (i = entries - 1; i >= 0; i--) { - if (!modules[i].loaded) { - continue; - } - ret = snprintf(modprobe, sizeof(modprobe), - "/sbin/modprobe -r -q %s", - modules[i].name); - if (ret < 0) { - PERROR("snprintf modprobe -r"); - return; - } - modprobe[sizeof(modprobe) - 1] = '\0'; - ret = system(modprobe); - if (ret == -1) { - if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { - ERR("Unable to launch modprobe -r for required module %s", - modules[i].name); - } else { - DBG("Unable to launch modprobe -r for optional module %s", - modules[i].name); - } - } else if (WEXITSTATUS(ret) != 0) { - if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { - ERR("Unable to remove required module %s", - modules[i].name); - } else { - DBG("Unable to remove optional module %s", - modules[i].name); - } - } else { - DBG("Modprobe removal successful %s", modules[i].name); - } - } -} - -#endif /* HAVE_KMOD */ - -/* - * Remove control kernel module(s) in reverse load order. - */ -void modprobe_remove_lttng_control(void) -{ - modprobe_remove_lttng(kern_modules_control_core, - ARRAY_SIZE(kern_modules_control_core)); -} - -static void free_probes(void) -{ - int i; - - if (!probes) { - return; - } - for (i = 0; i < nr_probes; ++i) { - free(probes[i].name); - } - free(probes); - probes = NULL; - nr_probes = 0; -} - -/* - * Remove data kernel modules in reverse load order. - */ -void modprobe_remove_lttng_data(void) -{ - if (!probes) { - return; - } - - modprobe_remove_lttng(probes, nr_probes); - free_probes(); -} - -/* - * Remove all kernel modules in reverse order. - */ -void modprobe_remove_lttng_all(void) -{ - modprobe_remove_lttng_data(); - modprobe_remove_lttng_control(); -} - -/* - * Load control kernel module(s). - */ -int modprobe_lttng_control(void) -{ - return modprobe_lttng(kern_modules_control_core, - ARRAY_SIZE(kern_modules_control_core)); -} - -/** - * Grow global list of probes (double capacity or set it to 1 if - * currently 0 and copy existing data). - */ -static int grow_probes(void) -{ - int i; - struct kern_modules_param *tmp_probes; - - /* Initialize capacity to 1 if 0. */ - if (probes_capacity == 0) { - probes = zmalloc(sizeof(*probes)); - if (!probes) { - PERROR("malloc probe list"); - return -ENOMEM; - } - - probes_capacity = 1; - return 0; - } - - /* Double size. */ - probes_capacity *= 2; - - tmp_probes = zmalloc(sizeof(*tmp_probes) * probes_capacity); - if (!tmp_probes) { - PERROR("malloc probe list"); - return -ENOMEM; - } - - for (i = 0; i < nr_probes; ++i) { - /* Ownership of 'name' field is transferred. */ - tmp_probes[i] = probes[i]; - } - - /* Replace probes with larger copy. */ - free(probes); - probes = tmp_probes; - - return 0; -} - -/* - * Appends a comma-separated list of probes to the global list - * of probes. - */ -static int append_list_to_probes(const char *list) -{ - char *next; - int ret; - char *tmp_list, *cur_list, *saveptr; - - LTTNG_ASSERT(list); - - cur_list = tmp_list = strdup(list); - if (!tmp_list) { - PERROR("strdup temp list"); - return -ENOMEM; - } - - for (;;) { - size_t name_len; - struct kern_modules_param *cur_mod; - - next = strtok_r(cur_list, ",", &saveptr); - if (!next) { - break; - } - cur_list = NULL; - - /* filter leading spaces */ - while (*next == ' ') { - next++; - } - - if (probes_capacity <= nr_probes) { - ret = grow_probes(); - if (ret) { - goto error; - } - } - - /* Length 13 is "lttng-probe-" + \0 */ - name_len = strlen(next) + 13; - - cur_mod = &probes[nr_probes]; - cur_mod->name = zmalloc(name_len); - if (!cur_mod->name) { - PERROR("malloc probe list"); - ret = -ENOMEM; - goto error; - } - - ret = snprintf(cur_mod->name, name_len, "lttng-probe-%s", next); - if (ret < 0) { - PERROR("snprintf modprobe name"); - ret = -ENOMEM; - goto error; - } - - cur_mod->load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL; - - nr_probes++; - } - - free(tmp_list); - return 0; - -error: - free(tmp_list); - free_probes(); - return ret; -} - -/* - * Load data kernel module(s). - */ -int modprobe_lttng_data(void) -{ - int ret, i; - char *list; - - /* - * Base probes: either from command line option, environment - * variable or default list. - */ - list = the_config.kmod_probes_list.value; - if (list) { - /* User-specified probes. */ - ret = append_list_to_probes(list); - if (ret) { - return ret; - } - } else { - /* Default probes. */ - int def_len = ARRAY_SIZE(kern_modules_probes_default); - - probes = zmalloc(sizeof(*probes) * def_len); - if (!probes) { - PERROR("malloc probe list"); - return -ENOMEM; - } - - nr_probes = probes_capacity = def_len; - - for (i = 0; i < def_len; ++i) { - char* name = strdup(kern_modules_probes_default[i].name); - - if (!name) { - PERROR("strdup probe item"); - ret = -ENOMEM; - goto error; - } - - probes[i].name = name; - probes[i].load_policy = kern_modules_probes_default[i].load_policy; - } - } - - /* - * Extra modules? Append them to current probes list. - */ - list = the_config.kmod_extra_probes_list.value; - if (list) { - ret = append_list_to_probes(list); - if (ret) { - goto error; - } - } - - /* - * Load probes modules now. - */ - ret = modprobe_lttng(probes, nr_probes); - if (ret) { - goto error; - } - return ret; - -error: - free_probes(); - return ret; -} diff --git a/src/bin/lttng-sessiond/modprobe.cpp b/src/bin/lttng-sessiond/modprobe.cpp new file mode 100644 index 000000000..7e27a02eb --- /dev/null +++ b/src/bin/lttng-sessiond/modprobe.cpp @@ -0,0 +1,776 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2014 Jan Glauber + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +/** + * @file modprobe.c + * + * @brief modprobe related functions. + * + */ + +#define _LGPL_SOURCE +#include +#include +#include + +#include +#include + +#include "modprobe.h" +#include "kern-modules.h" +#include "lttng-sessiond.h" + +/* LTTng kernel tracer mandatory core modules list */ +struct kern_modules_param kern_modules_control_core[] = { + { + .name = (char *) "lttng-ring-buffer-client-discard", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, + }, + { + .name = (char *) "lttng-ring-buffer-client-overwrite", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, + }, + { + .name = (char *) "lttng-ring-buffer-metadata-client", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, + }, + { + .name = (char *) "lttng-ring-buffer-client-mmap-discard", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, + }, + { + .name = (char *) "lttng-ring-buffer-client-mmap-overwrite", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, + }, + { + .name = (char *) "lttng-ring-buffer-metadata-mmap-client", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED, + }, + { + .name = (char *) "lttng-ring-buffer-event_notifier-client", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-counter-client-percpu-64-modular", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-counter-client-percpu-32-modular", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, +}; + +/* LTTng kerneltracer probe modules list */ +struct kern_modules_param kern_modules_probes_default[] = { + { + .name = (char *) "lttng-probe-asoc", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-block", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-btrfs", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-compaction", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-ext3", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-ext4", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-gpio", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-i2c", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-irq", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-jbd", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-jbd2", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-kmem", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-kvm", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-kvm-x86", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-kvm-x86-mmu", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-lock", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-module", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-napi", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-net", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-power", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-preemptirq", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-printk", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-random", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-rcu", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-regmap", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-regulator", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-rpm", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-sched", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-scsi", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-signal", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-skb", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-sock", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-statedump", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-sunrpc", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-timer", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-udp", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-vmscan", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-v4l2", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-workqueue", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-writeback", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-x86-irq-vectors", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, + { + .name = (char *) "lttng-probe-x86-exceptions", + .load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL, + }, +}; + +/* dynamic probe modules list */ +static struct kern_modules_param *probes; +static int nr_probes; +static int probes_capacity; + +#if HAVE_KMOD +#include + +/** + * @brief Logging function for libkmod integration. + */ +static void log_kmod(void *data, int priority, const char *file, int line, + const char *fn, const char *format, va_list args) +{ + char *str; + + if (vasprintf(&str, format, args) < 0) { + return; + } + + DBG("libkmod: %s", str); + free(str); +} + +/** + * @brief Setup the libkmod context. + * + * Create the context, add a custom logging function and preload the + * ressources for faster operation. + * + * @returns \c 0 on success + * \c < 0 on error + */ +static int setup_kmod_ctx(struct kmod_ctx **ctx) +{ + int ret = 0; + + *ctx = kmod_new(NULL, NULL); + if (!ctx) { + PERROR("Unable to create kmod library context"); + ret = -ENOMEM; + goto error; + } + + kmod_set_log_fn(*ctx, log_kmod, NULL); + ret = kmod_load_resources(*ctx); + if (ret < 0) { + ERR("Failed to load kmod library resources"); + goto error; + } + +error: + return ret; +} + +/** + * @brief Loads the kernel modules in \p modules + * + * @param modules List of modules to load + * @param entries Number of modules in the list + * + * If the modules are required, we will return with error after the + * first failed module load, otherwise we continue loading. + * + * @returns \c 0 on success + * \c < 0 on error + */ +static int modprobe_lttng(struct kern_modules_param *modules, + int entries) +{ + int ret = 0, i; + struct kmod_ctx *ctx; + + ret = setup_kmod_ctx(&ctx); + if (ret < 0) { + goto error; + } + + for (i = 0; i < entries; i++) { + struct kmod_module *mod = NULL; + + ret = kmod_module_new_from_name(ctx, modules[i].name, &mod); + if (ret < 0) { + PERROR("Failed to create kmod module for %s", modules[i].name); + goto error; + } + + ret = kmod_module_probe_insert_module(mod, 0, + NULL, NULL, NULL, NULL); + if (ret == -EEXIST) { + DBG("Module %s is already loaded", modules[i].name); + ret = 0; + } else if (ret < 0) { + if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { + ERR("Unable to load required module %s", + modules[i].name); + goto error; + } else { + DBG("Unable to load optional module %s; continuing", + modules[i].name); + ret = 0; + } + } else { + DBG("Modprobe successfully %s", modules[i].name); + modules[i].loaded = true; + } + + kmod_module_unref(mod); + } + +error: + if (ctx) { + kmod_unref(ctx); + } + return ret; +} + +/** + * @brief Recursively unload modules. + * + * This function implements the same modules unloading behavior as + * 'modprobe -r' or rmmod, it will recursevily go trought the \p module + * dependencies and unload modules with a refcount of 0. + * + * @param mod The module to unload + * + * @returns \c 0 on success + * \c < 0 on error + */ +static int rmmod_recurse(struct kmod_module *mod) { + int ret = 0; + struct kmod_list *deps, *itr; + + if (kmod_module_get_initstate(mod) == KMOD_MODULE_BUILTIN) { + DBG("Module %s is builtin", kmod_module_get_name(mod)); + return ret; + } + + ret = kmod_module_remove_module(mod, 0); + + deps = kmod_module_get_dependencies(mod); + if (deps != NULL) { + kmod_list_foreach(itr, deps) { + struct kmod_module *dep = kmod_module_get_module(itr); + if (kmod_module_get_refcnt(dep) == 0) { + DBG("Recursive remove module %s", + kmod_module_get_name(dep)); + rmmod_recurse(dep); + } + kmod_module_unref(dep); + } + kmod_module_unref_list(deps); + } + + return ret; +} + +/** + * @brief Unloads the kernel modules in \p modules + * + * @param modules List of modules to unload + * @param entries Number of modules in the list + * + */ +static void modprobe_remove_lttng(const struct kern_modules_param *modules, + int entries) +{ + int ret = 0, i; + struct kmod_ctx *ctx; + + ret = setup_kmod_ctx(&ctx); + if (ret < 0) { + goto error; + } + + for (i = entries - 1; i >= 0; i--) { + struct kmod_module *mod = NULL; + + if (!modules[i].loaded) { + continue; + } + + ret = kmod_module_new_from_name(ctx, modules[i].name, &mod); + if (ret < 0) { + PERROR("Failed to create kmod module for %s", modules[i].name); + goto error; + } + + ret = rmmod_recurse(mod); + if (ret == -EEXIST) { + DBG("Module %s is not in kernel.", modules[i].name); + } else if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED && ret < 0) { + ERR("Unable to remove module %s", modules[i].name); + } else { + DBG("Modprobe removal successful %s", + modules[i].name); + } + + kmod_module_unref(mod); + } + +error: + if (ctx) { + kmod_unref(ctx); + } +} + +#else /* HAVE_KMOD */ + +static int modprobe_lttng(struct kern_modules_param *modules, + int entries) +{ + int ret = 0, i; + char modprobe[256]; + + for (i = 0; i < entries; i++) { + ret = snprintf(modprobe, sizeof(modprobe), + "/sbin/modprobe %s%s", + modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED ? "" : "-q ", + modules[i].name); + if (ret < 0) { + PERROR("snprintf modprobe"); + goto error; + } + modprobe[sizeof(modprobe) - 1] = '\0'; + ret = system(modprobe); + if (ret == -1) { + if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { + ERR("Unable to launch modprobe for required module %s", + modules[i].name); + goto error; + } else { + DBG("Unable to launch modprobe for optional module %s; continuing", + modules[i].name); + ret = 0; + } + } else if (WEXITSTATUS(ret) != 0) { + if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { + ERR("Unable to load required module %s", + modules[i].name); + goto error; + } else { + DBG("Unable to load optional module %s; continuing", + modules[i].name); + ret = 0; + } + } else { + DBG("Modprobe successfully %s", modules[i].name); + modules[i].loaded = true; + } + } + +error: + return ret; +} + +static void modprobe_remove_lttng(const struct kern_modules_param *modules, + int entries) +{ + int ret = 0, i; + char modprobe[256]; + + for (i = entries - 1; i >= 0; i--) { + if (!modules[i].loaded) { + continue; + } + ret = snprintf(modprobe, sizeof(modprobe), + "/sbin/modprobe -r -q %s", + modules[i].name); + if (ret < 0) { + PERROR("snprintf modprobe -r"); + return; + } + modprobe[sizeof(modprobe) - 1] = '\0'; + ret = system(modprobe); + if (ret == -1) { + if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { + ERR("Unable to launch modprobe -r for required module %s", + modules[i].name); + } else { + DBG("Unable to launch modprobe -r for optional module %s", + modules[i].name); + } + } else if (WEXITSTATUS(ret) != 0) { + if (modules[i].load_policy == KERNEL_MODULE_PROPERTY_LOAD_POLICY_REQUIRED) { + ERR("Unable to remove required module %s", + modules[i].name); + } else { + DBG("Unable to remove optional module %s", + modules[i].name); + } + } else { + DBG("Modprobe removal successful %s", modules[i].name); + } + } +} + +#endif /* HAVE_KMOD */ + +/* + * Remove control kernel module(s) in reverse load order. + */ +void modprobe_remove_lttng_control(void) +{ + modprobe_remove_lttng(kern_modules_control_core, + ARRAY_SIZE(kern_modules_control_core)); +} + +static void free_probes(void) +{ + int i; + + if (!probes) { + return; + } + for (i = 0; i < nr_probes; ++i) { + free(probes[i].name); + } + free(probes); + probes = NULL; + nr_probes = 0; +} + +/* + * Remove data kernel modules in reverse load order. + */ +void modprobe_remove_lttng_data(void) +{ + if (!probes) { + return; + } + + modprobe_remove_lttng(probes, nr_probes); + free_probes(); +} + +/* + * Remove all kernel modules in reverse order. + */ +void modprobe_remove_lttng_all(void) +{ + modprobe_remove_lttng_data(); + modprobe_remove_lttng_control(); +} + +/* + * Load control kernel module(s). + */ +int modprobe_lttng_control(void) +{ + return modprobe_lttng(kern_modules_control_core, + ARRAY_SIZE(kern_modules_control_core)); +} + +/** + * Grow global list of probes (double capacity or set it to 1 if + * currently 0 and copy existing data). + */ +static int grow_probes(void) +{ + int i; + struct kern_modules_param *tmp_probes; + + /* Initialize capacity to 1 if 0. */ + if (probes_capacity == 0) { + probes = (kern_modules_param *) zmalloc(sizeof(*probes)); + if (!probes) { + PERROR("malloc probe list"); + return -ENOMEM; + } + + probes_capacity = 1; + return 0; + } + + /* Double size. */ + probes_capacity *= 2; + + tmp_probes = (kern_modules_param *) zmalloc(sizeof(*tmp_probes) * probes_capacity); + if (!tmp_probes) { + PERROR("malloc probe list"); + return -ENOMEM; + } + + for (i = 0; i < nr_probes; ++i) { + /* Ownership of 'name' field is transferred. */ + tmp_probes[i] = probes[i]; + } + + /* Replace probes with larger copy. */ + free(probes); + probes = tmp_probes; + + return 0; +} + +/* + * Appends a comma-separated list of probes to the global list + * of probes. + */ +static int append_list_to_probes(const char *list) +{ + char *next; + int ret; + char *tmp_list, *cur_list, *saveptr; + + LTTNG_ASSERT(list); + + cur_list = tmp_list = strdup(list); + if (!tmp_list) { + PERROR("strdup temp list"); + return -ENOMEM; + } + + for (;;) { + size_t name_len; + struct kern_modules_param *cur_mod; + + next = strtok_r(cur_list, ",", &saveptr); + if (!next) { + break; + } + cur_list = NULL; + + /* filter leading spaces */ + while (*next == ' ') { + next++; + } + + if (probes_capacity <= nr_probes) { + ret = grow_probes(); + if (ret) { + goto error; + } + } + + /* Length 13 is "lttng-probe-" + \0 */ + name_len = strlen(next) + 13; + + cur_mod = &probes[nr_probes]; + cur_mod->name = (char *) zmalloc(name_len); + if (!cur_mod->name) { + PERROR("malloc probe list"); + ret = -ENOMEM; + goto error; + } + + ret = snprintf(cur_mod->name, name_len, "lttng-probe-%s", next); + if (ret < 0) { + PERROR("snprintf modprobe name"); + ret = -ENOMEM; + goto error; + } + + cur_mod->load_policy = KERNEL_MODULE_PROPERTY_LOAD_POLICY_OPTIONAL; + + nr_probes++; + } + + free(tmp_list); + return 0; + +error: + free(tmp_list); + free_probes(); + return ret; +} + +/* + * Load data kernel module(s). + */ +int modprobe_lttng_data(void) +{ + int ret, i; + char *list; + + /* + * Base probes: either from command line option, environment + * variable or default list. + */ + list = the_config.kmod_probes_list.value; + if (list) { + /* User-specified probes. */ + ret = append_list_to_probes(list); + if (ret) { + return ret; + } + } else { + /* Default probes. */ + int def_len = ARRAY_SIZE(kern_modules_probes_default); + + probes = (kern_modules_param *) zmalloc(sizeof(*probes) * def_len); + if (!probes) { + PERROR("malloc probe list"); + return -ENOMEM; + } + + nr_probes = probes_capacity = def_len; + + for (i = 0; i < def_len; ++i) { + char* name = strdup(kern_modules_probes_default[i].name); + + if (!name) { + PERROR("strdup probe item"); + ret = -ENOMEM; + goto error; + } + + probes[i].name = name; + probes[i].load_policy = kern_modules_probes_default[i].load_policy; + } + } + + /* + * Extra modules? Append them to current probes list. + */ + list = the_config.kmod_extra_probes_list.value; + if (list) { + ret = append_list_to_probes(list); + if (ret) { + goto error; + } + } + + /* + * Load probes modules now. + */ + ret = modprobe_lttng(probes, nr_probes); + if (ret) { + goto error; + } + return ret; + +error: + free_probes(); + return ret; +} diff --git a/src/bin/lttng-sessiond/notification-thread-commands.c b/src/bin/lttng-sessiond/notification-thread-commands.c deleted file mode 100644 index 2908ccb76..000000000 --- a/src/bin/lttng-sessiond/notification-thread-commands.c +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (C) 2017 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include "notification-thread.h" -#include "notification-thread-commands.h" -#include -#include -#include -#include - -static -void init_notification_thread_command(struct notification_thread_command *cmd) -{ - CDS_INIT_LIST_HEAD(&cmd->cmd_list_node); - lttng_waiter_init(&cmd->reply_waiter); -} - -static -int run_command_wait(struct notification_thread_handle *handle, - struct notification_thread_command *cmd) -{ - int ret; - uint64_t notification_counter = 1; - - pthread_mutex_lock(&handle->cmd_queue.lock); - /* Add to queue. */ - cds_list_add_tail(&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(&cmd->cmd_list_node); - goto error_unlock_queue; - } - pthread_mutex_unlock(&handle->cmd_queue.lock); - - lttng_waiter_wait(&cmd->reply_waiter); - return 0; -error_unlock_queue: - pthread_mutex_unlock(&handle->cmd_queue.lock); - 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, - bool is_trigger_anonymous) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - LTTNG_ASSERT(trigger); - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_REGISTER_TRIGGER; - lttng_trigger_get(trigger); - cmd.parameters.register_trigger.trigger = trigger; - cmd.parameters.register_trigger.is_trigger_anonymous = - is_trigger_anonymous; - - 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_unregister_trigger( - struct notification_thread_handle *handle, - const struct lttng_trigger *trigger) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_UNREGISTER_TRIGGER; - cmd.parameters.unregister_trigger.trigger = trigger; - - 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_add_channel( - struct notification_thread_handle *handle, - char *session_name, uid_t uid, gid_t gid, - char *channel_name, uint64_t key, - enum lttng_domain_type domain, uint64_t capacity) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_ADD_CHANNEL; - cmd.parameters.add_channel.session.name = session_name; - cmd.parameters.add_channel.session.uid = uid; - cmd.parameters.add_channel.session.gid = gid; - cmd.parameters.add_channel.channel.name = channel_name; - cmd.parameters.add_channel.channel.key = key; - cmd.parameters.add_channel.channel.domain = domain; - cmd.parameters.add_channel.channel.capacity = capacity; - - 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_channel( - struct notification_thread_handle *handle, - uint64_t key, enum lttng_domain_type domain) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_REMOVE_CHANNEL; - cmd.parameters.remove_channel.key = key; - cmd.parameters.remove_channel.domain = domain; - - 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_session_rotation_ongoing( - struct notification_thread_handle *handle, - const char *session_name, uid_t uid, gid_t gid, - uint64_t trace_archive_chunk_id) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING; - cmd.parameters.session_rotation.session_name = session_name; - cmd.parameters.session_rotation.uid = uid; - cmd.parameters.session_rotation.gid = gid; - cmd.parameters.session_rotation.trace_archive_chunk_id = - trace_archive_chunk_id; - - 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_session_rotation_completed( - struct notification_thread_handle *handle, - const char *session_name, uid_t uid, gid_t gid, - uint64_t trace_archive_chunk_id, - struct lttng_trace_archive_location *location) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED; - cmd.parameters.session_rotation.session_name = session_name; - cmd.parameters.session_rotation.uid = uid; - cmd.parameters.session_rotation.gid = gid; - cmd.parameters.session_rotation.trace_archive_chunk_id = - trace_archive_chunk_id; - cmd.parameters.session_rotation.location = location; - - 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_add_tracer_event_source( - struct notification_thread_handle *handle, - int tracer_event_source_fd, - enum lttng_domain_type domain) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - LTTNG_ASSERT(tracer_event_source_fd >= 0); - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_ADD_TRACER_EVENT_SOURCE; - cmd.parameters.tracer_event_source.tracer_event_source_fd = - tracer_event_source_fd; - cmd.parameters.tracer_event_source.domain = domain; - - 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_tracer_event_source( - struct notification_thread_handle *handle, - int tracer_event_source_fd) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_REMOVE_TRACER_EVENT_SOURCE; - cmd.parameters.tracer_event_source.tracer_event_source_fd = - tracer_event_source_fd; - - 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_list_triggers( - struct notification_thread_handle *handle, - uid_t uid, - struct lttng_triggers **triggers) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - LTTNG_ASSERT(handle); - LTTNG_ASSERT(triggers); - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_LIST_TRIGGERS; - cmd.parameters.list_triggers.uid = uid; - - 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 = {}; - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_QUIT; - ret = run_command_wait(handle, &cmd); - LTTNG_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); -} - -enum lttng_error_code notification_thread_command_get_trigger( - struct notification_thread_handle *handle, - const struct lttng_trigger *trigger, - struct lttng_trigger **real_trigger) -{ - int ret; - enum lttng_error_code ret_code; - struct notification_thread_command cmd = {}; - - init_notification_thread_command(&cmd); - - cmd.type = NOTIFICATION_COMMAND_TYPE_GET_TRIGGER; - cmd.parameters.get_trigger.trigger = trigger; - ret = run_command_wait(handle, &cmd); - if (ret) { - ret_code = LTTNG_ERR_UNK; - goto end; - } - - ret_code = cmd.reply_code; - *real_trigger = cmd.reply.get_trigger.trigger; - -end: - return ret_code; -} - -/* - * Takes ownership of the payload if present. - */ -struct lttng_event_notifier_notification *lttng_event_notifier_notification_create( - uint64_t tracer_token, - enum lttng_domain_type domain, - char *payload, - size_t payload_size) -{ - struct lttng_event_notifier_notification *notification = NULL; - - LTTNG_ASSERT(domain != LTTNG_DOMAIN_NONE); - LTTNG_ASSERT((payload && payload_size) || (!payload && !payload_size)); - - notification = zmalloc(sizeof(struct lttng_event_notifier_notification)); - if (notification == NULL) { - ERR("Error allocating notification"); - goto end; - } - - notification->tracer_token = tracer_token; - notification->type = domain; - notification->capture_buffer = payload; - notification->capture_buf_size = payload_size; - -end: - return notification; -} - -void lttng_event_notifier_notification_destroy( - struct lttng_event_notifier_notification *notification) -{ - if (!notification) { - return; - } - - free(notification->capture_buffer); - free(notification); -} diff --git a/src/bin/lttng-sessiond/notification-thread-commands.cpp b/src/bin/lttng-sessiond/notification-thread-commands.cpp new file mode 100644 index 000000000..600951159 --- /dev/null +++ b/src/bin/lttng-sessiond/notification-thread-commands.cpp @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2017 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include "notification-thread.h" +#include "notification-thread-commands.h" +#include +#include +#include +#include + +static +void init_notification_thread_command(struct notification_thread_command *cmd) +{ + CDS_INIT_LIST_HEAD(&cmd->cmd_list_node); + lttng_waiter_init(&cmd->reply_waiter); +} + +static +int run_command_wait(struct notification_thread_handle *handle, + struct notification_thread_command *cmd) +{ + int ret; + uint64_t notification_counter = 1; + + pthread_mutex_lock(&handle->cmd_queue.lock); + /* Add to queue. */ + cds_list_add_tail(&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(&cmd->cmd_list_node); + goto error_unlock_queue; + } + pthread_mutex_unlock(&handle->cmd_queue.lock); + + lttng_waiter_wait(&cmd->reply_waiter); + return 0; +error_unlock_queue: + pthread_mutex_unlock(&handle->cmd_queue.lock); + 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 = (notification_thread_command *) 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, + bool is_trigger_anonymous) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + LTTNG_ASSERT(trigger); + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_REGISTER_TRIGGER; + lttng_trigger_get(trigger); + cmd.parameters.register_trigger.trigger = trigger; + cmd.parameters.register_trigger.is_trigger_anonymous = + is_trigger_anonymous; + + 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_unregister_trigger( + struct notification_thread_handle *handle, + const struct lttng_trigger *trigger) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_UNREGISTER_TRIGGER; + cmd.parameters.unregister_trigger.trigger = trigger; + + 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_add_channel( + struct notification_thread_handle *handle, + char *session_name, uid_t uid, gid_t gid, + char *channel_name, uint64_t key, + enum lttng_domain_type domain, uint64_t capacity) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_ADD_CHANNEL; + cmd.parameters.add_channel.session.name = session_name; + cmd.parameters.add_channel.session.uid = uid; + cmd.parameters.add_channel.session.gid = gid; + cmd.parameters.add_channel.channel.name = channel_name; + cmd.parameters.add_channel.channel.key = key; + cmd.parameters.add_channel.channel.domain = domain; + cmd.parameters.add_channel.channel.capacity = capacity; + + 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_channel( + struct notification_thread_handle *handle, + uint64_t key, enum lttng_domain_type domain) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_REMOVE_CHANNEL; + cmd.parameters.remove_channel.key = key; + cmd.parameters.remove_channel.domain = domain; + + 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_session_rotation_ongoing( + struct notification_thread_handle *handle, + const char *session_name, uid_t uid, gid_t gid, + uint64_t trace_archive_chunk_id) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING; + cmd.parameters.session_rotation.session_name = session_name; + cmd.parameters.session_rotation.uid = uid; + cmd.parameters.session_rotation.gid = gid; + cmd.parameters.session_rotation.trace_archive_chunk_id = + trace_archive_chunk_id; + + 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_session_rotation_completed( + struct notification_thread_handle *handle, + const char *session_name, uid_t uid, gid_t gid, + uint64_t trace_archive_chunk_id, + struct lttng_trace_archive_location *location) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED; + cmd.parameters.session_rotation.session_name = session_name; + cmd.parameters.session_rotation.uid = uid; + cmd.parameters.session_rotation.gid = gid; + cmd.parameters.session_rotation.trace_archive_chunk_id = + trace_archive_chunk_id; + cmd.parameters.session_rotation.location = location; + + 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_add_tracer_event_source( + struct notification_thread_handle *handle, + int tracer_event_source_fd, + enum lttng_domain_type domain) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + LTTNG_ASSERT(tracer_event_source_fd >= 0); + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_ADD_TRACER_EVENT_SOURCE; + cmd.parameters.tracer_event_source.tracer_event_source_fd = + tracer_event_source_fd; + cmd.parameters.tracer_event_source.domain = domain; + + 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_tracer_event_source( + struct notification_thread_handle *handle, + int tracer_event_source_fd) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_REMOVE_TRACER_EVENT_SOURCE; + cmd.parameters.tracer_event_source.tracer_event_source_fd = + tracer_event_source_fd; + + 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_list_triggers( + struct notification_thread_handle *handle, + uid_t uid, + struct lttng_triggers **triggers) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + LTTNG_ASSERT(handle); + LTTNG_ASSERT(triggers); + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_LIST_TRIGGERS; + cmd.parameters.list_triggers.uid = uid; + + 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 = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_QUIT; + ret = run_command_wait(handle, &cmd); + LTTNG_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); +} + +enum lttng_error_code notification_thread_command_get_trigger( + struct notification_thread_handle *handle, + const struct lttng_trigger *trigger, + struct lttng_trigger **real_trigger) +{ + int ret; + enum lttng_error_code ret_code; + struct notification_thread_command cmd = {}; + + init_notification_thread_command(&cmd); + + cmd.type = NOTIFICATION_COMMAND_TYPE_GET_TRIGGER; + cmd.parameters.get_trigger.trigger = trigger; + ret = run_command_wait(handle, &cmd); + if (ret) { + ret_code = LTTNG_ERR_UNK; + goto end; + } + + ret_code = cmd.reply_code; + *real_trigger = cmd.reply.get_trigger.trigger; + +end: + return ret_code; +} + +/* + * Takes ownership of the payload if present. + */ +struct lttng_event_notifier_notification *lttng_event_notifier_notification_create( + uint64_t tracer_token, + enum lttng_domain_type domain, + char *payload, + size_t payload_size) +{ + struct lttng_event_notifier_notification *notification = NULL; + + LTTNG_ASSERT(domain != LTTNG_DOMAIN_NONE); + LTTNG_ASSERT((payload && payload_size) || (!payload && !payload_size)); + + notification = (lttng_event_notifier_notification *) zmalloc(sizeof(struct lttng_event_notifier_notification)); + if (notification == NULL) { + ERR("Error allocating notification"); + goto end; + } + + notification->tracer_token = tracer_token; + notification->type = domain; + notification->capture_buffer = payload; + notification->capture_buf_size = payload_size; + +end: + return notification; +} + +void lttng_event_notifier_notification_destroy( + struct lttng_event_notifier_notification *notification) +{ + if (!notification) { + return; + } + + free(notification->capture_buffer); + free(notification); +} diff --git a/src/bin/lttng-sessiond/notification-thread-events.c b/src/bin/lttng-sessiond/notification-thread-events.c deleted file mode 100644 index a295739f2..000000000 --- a/src/bin/lttng-sessiond/notification-thread-events.c +++ /dev/null @@ -1,4983 +0,0 @@ -/* - * Copyright (C) 2017 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "lttng/action/action.h" -#include "lttng/trigger/trigger-internal.h" -#define _LGPL_SOURCE -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "condition-internal.h" -#include "event-notifier-error-accounting.h" -#include "notification-thread.h" -#include "notification-thread-events.h" -#include "notification-thread-commands.h" -#include "lttng-sessiond.h" -#include "kernel.h" - -#define CLIENT_POLL_MASK_IN (LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP) -#define CLIENT_POLL_MASK_IN_OUT (CLIENT_POLL_MASK_IN | LPOLLOUT) - -/* The tracers currently limit the capture size to PIPE_BUF (4kb on linux). */ -#define MAX_CAPTURE_SIZE (PIPE_BUF) - -enum lttng_object_type { - LTTNG_OBJECT_TYPE_UNKNOWN, - LTTNG_OBJECT_TYPE_NONE, - LTTNG_OBJECT_TYPE_CHANNEL, - LTTNG_OBJECT_TYPE_SESSION, -}; - -struct lttng_trigger_list_element { - /* No ownership of the trigger object is assumed. */ - struct lttng_trigger *trigger; - struct cds_list_head node; -}; - -struct lttng_channel_trigger_list { - struct channel_key channel_key; - /* List of struct lttng_trigger_list_element. */ - struct cds_list_head list; - /* Node in the channel_triggers_ht */ - struct cds_lfht_node channel_triggers_ht_node; - /* call_rcu delayed reclaim. */ - struct rcu_head rcu_node; -}; - -/* - * List of triggers applying to a given session. - * - * See: - * - lttng_session_trigger_list_create() - * - lttng_session_trigger_list_build() - * - lttng_session_trigger_list_destroy() - * - lttng_session_trigger_list_add() - */ -struct lttng_session_trigger_list { - /* - * Not owned by this; points to the session_info structure's - * session name. - */ - const char *session_name; - /* List of struct lttng_trigger_list_element. */ - struct cds_list_head list; - /* Node in the session_triggers_ht */ - struct cds_lfht_node session_triggers_ht_node; - /* - * Weak reference to the notification system's session triggers - * hashtable. - * - * The session trigger list structure structure is owned by - * the session's session_info. - * - * The session_info is kept alive the the channel_infos holding a - * reference to it (reference counting). When those channels are - * destroyed (at runtime or on teardown), the reference they hold - * to the session_info are released. On destruction of session_info, - * session_info_destroy() will remove the list of triggers applying - * to this session from the notification system's state. - * - * This implies that the session_triggers_ht must be destroyed - * after the channels. - */ - struct cds_lfht *session_triggers_ht; - /* Used for delayed RCU reclaim. */ - struct rcu_head rcu_node; -}; - -struct lttng_trigger_ht_element { - struct lttng_trigger *trigger; - struct cds_lfht_node node; - struct cds_lfht_node node_by_name_uid; - struct cds_list_head client_list_trigger_node; - /* call_rcu delayed reclaim. */ - struct rcu_head rcu_node; -}; - -struct lttng_condition_list_element { - struct lttng_condition *condition; - struct cds_list_head node; -}; - -struct channel_state_sample { - struct channel_key key; - struct cds_lfht_node channel_state_ht_node; - uint64_t highest_usage; - uint64_t lowest_usage; - uint64_t channel_total_consumed; - /* call_rcu delayed reclaim. */ - struct rcu_head rcu_node; -}; - -static unsigned long hash_channel_key(struct channel_key *key); -static int evaluate_buffer_condition(const struct lttng_condition *condition, - struct lttng_evaluation **evaluation, - const struct notification_thread_state *state, - const struct channel_state_sample *previous_sample, - const struct channel_state_sample *latest_sample, - uint64_t previous_session_consumed_total, - uint64_t latest_session_consumed_total, - struct channel_info *channel_info); -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); - - -/* session_info API */ -static -void session_info_destroy(void *_data); -static -void session_info_get(struct session_info *session_info); -static -void session_info_put(struct session_info *session_info); -static -struct session_info *session_info_create(const char *name, - uid_t uid, gid_t gid, - struct lttng_session_trigger_list *trigger_list, - struct cds_lfht *sessions_ht); -static -void session_info_add_channel(struct session_info *session_info, - struct channel_info *channel_info); -static -void session_info_remove_channel(struct session_info *session_info, - struct channel_info *channel_info); - -/* lttng_session_trigger_list API */ -static -struct lttng_session_trigger_list *lttng_session_trigger_list_create( - const char *session_name, - struct cds_lfht *session_triggers_ht); -static -struct lttng_session_trigger_list *lttng_session_trigger_list_build( - const struct notification_thread_state *state, - const char *session_name); -static -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, - 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 handle_one_event_notifier_notification( - struct notification_thread_state *state, - int pipe, enum lttng_domain_type domain); - -static -void free_lttng_trigger_ht_element_rcu(struct rcu_head *node); - -static -int match_client_socket(struct cds_lfht_node *node, const void *key) -{ - /* This double-cast is intended to supress pointer-to-cast warning. */ - const int socket = (int) (intptr_t) key; - const struct notification_client *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->id == id; -} - -static -int match_channel_trigger_list(struct cds_lfht_node *node, const void *key) -{ - struct channel_key *channel_key = (struct channel_key *) key; - struct lttng_channel_trigger_list *trigger_list; - - trigger_list = caa_container_of(node, struct lttng_channel_trigger_list, - channel_triggers_ht_node); - - return !!((channel_key->key == trigger_list->channel_key.key) && - (channel_key->domain == trigger_list->channel_key.domain)); -} - -static -int match_session_trigger_list(struct cds_lfht_node *node, const void *key) -{ - const char *session_name = (const char *) key; - struct lttng_session_trigger_list *trigger_list; - - trigger_list = caa_container_of(node, struct lttng_session_trigger_list, - session_triggers_ht_node); - - return !!(strcmp(trigger_list->session_name, session_name) == 0); -} - -static -int match_channel_state_sample(struct cds_lfht_node *node, const void *key) -{ - struct channel_key *channel_key = (struct channel_key *) key; - struct channel_state_sample *sample; - - sample = caa_container_of(node, struct channel_state_sample, - channel_state_ht_node); - - return !!((channel_key->key == sample->key.key) && - (channel_key->domain == sample->key.domain)); -} - -static -int match_channel_info(struct cds_lfht_node *node, const void *key) -{ - struct channel_key *channel_key = (struct channel_key *) key; - struct channel_info *channel_info; - - channel_info = caa_container_of(node, struct channel_info, - channels_ht_node); - - return !!((channel_key->key == channel_info->key.key) && - (channel_key->domain == channel_info->key.domain)); -} - -static -int match_trigger(struct cds_lfht_node *node, const void *key) -{ - struct lttng_trigger *trigger_key = (struct lttng_trigger *) key; - struct lttng_trigger_ht_element *trigger_ht_element; - - trigger_ht_element = caa_container_of(node, struct lttng_trigger_ht_element, - node); - - return !!lttng_trigger_is_equal(trigger_key, trigger_ht_element->trigger); -} - -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 -int match_client_list_condition(struct cds_lfht_node *node, const void *key) -{ - struct lttng_condition *condition_key = (struct lttng_condition *) key; - struct notification_client_list *client_list; - const struct lttng_condition *condition; - - LTTNG_ASSERT(condition_key); - - client_list = caa_container_of(node, struct notification_client_list, - notification_trigger_clients_ht_node); - condition = client_list->condition; - - return !!lttng_condition_is_equal(condition_key, condition); -} - -static -int match_session(struct cds_lfht_node *node, const void *key) -{ - const char *name = key; - struct session_info *session_info = caa_container_of( - node, struct session_info, sessions_ht_node); - - return !strcmp(session_info->name, name); -} - -static -const char *notification_command_type_str( - enum notification_thread_command_type type) -{ - switch (type) { - case NOTIFICATION_COMMAND_TYPE_REGISTER_TRIGGER: - return "REGISTER_TRIGGER"; - case NOTIFICATION_COMMAND_TYPE_UNREGISTER_TRIGGER: - return "UNREGISTER_TRIGGER"; - case NOTIFICATION_COMMAND_TYPE_ADD_CHANNEL: - return "ADD_CHANNEL"; - case NOTIFICATION_COMMAND_TYPE_REMOVE_CHANNEL: - return "REMOVE_CHANNEL"; - case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING: - return "SESSION_ROTATION_ONGOING"; - case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED: - return "SESSION_ROTATION_COMPLETED"; - case NOTIFICATION_COMMAND_TYPE_ADD_TRACER_EVENT_SOURCE: - return "ADD_TRACER_EVENT_SOURCE"; - case NOTIFICATION_COMMAND_TYPE_REMOVE_TRACER_EVENT_SOURCE: - return "REMOVE_TRACER_EVENT_SOURCE"; - case NOTIFICATION_COMMAND_TYPE_LIST_TRIGGERS: - return "LIST_TRIGGERS"; - case NOTIFICATION_COMMAND_TYPE_GET_TRIGGER: - return "GET_TRIGGER"; - case NOTIFICATION_COMMAND_TYPE_QUIT: - return "QUIT"; - case NOTIFICATION_COMMAND_TYPE_CLIENT_COMMUNICATION_UPDATE: - return "CLIENT_COMMUNICATION_UPDATE"; - default: - abort(); - } -} - -/* - * Match trigger based on name and credentials only. - * Name duplication is NOT allowed for the same uid. - */ -static -int match_trigger_by_name_uid(struct cds_lfht_node *node, - const void *key) -{ - bool match = false; - const char *element_trigger_name; - const char *key_name; - enum lttng_trigger_status status; - const struct lttng_credentials *key_creds; - const struct lttng_credentials *node_creds; - const struct lttng_trigger *trigger_key = - (const struct lttng_trigger *) key; - const struct lttng_trigger_ht_element *trigger_ht_element = - caa_container_of(node, - struct lttng_trigger_ht_element, - node_by_name_uid); - - status = lttng_trigger_get_name(trigger_ht_element->trigger, - &element_trigger_name); - element_trigger_name = status == LTTNG_TRIGGER_STATUS_OK ? - element_trigger_name : NULL; - - status = lttng_trigger_get_name(trigger_key, &key_name); - key_name = status == LTTNG_TRIGGER_STATUS_OK ? key_name : NULL; - - /* - * Compare the names. - * Consider null names as not equal. This is to maintain backwards - * compatibility with pre-2.13 anonymous triggers. Multiples anonymous - * triggers are allowed for a given user. - */ - if (!element_trigger_name || !key_name) { - goto end; - } - - if (strcmp(element_trigger_name, key_name) != 0) { - goto end; - } - - /* Compare the owners' UIDs. */ - key_creds = lttng_trigger_get_credentials(trigger_key); - node_creds = lttng_trigger_get_credentials(trigger_ht_element->trigger); - - match = lttng_credentials_is_equal_uid(key_creds, node_creds); - -end: - return match; -} - -/* - * Hash trigger based on name and credentials only. - */ -static -unsigned long hash_trigger_by_name_uid(const struct lttng_trigger *trigger) -{ - unsigned long hash = 0; - const struct lttng_credentials *trigger_creds; - const char *trigger_name; - enum lttng_trigger_status status; - - status = lttng_trigger_get_name(trigger, &trigger_name); - if (status == LTTNG_TRIGGER_STATUS_OK) { - hash = hash_key_str(trigger_name, lttng_ht_seed); - } - - trigger_creds = lttng_trigger_get_credentials(trigger); - hash ^= hash_key_ulong((void *) (unsigned long) LTTNG_OPTIONAL_GET(trigger_creds->uid), - lttng_ht_seed); - - return hash; -} - -static -unsigned long hash_channel_key(struct channel_key *key) -{ - unsigned long key_hash = hash_key_u64(&key->key, lttng_ht_seed); - unsigned long domain_hash = hash_key_ulong( - (void *) (unsigned long) key->domain, lttng_ht_seed); - - 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 - * object's state is updated. - * - * For instance, a condition bound to a channel will be evaluated everytime - * the channel's state is changed by a channel monitoring sample. - */ -static -enum lttng_object_type get_condition_binding_object( - const struct lttng_condition *condition) -{ - switch (lttng_condition_get_type(condition)) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: - return LTTNG_OBJECT_TYPE_CHANNEL; - 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_MATCHES: - return LTTNG_OBJECT_TYPE_NONE; - default: - return LTTNG_OBJECT_TYPE_UNKNOWN; - } -} - -static -void free_channel_info_rcu(struct rcu_head *node) -{ - free(caa_container_of(node, struct channel_info, rcu_node)); -} - -static -void channel_info_destroy(struct channel_info *channel_info) -{ - if (!channel_info) { - return; - } - - if (channel_info->session_info) { - session_info_remove_channel(channel_info->session_info, - channel_info); - session_info_put(channel_info->session_info); - } - if (channel_info->name) { - free(channel_info->name); - } - call_rcu(&channel_info->rcu_node, free_channel_info_rcu); -} - -static -void free_session_info_rcu(struct rcu_head *node) -{ - free(caa_container_of(node, struct session_info, rcu_node)); -} - -/* Don't call directly, use the ref-counting mechanism. */ -static -void session_info_destroy(void *_data) -{ - struct session_info *session_info = _data; - int ret; - - LTTNG_ASSERT(session_info); - if (session_info->channel_infos_ht) { - ret = cds_lfht_destroy(session_info->channel_infos_ht, NULL); - if (ret) { - ERR("Failed to destroy channel information hash table"); - } - } - lttng_session_trigger_list_destroy(session_info->trigger_list); - - rcu_read_lock(); - cds_lfht_del(session_info->sessions_ht, - &session_info->sessions_ht_node); - rcu_read_unlock(); - free(session_info->name); - call_rcu(&session_info->rcu_node, free_session_info_rcu); -} - -static -void session_info_get(struct session_info *session_info) -{ - if (!session_info) { - return; - } - lttng_ref_get(&session_info->ref); -} - -static -void session_info_put(struct session_info *session_info) -{ - if (!session_info) { - return; - } - lttng_ref_put(&session_info->ref); -} - -static -struct session_info *session_info_create(const char *name, uid_t uid, gid_t gid, - struct lttng_session_trigger_list *trigger_list, - struct cds_lfht *sessions_ht) -{ - struct session_info *session_info; - - LTTNG_ASSERT(name); - - session_info = zmalloc(sizeof(*session_info)); - if (!session_info) { - goto end; - } - lttng_ref_init(&session_info->ref, session_info_destroy); - - session_info->channel_infos_ht = cds_lfht_new(DEFAULT_HT_SIZE, - 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!session_info->channel_infos_ht) { - goto error; - } - - cds_lfht_node_init(&session_info->sessions_ht_node); - session_info->name = strdup(name); - if (!session_info->name) { - goto error; - } - session_info->uid = uid; - session_info->gid = gid; - session_info->trigger_list = trigger_list; - session_info->sessions_ht = sessions_ht; -end: - return session_info; -error: - session_info_put(session_info); - return NULL; -} - -static -void session_info_add_channel(struct session_info *session_info, - struct channel_info *channel_info) -{ - rcu_read_lock(); - cds_lfht_add(session_info->channel_infos_ht, - hash_channel_key(&channel_info->key), - &channel_info->session_info_channels_ht_node); - rcu_read_unlock(); -} - -static -void session_info_remove_channel(struct session_info *session_info, - struct channel_info *channel_info) -{ - rcu_read_lock(); - cds_lfht_del(session_info->channel_infos_ht, - &channel_info->session_info_channels_ht_node); - rcu_read_unlock(); -} - -static -struct channel_info *channel_info_create(const char *channel_name, - struct channel_key *channel_key, uint64_t channel_capacity, - struct session_info *session_info) -{ - struct channel_info *channel_info = zmalloc(sizeof(*channel_info)); - - if (!channel_info) { - goto end; - } - - cds_lfht_node_init(&channel_info->channels_ht_node); - cds_lfht_node_init(&channel_info->session_info_channels_ht_node); - memcpy(&channel_info->key, channel_key, sizeof(*channel_key)); - channel_info->capacity = channel_capacity; - - channel_info->name = strdup(channel_name); - if (!channel_info->name) { - goto error; - } - - /* - * Set the references between session and channel infos: - * - channel_info holds a strong reference to session_info - * - session_info holds a weak reference to channel_info - */ - session_info_get(session_info); - session_info_add_channel(session_info, channel_info); - channel_info->session_info = session_info; -end: - return channel_info; -error: - channel_info_destroy(channel_info); - return NULL; -} - -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; - - lttng_condition_put(list->condition); - - 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->clients_list, node) { - free(client_list_element); - } - - LTTNG_ASSERT(cds_list_empty(&list->triggers_list)); - - pthread_mutex_destroy(&list->lock); - call_rcu(&list->rcu_node, free_notification_client_list_rcu); -} - -static -bool condition_applies_to_client(const struct lttng_condition *condition, - struct notification_client *client) -{ - bool applies = false; - struct lttng_condition_list_element *condition_list_element; - - cds_list_for_each_entry(condition_list_element, &client->condition_list, - node) { - applies = lttng_condition_is_equal( - condition_list_element->condition, - condition); - if (applies) { - break; - } - } - - return applies; -} - -static -struct notification_client_list *notification_client_list_create( - struct notification_thread_state *state, - const struct lttng_condition *condition) -{ - struct notification_client *client; - struct cds_lfht_iter iter; - struct notification_client_list *client_list; - - client_list = zmalloc(sizeof(*client_list)); - if (!client_list) { - PERROR("Failed to allocate notification client list"); - goto end; - } - - pthread_mutex_init(&client_list->lock, NULL); - /* - * The trigger that owns the condition has the first reference to this - * client list. - */ - urcu_ref_init(&client_list->ref); - cds_lfht_node_init(&client_list->notification_trigger_clients_ht_node); - CDS_INIT_LIST_HEAD(&client_list->clients_list); - CDS_INIT_LIST_HEAD(&client_list->triggers_list); - - /* - * Create a copy of the condition so that it's independent of any - * trigger. The client list may outlive the trigger object (which owns - * the condition) that is used to create it. - */ - client_list->condition = lttng_condition_copy(condition); - - /* Build a list of clients to which this new condition applies. */ - cds_lfht_for_each_entry (state->client_socket_ht, &iter, client, - client_socket_ht_node) { - struct notification_client_list_element *client_list_element; - - if (!condition_applies_to_client(condition, client)) { - continue; - } - - client_list_element = zmalloc(sizeof(*client_list_element)); - if (!client_list_element) { - 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->clients_list); - } - - client_list->notification_trigger_clients_ht = - state->notification_trigger_clients_ht; - - rcu_read_lock(); - /* - * Add the client list to the global list of client list. - */ - cds_lfht_add_unique(state->notification_trigger_clients_ht, - lttng_condition_hash(client_list->condition), - match_client_list_condition, - client_list->condition, - &client_list->notification_trigger_clients_ht_node); - rcu_read_unlock(); - goto end; - -error_put_client_list: - notification_client_list_put(client_list); - client_list = NULL; - -end: - return client_list; -} - -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, - const struct lttng_condition *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); - 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; -} - -static -int evaluate_channel_condition_for_client( - const struct lttng_condition *condition, - struct notification_thread_state *state, - struct lttng_evaluation **evaluation, - uid_t *session_uid, gid_t *session_gid) -{ - int ret; - struct cds_lfht_iter iter; - struct cds_lfht_node *node; - struct channel_info *channel_info = NULL; - struct channel_key *channel_key = NULL; - 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) { - struct lttng_trigger_list_element *element; - - cds_list_for_each_entry(element, &channel_trigger_list->list, node) { - const struct lttng_condition *current_condition = - lttng_trigger_get_const_condition( - element->trigger); - - LTTNG_ASSERT(current_condition); - if (!lttng_condition_is_equal(condition, - current_condition)) { - continue; - } - - /* Found the trigger, save the channel key. */ - channel_key = &channel_trigger_list->channel_key; - break; - } - if (channel_key) { - /* The channel key was found stop iteration. */ - break; - } - } - - if (!channel_key){ - /* No channel found; normal exit. */ - DBG("No known channel associated with newly subscribed-to condition"); - ret = 0; - goto end; - } - - /* Fetch channel info for the matching channel. */ - cds_lfht_lookup(state->channels_ht, - hash_channel_key(channel_key), - match_channel_info, - channel_key, - &iter); - node = cds_lfht_iter_get_node(&iter); - LTTNG_ASSERT(node); - channel_info = caa_container_of(node, struct channel_info, - channels_ht_node); - - /* Retrieve the channel's last sample, if it exists. */ - cds_lfht_lookup(state->channel_state_ht, - hash_channel_key(channel_key), - match_channel_state_sample, - channel_key, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (node) { - last_sample = caa_container_of(node, - struct channel_state_sample, - channel_state_ht_node); - } else { - /* Nothing to evaluate, no sample was ever taken. Normal exit */ - DBG("No channel sample associated with newly subscribed-to condition"); - ret = 0; - goto end; - } - - ret = evaluate_buffer_condition(condition, evaluation, state, - NULL, last_sample, - 0, channel_info->session_info->consumed_data_size, - channel_info); - if (ret) { - WARN("Fatal error occurred while evaluating a newly subscribed-to condition"); - goto end; - } - - *session_uid = channel_info->session_info->uid; - *session_gid = channel_info->session_info->gid; -end: - rcu_read_unlock(); - return ret; -} - -static -const char *get_condition_session_name(const struct lttng_condition *condition) -{ - const char *session_name = NULL; - enum lttng_condition_status status; - - switch (lttng_condition_get_type(condition)) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - status = lttng_condition_buffer_usage_get_session_name( - condition, &session_name); - break; - case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: - status = lttng_condition_session_consumed_size_get_session_name( - condition, &session_name); - break; - case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: - case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: - status = lttng_condition_session_rotation_get_session_name( - condition, &session_name); - break; - default: - abort(); - } - if (status != LTTNG_CONDITION_STATUS_OK) { - ERR("Failed to retrieve session rotation condition's session name"); - goto end; - } -end: - return session_name; -} - -static -int evaluate_session_condition_for_client( - const struct lttng_condition *condition, - struct notification_thread_state *state, - struct lttng_evaluation **evaluation, - uid_t *session_uid, gid_t *session_gid) -{ - int ret; - struct cds_lfht_iter iter; - struct cds_lfht_node *node; - 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. */ - cds_lfht_lookup(state->sessions_ht, - hash_key_str(session_name, lttng_ht_seed), - match_session, - session_name, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (!node) { - DBG("No known session matching name \"%s\"", - session_name); - ret = 0; - goto end; - } - - session_info = caa_container_of(node, struct session_info, - sessions_ht_node); - session_info_get(session_info); - - /* - * Evaluation is performed in-line here since only one type of - * session-bound condition is handled for the moment. - */ - switch (lttng_condition_get_type(condition)) { - case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: - if (!session_info->rotation.ongoing) { - ret = 0; - goto end_session_put; - } - - *evaluation = lttng_evaluation_session_rotation_ongoing_create( - session_info->rotation.id); - if (!*evaluation) { - /* Fatal error. */ - ERR("Failed to create session rotation ongoing evaluation for session \"%s\"", - session_info->name); - ret = -1; - goto end_session_put; - } - ret = 0; - break; - default: - ret = 0; - goto end_session_put; - } - - *session_uid = session_info->uid; - *session_gid = session_info->gid; - -end_session_put: - session_info_put(session_info); -end: - rcu_read_unlock(); - return ret; -} - -static -int evaluate_condition_for_client(const struct lttng_trigger *trigger, - const struct lttng_condition *condition, - struct notification_client *client, - struct notification_thread_state *state) -{ - int ret; - struct lttng_evaluation *evaluation = NULL; - 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; - - LTTNG_ASSERT(trigger); - LTTNG_ASSERT(condition); - LTTNG_ASSERT(client); - LTTNG_ASSERT(state); - - switch (get_condition_binding_object(condition)) { - case LTTNG_OBJECT_TYPE_SESSION: - ret = evaluate_session_condition_for_client(condition, state, - &evaluation, &object_uid, &object_gid); - break; - case LTTNG_OBJECT_TYPE_CHANNEL: - ret = evaluate_channel_condition_for_client(condition, state, - &evaluation, &object_uid, &object_gid); - break; - case LTTNG_OBJECT_TYPE_NONE: - DBG("Newly subscribed-to condition not bound to object, nothing to evaluate"); - ret = 0; - goto end; - case LTTNG_OBJECT_TYPE_UNKNOWN: - default: - ret = -1; - goto end; - } - if (ret) { - /* Fatal error. */ - goto end; - } - if (!evaluation) { - /* Evaluation yielded nothing. Normal exit. */ - DBG("Newly subscribed-to condition evaluated to false, nothing to report to client"); - ret = 0; - goto end; - } - - /* - * Create a temporary client list with the client currently - * subscribing. - */ - cds_lfht_node_init(&client_list.notification_trigger_clients_ht_node); - CDS_INIT_LIST_HEAD(&client_list.clients_list); - - CDS_INIT_LIST_HEAD(&client_list_element.node); - client_list_element.client = client; - cds_list_add(&client_list_element.node, &client_list.clients_list); - - /* Send evaluation result to the newly-subscribed client. */ - DBG("Newly subscribed-to condition evaluated to true, notifying client"); - ret = send_evaluation_to_clients(trigger, evaluation, &client_list, - state, object_uid, object_gid); - -end: - return ret; -} - -static -int notification_thread_client_subscribe(struct notification_client *client, - struct lttng_condition *condition, - struct notification_thread_state *state, - enum lttng_notification_channel_status *_status) -{ - int ret = 0; - 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; - struct lttng_trigger_ht_element *trigger_ht_element; - enum lttng_notification_channel_status status = - LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; - - /* - * Ensure that the client has not already subscribed to this condition - * before. - */ - cds_list_for_each_entry(condition_list_element, &client->condition_list, node) { - if (lttng_condition_is_equal(condition_list_element->condition, - condition)) { - status = LTTNG_NOTIFICATION_CHANNEL_STATUS_ALREADY_SUBSCRIBED; - goto end; - } - } - - condition_list_element = zmalloc(sizeof(*condition_list_element)); - if (!condition_list_element) { - ret = -1; - goto error; - } - client_list_element = zmalloc(sizeof(*client_list_element)); - if (!client_list_element) { - ret = -1; - goto error; - } - - /* - * Add the newly-subscribed condition to the client's subscription list. - */ - CDS_INIT_LIST_HEAD(&condition_list_element->node); - condition_list_element->condition = condition; - condition = NULL; - cds_list_add(&condition_list_element->node, &client->condition_list); - - client_list = get_client_list_from_condition( - state, condition_list_element->condition); - if (!client_list) { - /* - * No notification-emiting trigger registered with this - * condition. We don't evaluate the condition right away - * since this trigger is not registered yet. - */ - free(client_list_element); - 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. - * - * Note the iteration on all triggers which share an identical - * `condition` than the one to which the client is registering. This is - * done to ensure that the client receives a distinct notification for - * all triggers that have a `notify` action that have this condition. - */ - pthread_mutex_lock(&client_list->lock); - cds_list_for_each_entry(trigger_ht_element, - &client_list->triggers_list, client_list_trigger_node) { - if (evaluate_condition_for_client(trigger_ht_element->trigger, condition_list_element->condition, - client, state)) { - WARN("Evaluation of a condition on client subscription failed, aborting."); - ret = -1; - free(client_list_element); - pthread_mutex_unlock(&client_list->lock); - goto end; - } - } - pthread_mutex_unlock(&client_list->lock); - - /* - * Add the client to the list of clients interested in a given trigger - * if a "notification" trigger with a corresponding condition was - * added prior. - */ - 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->clients_list); - pthread_mutex_unlock(&client_list->lock); -end: - if (_status) { - *_status = status; - } - if (client_list) { - notification_client_list_put(client_list); - } - lttng_condition_destroy(condition); - return ret; -error: - free(condition_list_element); - free(client_list_element); - lttng_condition_destroy(condition); - return ret; -} - -static -int notification_thread_client_unsubscribe( - struct notification_client *client, - struct lttng_condition *condition, - struct notification_thread_state *state, - enum lttng_notification_channel_status *_status) -{ - struct notification_client_list *client_list; - struct lttng_condition_list_element *condition_list_element, - *condition_tmp; - struct notification_client_list_element *client_list_element, - *client_tmp; - bool condition_found = false; - enum lttng_notification_channel_status status = - LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; - - /* Remove the condition from the client's condition list. */ - cds_list_for_each_entry_safe(condition_list_element, condition_tmp, - &client->condition_list, node) { - if (!lttng_condition_is_equal(condition_list_element->condition, - condition)) { - continue; - } - - cds_list_del(&condition_list_element->node); - /* - * The caller may be iterating on the client's conditions to - * tear down a client's connection. In this case, the condition - * will be destroyed at the end. - */ - if (condition != condition_list_element->condition) { - lttng_condition_destroy( - condition_list_element->condition); - } - free(condition_list_element); - condition_found = true; - break; - } - - if (!condition_found) { - status = LTTNG_NOTIFICATION_CHANNEL_STATUS_UNKNOWN_CONDITION; - goto end; - } - - /* - * Remove the client from the list of clients interested the trigger - * matching the condition. - */ - client_list = get_client_list_from_condition(state, condition); - if (!client_list) { - goto end; - } - - pthread_mutex_lock(&client_list->lock); - cds_list_for_each_entry_safe(client_list_element, client_tmp, - &client_list->clients_list, node) { - if (client_list_element->client->id != client->id) { - continue; - } - cds_list_del(&client_list_element->node); - free(client_list_element); - break; - } - pthread_mutex_unlock(&client_list->lock); - notification_client_list_put(client_list); - client_list = NULL; -end: - lttng_condition_destroy(condition); - if (_status) { - *_status = status; - } - return 0; -} - -static -void free_notification_client_rcu(struct rcu_head *node) -{ - free(caa_container_of(node, struct notification_client, rcu_node)); -} - -static -void notification_client_destroy(struct notification_client *client, - struct notification_thread_state *state) -{ - if (!client) { - return; - } - - /* - * 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_payload_reset(&client->communication.inbound.payload); - lttng_payload_reset(&client->communication.outbound.payload); - pthread_mutex_destroy(&client->lock); - call_rcu(&client->rcu_node, free_notification_client_rcu); -} - -/* - * Call with rcu_read_lock held (and hold for the lifetime of the returned - * client pointer). - */ -static -struct notification_client *get_client_from_socket(int socket, - 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_socket_ht, - hash_client_socket(socket), - match_client_socket, - (void *) (unsigned long) socket, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (!node) { - goto end; - } - - client = caa_container_of(node, struct notification_client, - client_socket_ht_node); -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, - const struct channel_info *channel_info) -{ - enum lttng_condition_status status; - enum lttng_domain_type condition_domain; - const char *condition_session_name = NULL; - const char *condition_channel_name = NULL; - - status = lttng_condition_buffer_usage_get_domain_type(condition, - &condition_domain); - LTTNG_ASSERT(status == LTTNG_CONDITION_STATUS_OK); - if (channel_info->key.domain != condition_domain) { - goto fail; - } - - status = lttng_condition_buffer_usage_get_session_name( - condition, &condition_session_name); - LTTNG_ASSERT((status == LTTNG_CONDITION_STATUS_OK) && condition_session_name); - - status = lttng_condition_buffer_usage_get_channel_name( - condition, &condition_channel_name); - LTTNG_ASSERT((status == LTTNG_CONDITION_STATUS_OK) && condition_channel_name); - - if (strcmp(channel_info->session_info->name, condition_session_name)) { - goto fail; - } - if (strcmp(channel_info->name, condition_channel_name)) { - goto fail; - } - - return true; -fail: - return false; -} - -static -bool session_consumed_size_condition_applies_to_channel( - const struct lttng_condition *condition, - const struct channel_info *channel_info) -{ - enum lttng_condition_status status; - const char *condition_session_name = NULL; - - status = lttng_condition_session_consumed_size_get_session_name( - condition, &condition_session_name); - LTTNG_ASSERT((status == LTTNG_CONDITION_STATUS_OK) && condition_session_name); - - if (strcmp(channel_info->session_info->name, condition_session_name)) { - goto fail; - } - - return true; -fail: - return false; -} - -static -bool trigger_applies_to_channel(const struct lttng_trigger *trigger, - const struct channel_info *channel_info) -{ - const struct lttng_condition *condition; - bool trigger_applies; - - condition = lttng_trigger_get_const_condition(trigger); - if (!condition) { - goto fail; - } - - switch (lttng_condition_get_type(condition)) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - trigger_applies = buffer_usage_condition_applies_to_channel( - condition, channel_info); - break; - case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: - trigger_applies = session_consumed_size_condition_applies_to_channel( - condition, channel_info); - break; - default: - goto fail; - } - - return trigger_applies; -fail: - return false; -} - -/* Must be called with RCU read lock held. */ -static -struct lttng_session_trigger_list *get_session_trigger_list( - struct notification_thread_state *state, - const char *session_name) -{ - struct lttng_session_trigger_list *list = NULL; - struct cds_lfht_node *node; - struct cds_lfht_iter iter; - - cds_lfht_lookup(state->session_triggers_ht, - hash_key_str(session_name, lttng_ht_seed), - match_session_trigger_list, - session_name, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (!node) { - /* - * Not an error, the list of triggers applying to that session - * will be initialized when the session is created. - */ - DBG("No trigger list found for session \"%s\" as it is not yet known to the notification system", - session_name); - goto end; - } - - list = caa_container_of(node, - struct lttng_session_trigger_list, - session_triggers_ht_node); -end: - return list; -} - -/* - * Allocate an empty lttng_session_trigger_list for the session named - * 'session_name'. - * - * No ownership of 'session_name' is assumed by the session trigger list. - * It is the caller's responsability to ensure the session name is alive - * for as long as this list is. - */ -static -struct lttng_session_trigger_list *lttng_session_trigger_list_create( - const char *session_name, - struct cds_lfht *session_triggers_ht) -{ - struct lttng_session_trigger_list *list; - - list = zmalloc(sizeof(*list)); - if (!list) { - goto end; - } - list->session_name = session_name; - CDS_INIT_LIST_HEAD(&list->list); - cds_lfht_node_init(&list->session_triggers_ht_node); - list->session_triggers_ht = session_triggers_ht; - - rcu_read_lock(); - /* Publish the list through the session_triggers_ht. */ - cds_lfht_add(session_triggers_ht, - hash_key_str(session_name, lttng_ht_seed), - &list->session_triggers_ht_node); - rcu_read_unlock(); -end: - return list; -} - -static -void free_session_trigger_list_rcu(struct rcu_head *node) -{ - free(caa_container_of(node, struct lttng_session_trigger_list, - rcu_node)); -} - -static -void lttng_session_trigger_list_destroy(struct lttng_session_trigger_list *list) -{ - struct lttng_trigger_list_element *trigger_list_element, *tmp; - - /* Empty the list element by element, and then free the list itself. */ - cds_list_for_each_entry_safe(trigger_list_element, tmp, - &list->list, node) { - cds_list_del(&trigger_list_element->node); - free(trigger_list_element); - } - rcu_read_lock(); - /* Unpublish the list from the session_triggers_ht. */ - cds_lfht_del(list->session_triggers_ht, - &list->session_triggers_ht_node); - rcu_read_unlock(); - call_rcu(&list->rcu_node, free_session_trigger_list_rcu); -} - -static -int lttng_session_trigger_list_add(struct lttng_session_trigger_list *list, - struct lttng_trigger *trigger) -{ - int ret = 0; - struct lttng_trigger_list_element *new_element = - zmalloc(sizeof(*new_element)); - - if (!new_element) { - ret = -1; - goto end; - } - CDS_INIT_LIST_HEAD(&new_element->node); - new_element->trigger = trigger; - cds_list_add(&new_element->node, &list->list); -end: - return ret; -} - -static -bool trigger_applies_to_session(const struct lttng_trigger *trigger, - const char *session_name) -{ - bool applies = false; - const struct lttng_condition *condition; - - 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 condition_status; - const char *condition_session_name; - - condition_status = lttng_condition_session_rotation_get_session_name( - condition, &condition_session_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - ERR("Failed to retrieve session rotation condition's session name"); - goto end; - } - - LTTNG_ASSERT(condition_session_name); - applies = !strcmp(condition_session_name, session_name); - break; - } - default: - goto end; - } -end: - return applies; -} - -/* - * Allocate and initialize an lttng_session_trigger_list which contains - * all triggers that apply to the session named 'session_name'. - * - * No ownership of 'session_name' is assumed by the session trigger list. - * It is the caller's responsability to ensure the session name is alive - * for as long as this list is. - */ -static -struct lttng_session_trigger_list *lttng_session_trigger_list_build( - const struct notification_thread_state *state, - const char *session_name) -{ - int trigger_count = 0; - struct lttng_session_trigger_list *session_trigger_list = NULL; - struct lttng_trigger_ht_element *trigger_ht_element = NULL; - struct cds_lfht_iter iter; - - session_trigger_list = lttng_session_trigger_list_create(session_name, - state->session_triggers_ht); - - /* Add all triggers applying to the session named 'session_name'. */ - cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element, - node) { - int ret; - - if (!trigger_applies_to_session(trigger_ht_element->trigger, - session_name)) { - continue; - } - - ret = lttng_session_trigger_list_add(session_trigger_list, - trigger_ht_element->trigger); - if (ret) { - goto error; - } - - trigger_count++; - } - - DBG("Found %i triggers that apply to newly created session", - trigger_count); - return session_trigger_list; -error: - lttng_session_trigger_list_destroy(session_trigger_list); - return NULL; -} - -static -struct session_info *find_or_create_session_info( - struct notification_thread_state *state, - const char *name, uid_t uid, gid_t gid) -{ - struct session_info *session = NULL; - struct cds_lfht_node *node; - struct cds_lfht_iter iter; - struct lttng_session_trigger_list *trigger_list; - - rcu_read_lock(); - cds_lfht_lookup(state->sessions_ht, - hash_key_str(name, lttng_ht_seed), - match_session, - name, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (node) { - DBG("Found session info of session \"%s\" (uid = %i, gid = %i)", - name, uid, gid); - session = caa_container_of(node, struct session_info, - sessions_ht_node); - LTTNG_ASSERT(session->uid == uid); - LTTNG_ASSERT(session->gid == gid); - session_info_get(session); - goto end; - } - - trigger_list = lttng_session_trigger_list_build(state, name); - if (!trigger_list) { - goto error; - } - - session = session_info_create(name, uid, gid, trigger_list, - state->sessions_ht); - if (!session) { - ERR("Failed to allocation session info for session \"%s\" (uid = %i, gid = %i)", - name, uid, gid); - lttng_session_trigger_list_destroy(trigger_list); - goto error; - } - trigger_list = NULL; - - cds_lfht_add(state->sessions_ht, hash_key_str(name, lttng_ht_seed), - &session->sessions_ht_node); -end: - rcu_read_unlock(); - return session; -error: - rcu_read_unlock(); - session_info_put(session); - return NULL; -} - -static -int handle_notification_thread_command_add_channel( - struct notification_thread_state *state, - const char *session_name, uid_t session_uid, gid_t session_gid, - const char *channel_name, enum lttng_domain_type channel_domain, - uint64_t channel_key_int, uint64_t channel_capacity, - enum lttng_error_code *cmd_result) -{ - struct cds_list_head trigger_list; - struct channel_info *new_channel_info = NULL; - struct channel_key channel_key = { - .key = channel_key_int, - .domain = channel_domain, - }; - struct lttng_channel_trigger_list *channel_trigger_list = NULL; - struct lttng_trigger_ht_element *trigger_ht_element = NULL; - int trigger_count = 0; - struct cds_lfht_iter iter; - struct session_info *session_info = NULL; - - DBG("Adding channel %s from session %s, channel key = %" PRIu64 " in %s domain", - channel_name, session_name, channel_key_int, - lttng_domain_type_str(channel_domain)); - - CDS_INIT_LIST_HEAD(&trigger_list); - - session_info = find_or_create_session_info(state, session_name, - session_uid, session_gid); - if (!session_info) { - /* Allocation error or an internal error occurred. */ - goto error; - } - - new_channel_info = channel_info_create(channel_name, &channel_key, - channel_capacity, session_info); - if (!new_channel_info) { - goto error; - } - - rcu_read_lock(); - /* Build a list of all triggers applying to the new channel. */ - cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element, - node) { - struct lttng_trigger_list_element *new_element; - - if (!trigger_applies_to_channel(trigger_ht_element->trigger, - new_channel_info)) { - continue; - } - - new_element = zmalloc(sizeof(*new_element)); - if (!new_element) { - rcu_read_unlock(); - goto error; - } - CDS_INIT_LIST_HEAD(&new_element->node); - new_element->trigger = trigger_ht_element->trigger; - cds_list_add(&new_element->node, &trigger_list); - trigger_count++; - } - rcu_read_unlock(); - - DBG("Found %i triggers that apply to newly added channel", - trigger_count); - channel_trigger_list = zmalloc(sizeof(*channel_trigger_list)); - if (!channel_trigger_list) { - goto error; - } - channel_trigger_list->channel_key = new_channel_info->key; - CDS_INIT_LIST_HEAD(&channel_trigger_list->list); - cds_lfht_node_init(&channel_trigger_list->channel_triggers_ht_node); - cds_list_splice(&trigger_list, &channel_trigger_list->list); - - rcu_read_lock(); - /* Add channel to the channel_ht which owns the channel_infos. */ - cds_lfht_add(state->channels_ht, - hash_channel_key(&new_channel_info->key), - &new_channel_info->channels_ht_node); - /* - * Add the list of triggers associated with this channel to the - * channel_triggers_ht. - */ - cds_lfht_add(state->channel_triggers_ht, - hash_channel_key(&new_channel_info->key), - &channel_trigger_list->channel_triggers_ht_node); - rcu_read_unlock(); - session_info_put(session_info); - *cmd_result = LTTNG_OK; - return 0; -error: - channel_info_destroy(new_channel_info); - session_info_put(session_info); - return 1; -} - -static -void free_channel_trigger_list_rcu(struct rcu_head *node) -{ - free(caa_container_of(node, struct lttng_channel_trigger_list, - rcu_node)); -} - -static -void free_channel_state_sample_rcu(struct rcu_head *node) -{ - free(caa_container_of(node, struct channel_state_sample, - rcu_node)); -} - -static -int handle_notification_thread_command_remove_channel( - struct notification_thread_state *state, - uint64_t channel_key, enum lttng_domain_type domain, - enum lttng_error_code *cmd_result) -{ - struct cds_lfht_node *node; - struct cds_lfht_iter iter; - struct lttng_channel_trigger_list *trigger_list; - struct lttng_trigger_list_element *trigger_list_element, *tmp; - struct channel_key key = { .key = channel_key, .domain = domain }; - struct channel_info *channel_info; - - DBG("Removing channel key = %" PRIu64 " in %s domain", - channel_key, lttng_domain_type_str(domain)); - - rcu_read_lock(); - - cds_lfht_lookup(state->channel_triggers_ht, - hash_channel_key(&key), - match_channel_trigger_list, - &key, - &iter); - node = cds_lfht_iter_get_node(&iter); - /* - * There is a severe internal error if we are being asked to remove a - * channel that doesn't exist. - */ - if (!node) { - ERR("Channel being removed is unknown to the notification thread"); - goto end; - } - - /* Free the list of triggers associated with this channel. */ - trigger_list = caa_container_of(node, struct lttng_channel_trigger_list, - channel_triggers_ht_node); - cds_list_for_each_entry_safe(trigger_list_element, tmp, - &trigger_list->list, node) { - cds_list_del(&trigger_list_element->node); - free(trigger_list_element); - } - cds_lfht_del(state->channel_triggers_ht, node); - call_rcu(&trigger_list->rcu_node, free_channel_trigger_list_rcu); - - /* Free sampled channel state. */ - cds_lfht_lookup(state->channel_state_ht, - hash_channel_key(&key), - match_channel_state_sample, - &key, - &iter); - node = cds_lfht_iter_get_node(&iter); - /* - * This is expected to be NULL if the channel is destroyed before we - * received a sample. - */ - if (node) { - struct channel_state_sample *sample = caa_container_of(node, - struct channel_state_sample, - channel_state_ht_node); - - cds_lfht_del(state->channel_state_ht, node); - call_rcu(&sample->rcu_node, free_channel_state_sample_rcu); - } - - /* Remove the channel from the channels_ht and free it. */ - cds_lfht_lookup(state->channels_ht, - hash_channel_key(&key), - match_channel_info, - &key, - &iter); - node = cds_lfht_iter_get_node(&iter); - LTTNG_ASSERT(node); - channel_info = caa_container_of(node, struct channel_info, - channels_ht_node); - cds_lfht_del(state->channels_ht, node); - channel_info_destroy(channel_info); -end: - rcu_read_unlock(); - *cmd_result = LTTNG_OK; - return 0; -} - -static -int handle_notification_thread_command_session_rotation( - struct notification_thread_state *state, - enum notification_thread_command_type cmd_type, - const char *session_name, uid_t session_uid, gid_t session_gid, - uint64_t trace_archive_chunk_id, - struct lttng_trace_archive_location *location, - enum lttng_error_code *_cmd_result) -{ - int ret = 0; - enum lttng_error_code cmd_result = LTTNG_OK; - struct lttng_session_trigger_list *trigger_list; - struct lttng_trigger_list_element *trigger_list_element; - struct session_info *session_info; - const struct lttng_credentials session_creds = { - .uid = LTTNG_OPTIONAL_INIT_VALUE(session_uid), - .gid = LTTNG_OPTIONAL_INIT_VALUE(session_gid), - }; - - rcu_read_lock(); - - session_info = find_or_create_session_info(state, session_name, - session_uid, session_gid); - if (!session_info) { - /* Allocation error or an internal error occurred. */ - ret = -1; - cmd_result = LTTNG_ERR_NOMEM; - goto end; - } - - session_info->rotation.ongoing = - cmd_type == NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING; - session_info->rotation.id = trace_archive_chunk_id; - trigger_list = get_session_trigger_list(state, session_name); - if (!trigger_list) { - DBG("No triggers applying to session \"%s\" found", - session_name); - goto end; - } - - cds_list_for_each_entry(trigger_list_element, &trigger_list->list, - node) { - const struct lttng_condition *condition; - struct lttng_trigger *trigger; - struct notification_client_list *client_list; - struct lttng_evaluation *evaluation = NULL; - enum lttng_condition_type condition_type; - enum action_executor_status executor_status; - - trigger = trigger_list_element->trigger; - condition = lttng_trigger_get_const_condition(trigger); - LTTNG_ASSERT(condition); - condition_type = lttng_condition_get_type(condition); - - if (condition_type == LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING && - cmd_type != NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING) { - continue; - } else if (condition_type == LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED && - cmd_type != NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED) { - continue; - } - - client_list = get_client_list_from_condition(state, condition); - if (cmd_type == NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING) { - evaluation = lttng_evaluation_session_rotation_ongoing_create( - trace_archive_chunk_id); - } else { - evaluation = lttng_evaluation_session_rotation_completed_create( - trace_archive_chunk_id, location); - } - - if (!evaluation) { - /* Internal error */ - ret = -1; - cmd_result = LTTNG_ERR_UNK; - goto put_list; - } - - /* - * Ownership of `evaluation` transferred to the action executor - * no matter the result. - */ - executor_status = action_executor_enqueue_trigger( - state->executor, trigger, evaluation, - &session_creds, client_list); - evaluation = NULL; - switch (executor_status) { - case ACTION_EXECUTOR_STATUS_OK: - break; - case ACTION_EXECUTOR_STATUS_ERROR: - case ACTION_EXECUTOR_STATUS_INVALID: - /* - * TODO Add trigger identification (name/id) when - * it is added to the API. - */ - ERR("Fatal error occurred while enqueuing action associated with session rotation trigger"); - ret = -1; - goto put_list; - case ACTION_EXECUTOR_STATUS_OVERFLOW: - /* - * TODO Add trigger identification (name/id) when - * it is added to the API. - * - * Not a fatal error. - */ - WARN("No space left when enqueuing action associated with session rotation trigger"); - ret = 0; - goto put_list; - default: - abort(); - } - -put_list: - notification_client_list_put(client_list); - if (caa_unlikely(ret)) { - break; - } - } -end: - session_info_put(session_info); - *_cmd_result = cmd_result; - rcu_read_unlock(); - return ret; -} - -static -int handle_notification_thread_command_add_tracer_event_source( - struct notification_thread_state *state, - int tracer_event_source_fd, - enum lttng_domain_type domain_type, - enum lttng_error_code *_cmd_result) -{ - int ret = 0; - enum lttng_error_code cmd_result = LTTNG_OK; - struct notification_event_tracer_event_source_element *element = NULL; - - element = zmalloc(sizeof(*element)); - if (!element) { - cmd_result = LTTNG_ERR_NOMEM; - ret = -1; - goto end; - } - - element->fd = tracer_event_source_fd; - element->domain = domain_type; - - cds_list_add(&element->node, &state->tracer_event_sources_list); - - DBG3("Adding tracer event source fd to poll set: tracer_event_source_fd = %d, domain = '%s'", - tracer_event_source_fd, - lttng_domain_type_str(domain_type)); - - /* Adding the read side pipe to the event poll. */ - ret = lttng_poll_add(&state->events, tracer_event_source_fd, LPOLLIN | LPOLLERR); - if (ret < 0) { - ERR("Failed to add tracer event source to poll set: tracer_event_source_fd = %d, domain = '%s'", - tracer_event_source_fd, - lttng_domain_type_str(element->domain)); - cds_list_del(&element->node); - free(element); - goto end; - } - - element->is_fd_in_poll_set = true; - -end: - *_cmd_result = cmd_result; - return ret; -} - -static -int drain_event_notifier_notification_pipe( - struct notification_thread_state *state, - int pipe, enum lttng_domain_type domain) -{ - struct lttng_poll_event events = {0}; - int ret; - - ret = lttng_poll_create(&events, 1, LTTNG_CLOEXEC); - if (ret < 0) { - ERR("Error creating lttng_poll_event"); - goto end; - } - - ret = lttng_poll_add(&events, pipe, LPOLLIN); - if (ret < 0) { - ERR("Error adding fd event notifier notification pipe to lttng_poll_event: fd = %d", - pipe); - goto end; - } - - while (true) { - /* - * Continue to consume notifications as long as there are new - * ones coming in. The tracer has been asked to stop producing - * them. - * - * LPOLLIN is explicitly checked since LPOLLHUP is implicitly - * monitored (on Linux, at least) and will be returned when - * the pipe is closed but empty. - */ - ret = lttng_poll_wait_interruptible(&events, 0); - if (ret == 0 || (LTTNG_POLL_GETEV(&events, 0) & LPOLLIN) == 0) { - /* No more notification to be read on this pipe. */ - ret = 0; - goto end; - } else if (ret < 0) { - PERROR("Failed on lttng_poll_wait_interruptible() call"); - ret = -1; - goto end; - } - - ret = handle_one_event_notifier_notification(state, pipe, domain); - if (ret) { - ERR("Error consuming an event notifier notification from pipe: fd = %d", - pipe); - } - } -end: - lttng_poll_clean(&events); - return ret; -} - -static -struct notification_event_tracer_event_source_element * -find_tracer_event_source_element(struct notification_thread_state *state, - int tracer_event_source_fd) -{ - struct notification_event_tracer_event_source_element *source_element; - - cds_list_for_each_entry(source_element, - &state->tracer_event_sources_list, node) { - if (source_element->fd == tracer_event_source_fd) { - goto end; - } - } - - source_element = NULL; -end: - return source_element; -} - -static -int remove_tracer_event_source_from_pollset( - struct notification_thread_state *state, - struct notification_event_tracer_event_source_element *source_element) -{ - int ret = 0; - - LTTNG_ASSERT(source_element->is_fd_in_poll_set); - - DBG3("Removing tracer event source from poll set: tracer_event_source_fd = %d, domain = '%s'", - source_element->fd, - lttng_domain_type_str(source_element->domain)); - - /* Removing the fd from the event poll set. */ - ret = lttng_poll_del(&state->events, source_element->fd); - if (ret < 0) { - ERR("Failed to remove tracer event source from poll set: tracer_event_source_fd = %d, domain = '%s'", - source_element->fd, - lttng_domain_type_str(source_element->domain)); - ret = -1; - goto end; - } - - source_element->is_fd_in_poll_set = false; - - /* - * Force the notification thread to restart the poll() loop to ensure - * that any events from the removed fd are removed. - */ - state->restart_poll = true; - - ret = drain_event_notifier_notification_pipe(state, source_element->fd, - source_element->domain); - if (ret) { - ERR("Error draining event notifier notification: tracer_event_source_fd = %d, domain = %s", - source_element->fd, - lttng_domain_type_str(source_element->domain)); - ret = -1; - goto end; - } - -end: - return ret; -} - -int handle_notification_thread_tracer_event_source_died( - struct notification_thread_state *state, - int tracer_event_source_fd) -{ - int ret = 0; - struct notification_event_tracer_event_source_element *source_element; - - source_element = find_tracer_event_source_element(state, - tracer_event_source_fd); - - LTTNG_ASSERT(source_element); - - ret = remove_tracer_event_source_from_pollset(state, source_element); - if (ret) { - ERR("Failed to remove dead tracer event source from poll set"); - } - - return ret; -} - -static -int handle_notification_thread_command_remove_tracer_event_source( - struct notification_thread_state *state, - int tracer_event_source_fd, - enum lttng_error_code *_cmd_result) -{ - int ret = 0; - enum lttng_error_code cmd_result = LTTNG_OK; - struct notification_event_tracer_event_source_element *source_element = NULL; - - source_element = find_tracer_event_source_element(state, - tracer_event_source_fd); - - LTTNG_ASSERT(source_element); - - /* Remove the tracer source from the list. */ - cds_list_del(&source_element->node); - - if (!source_element->is_fd_in_poll_set) { - /* Skip the poll set removal. */ - goto end; - } - - ret = remove_tracer_event_source_from_pollset(state, source_element); - if (ret) { - ERR("Failed to remove tracer event source from poll set"); - cmd_result = LTTNG_ERR_FATAL; - } - -end: - free(source_element); - *_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 client_uid, - struct lttng_triggers **triggers, - enum lttng_error_code *_cmd_result) -{ - int ret = 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; - - rcu_read_lock(); - - local_triggers = lttng_triggers_create(); - if (!local_triggers) { - /* Not a fatal error. */ - cmd_result = LTTNG_ERR_NOMEM; - goto end; - } - - cds_lfht_for_each_entry(state->triggers_ht, &iter, - trigger_ht_element, node) { - /* - * Only return the triggers to which the client has access. - * The root user has visibility over all triggers. - */ - creds = lttng_trigger_get_credentials(trigger_ht_element->trigger); - if (client_uid != lttng_credentials_get_uid(creds) && client_uid != 0) { - continue; - } - - ret = lttng_triggers_add(local_triggers, - trigger_ht_element->trigger); - if (ret < 0) { - /* Not a fatal error. */ - ret = 0; - cmd_result = LTTNG_ERR_NOMEM; - goto end; - } - } - - /* Transferring ownership to the caller. */ - *triggers = local_triggers; - local_triggers = NULL; - -end: - rcu_read_unlock(); - lttng_triggers_destroy(local_triggers); - *_cmd_result = cmd_result; - return ret; -} - -static inline void get_trigger_info_for_log(const struct lttng_trigger *trigger, - const char **trigger_name, - uid_t *trigger_owner_uid) -{ - enum lttng_trigger_status trigger_status; - - trigger_status = lttng_trigger_get_name(trigger, trigger_name); - switch (trigger_status) { - case LTTNG_TRIGGER_STATUS_OK: - break; - case LTTNG_TRIGGER_STATUS_UNSET: - *trigger_name = "(anonymous)"; - break; - default: - abort(); - } - - trigger_status = lttng_trigger_get_owner_uid(trigger, - trigger_owner_uid); - LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); -} - -static int handle_notification_thread_command_get_trigger( - struct notification_thread_state *state, - const struct lttng_trigger *trigger, - struct lttng_trigger **registered_trigger, - enum lttng_error_code *_cmd_result) -{ - int ret = -1; - struct cds_lfht_iter iter; - struct lttng_trigger_ht_element *trigger_ht_element; - enum lttng_error_code cmd_result = LTTNG_ERR_TRIGGER_NOT_FOUND; - const char *trigger_name; - uid_t trigger_owner_uid; - - rcu_read_lock(); - - cds_lfht_for_each_entry( - state->triggers_ht, &iter, trigger_ht_element, node) { - if (lttng_trigger_is_equal( - trigger, trigger_ht_element->trigger)) { - /* Take one reference on the return trigger. */ - *registered_trigger = trigger_ht_element->trigger; - lttng_trigger_get(*registered_trigger); - ret = 0; - cmd_result = LTTNG_OK; - goto end; - } - } - - /* Not a fatal error if the trigger is not found. */ - get_trigger_info_for_log(trigger, &trigger_name, &trigger_owner_uid); - DBG("Failed to retrieve registered version of trigger: trigger name = '%s', trigger owner uid = %d", - trigger_name, (int) trigger_owner_uid); - - ret = 0; - -end: - rcu_read_unlock(); - *_cmd_result = cmd_result; - return ret; -} - -static -bool condition_is_supported(struct lttng_condition *condition) -{ - bool is_supported; - - switch (lttng_condition_get_type(condition)) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - { - int ret; - enum lttng_domain_type domain; - - ret = lttng_condition_buffer_usage_get_domain_type(condition, - &domain); - LTTNG_ASSERT(ret == 0); - - if (domain != LTTNG_DOMAIN_KERNEL) { - is_supported = true; - 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. - * - * Assume unsupported on error. - */ - is_supported = kernel_supports_ring_buffer_snapshot_sample_positions() == 1; - break; - } - case LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES: - { - const struct lttng_event_rule *event_rule; - enum lttng_domain_type domain; - const enum lttng_condition_status status = - lttng_condition_event_rule_matches_get_rule( - condition, &event_rule); - - LTTNG_ASSERT(status == LTTNG_CONDITION_STATUS_OK); - - domain = lttng_event_rule_get_domain_type(event_rule); - if (domain != LTTNG_DOMAIN_KERNEL) { - is_supported = true; - goto end; - } - - /* - * Older kernel tracers can't emit notification. Therefore, we - * reject triggers that require that mechanism to be available - * to be evaluated. - * - * Assume unsupported on error. - */ - is_supported = kernel_supports_event_notifiers() == 1; - break; - } - default: - is_supported = true; - } -end: - return is_supported; -} - -/* 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; - - status = lttng_condition_session_rotation_get_session_name( - condition, &session_name); - if (status != LTTNG_CONDITION_STATUS_OK) { - ERR("Failed to bind trigger to session: unable to get 'session_rotation' condition's session name"); - ret = -1; - goto end; - } - break; - } - default: - ret = -1; - goto end; - } - - trigger_list = get_session_trigger_list(state, session_name); - if (!trigger_list) { - DBG("Unable to bind trigger applying to session \"%s\" as it is not yet known to the notification system", - session_name); - goto end; - - } - - DBG("Newly registered trigger bound to session \"%s\"", - session_name); - ret = lttng_session_trigger_list_add(trigger_list, trigger); -end: - return ret; -} - -/* Must be called with RCU read lock held. */ -static -int bind_trigger_to_matching_channels(struct lttng_trigger *trigger, - struct notification_thread_state *state) -{ - int ret = 0; - struct cds_lfht_node *node; - struct cds_lfht_iter iter; - struct channel_info *channel; - - cds_lfht_for_each_entry(state->channels_ht, &iter, channel, - channels_ht_node) { - struct lttng_trigger_list_element *trigger_list_element; - struct lttng_channel_trigger_list *trigger_list; - struct cds_lfht_iter lookup_iter; - - if (!trigger_applies_to_channel(trigger, channel)) { - continue; - } - - cds_lfht_lookup(state->channel_triggers_ht, - hash_channel_key(&channel->key), - match_channel_trigger_list, - &channel->key, - &lookup_iter); - node = cds_lfht_iter_get_node(&lookup_iter); - LTTNG_ASSERT(node); - trigger_list = caa_container_of(node, - struct lttng_channel_trigger_list, - channel_triggers_ht_node); - - trigger_list_element = zmalloc(sizeof(*trigger_list_element)); - if (!trigger_list_element) { - ret = -1; - goto end; - } - CDS_INIT_LIST_HEAD(&trigger_list_element->node); - trigger_list_element->trigger = trigger; - cds_list_add(&trigger_list_element->node, &trigger_list->list); - DBG("Newly registered trigger bound to channel \"%s\"", - channel->name); - } -end: - return ret; -} - -static -bool is_trigger_action_notify(const struct lttng_trigger *trigger) -{ - bool is_notify = false; - unsigned int i, count; - enum lttng_action_status action_status; - const struct lttng_action *action = - lttng_trigger_get_const_action(trigger); - enum lttng_action_type action_type; - - LTTNG_ASSERT(action); - action_type = lttng_action_get_type(action); - if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { - is_notify = true; - goto end; - } else if (action_type != LTTNG_ACTION_TYPE_LIST) { - goto end; - } - - action_status = lttng_action_list_get_count(action, &count); - LTTNG_ASSERT(action_status == LTTNG_ACTION_STATUS_OK); - - for (i = 0; i < count; i++) { - const struct lttng_action *inner_action = - lttng_action_list_get_at_index( - action, i); - - action_type = lttng_action_get_type(inner_action); - if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { - is_notify = true; - goto end; - } - } - -end: - return is_notify; -} - -static bool trigger_name_taken(struct notification_thread_state *state, - const struct lttng_trigger *trigger) -{ - struct cds_lfht_iter iter; - - /* - * No duplicata is allowed in the triggers_by_name_uid_ht. - * The match is done against the trigger name and uid. - */ - cds_lfht_lookup(state->triggers_by_name_uid_ht, - hash_trigger_by_name_uid(trigger), - match_trigger_by_name_uid, - trigger, - &iter); - return !!cds_lfht_iter_get_node(&iter); -} - -static -enum lttng_error_code generate_trigger_name( - struct notification_thread_state *state, - struct lttng_trigger *trigger, const char **name) -{ - enum lttng_error_code ret_code = LTTNG_OK; - bool taken = false; - enum lttng_trigger_status status; - - do { - const int ret = lttng_trigger_generate_name(trigger, - state->trigger_id.name_offset++); - if (ret) { - /* The only reason this can fail right now. */ - ret_code = LTTNG_ERR_NOMEM; - break; - } - - status = lttng_trigger_get_name(trigger, name); - LTTNG_ASSERT(status == LTTNG_TRIGGER_STATUS_OK); - - taken = trigger_name_taken(state, trigger); - } while (taken || state->trigger_id.name_offset == UINT64_MAX); - - return ret_code; -} - -static inline -void notif_thread_state_remove_trigger_ht_elem( - struct notification_thread_state *state, - struct lttng_trigger_ht_element *trigger_ht_element) -{ - LTTNG_ASSERT(state); - LTTNG_ASSERT(trigger_ht_element); - - cds_lfht_del(state->triggers_ht, &trigger_ht_element->node); - cds_lfht_del(state->triggers_by_name_uid_ht, &trigger_ht_element->node_by_name_uid); -} - -static -enum lttng_error_code setup_tracer_notifier( - struct notification_thread_state *state, - struct lttng_trigger *trigger) -{ - enum lttng_error_code ret; - enum event_notifier_error_accounting_status error_accounting_status; - struct cds_lfht_node *node; - uint64_t error_counter_index = 0; - struct lttng_condition *condition = lttng_trigger_get_condition(trigger); - struct notification_trigger_tokens_ht_element *trigger_tokens_ht_element = NULL; - - trigger_tokens_ht_element = zmalloc(sizeof(*trigger_tokens_ht_element)); - if (!trigger_tokens_ht_element) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - - /* Add trigger token to the trigger_tokens_ht. */ - cds_lfht_node_init(&trigger_tokens_ht_element->node); - trigger_tokens_ht_element->token = LTTNG_OPTIONAL_GET(trigger->tracer_token); - 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) { - ret = LTTNG_ERR_TRIGGER_EXISTS; - goto error_free_ht_element; - } - - error_accounting_status = event_notifier_error_accounting_register_event_notifier( - trigger, &error_counter_index); - if (error_accounting_status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - if (error_accounting_status == EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NO_INDEX_AVAILABLE) { - DBG("Trigger list error accounting counter full."); - ret = LTTNG_ERR_EVENT_NOTIFIER_ERROR_ACCOUNTING_FULL; - } else { - ERR("Error registering trigger for error accounting"); - ret = LTTNG_ERR_EVENT_NOTIFIER_REGISTRATION; - } - - goto error_remove_ht_element; - } - - lttng_condition_event_rule_matches_set_error_counter_index( - condition, error_counter_index); - - ret = LTTNG_OK; - goto end; - -error_remove_ht_element: - cds_lfht_del(state->trigger_tokens_ht, &trigger_tokens_ht_element->node); -error_free_ht_element: - free(trigger_tokens_ht_element); -end: - return ret; -} - -/* - * FIXME A client's credentials are not checked when registering a 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, - bool is_trigger_anonymous, - enum lttng_error_code *cmd_result) -{ - int ret = 0; - struct lttng_condition *condition; - struct notification_client_list *client_list = NULL; - struct lttng_trigger_ht_element *trigger_ht_element = NULL; - struct cds_lfht_node *node; - const char* trigger_name; - bool free_trigger = true; - struct lttng_evaluation *evaluation = NULL; - struct lttng_credentials object_creds; - uid_t object_uid; - gid_t object_gid; - enum action_executor_status executor_status; - const uint64_t trigger_tracer_token = - state->trigger_id.next_tracer_token++; - - rcu_read_lock(); - - /* Set the trigger's tracer token. */ - lttng_trigger_set_tracer_token(trigger, trigger_tracer_token); - - if (!is_trigger_anonymous) { - if (lttng_trigger_get_name(trigger, &trigger_name) == - LTTNG_TRIGGER_STATUS_UNSET) { - const enum lttng_error_code ret_code = - generate_trigger_name(state, trigger, - &trigger_name); - - if (ret_code != LTTNG_OK) { - /* Fatal error. */ - ret = -1; - *cmd_result = ret_code; - goto error; - } - } else if (trigger_name_taken(state, trigger)) { - /* Not a fatal error. */ - *cmd_result = LTTNG_ERR_TRIGGER_EXISTS; - ret = 0; - goto error; - } - } else { - trigger_name = "(anonymous)"; - } - - condition = lttng_trigger_get_condition(trigger); - LTTNG_ASSERT(condition); - - /* Some conditions require tracers to implement a minimal ABI version. */ - if (!condition_is_supported(condition)) { - *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_uid); - 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_uid_ht, - hash_trigger_by_name_uid(trigger), - match_trigger_by_name_uid, - trigger, - &trigger_ht_element->node_by_name_uid); - if (node != &trigger_ht_element->node_by_name_uid) { - /* Internal error: add to triggers_ht should have failed. */ - ret = -1; - goto error_free_ht_element; - } - - /* From this point consider the trigger registered. */ - lttng_trigger_set_as_registered(trigger); - - /* - * Some triggers might need a tracer notifier depending on its - * condition and actions. - */ - if (lttng_trigger_needs_tracer_notifier(trigger)) { - enum lttng_error_code error_code; - - error_code = setup_tracer_notifier(state, trigger); - if (error_code != LTTNG_OK) { - notif_thread_state_remove_trigger_ht_elem(state, - trigger_ht_element); - if (error_code == LTTNG_ERR_NOMEM) { - ret = -1; - } else { - *cmd_result = error_code; - ret = 0; - } - - goto error_free_ht_element; - } - } - - /* - * The rest only applies to triggers that have a "notify" action. - * It is not skipped as this is the only action type currently - * supported. - */ - if (is_trigger_action_notify(trigger)) { - /* - * Find or create the client list of this condition. It may - * already be present if another trigger is already registered - * with the same condition. - */ - client_list = get_client_list_from_condition(state, condition); - if (!client_list) { - /* - * No client list for this condition yet. We create new - * one and build it up. - */ - client_list = notification_client_list_create(state, condition); - if (!client_list) { - ERR("Error creating notification client list for trigger %s", trigger->name); - goto error_free_ht_element; - } - } - - CDS_INIT_LIST_HEAD(&trigger_ht_element->client_list_trigger_node); - - pthread_mutex_lock(&client_list->lock); - cds_list_add(&trigger_ht_element->client_list_trigger_node, &client_list->triggers_list); - pthread_mutex_unlock(&client_list->lock); - } - - /* - * Ownership of the trigger and of its wrapper was transfered to - * the triggers_ht. Same for token ht element if necessary. - */ - trigger_ht_element = NULL; - free_trigger = false; - - 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_ht_element; - } - break; - case LTTNG_OBJECT_TYPE_CHANNEL: - /* - * Add the trigger to list of triggers bound to the channels - * currently known. - */ - ret = bind_trigger_to_matching_channels(trigger, state); - if (ret) { - goto error_free_ht_element; - } - break; - case LTTNG_OBJECT_TYPE_NONE: - break; - default: - ERR("Unknown object type on which to bind a newly registered trigger was encountered"); - ret = -1; - goto error_free_ht_element; - } - - /* - * The new trigger's condition must be evaluated against the current - * state. - * - * In the case of `notify` action, nothing preventing clients from - * subscribing to a condition before the corresponding trigger is - * registered, we have to evaluate this new condition right away. - * - * At some point, we were waiting for the next "evaluation" (e.g. on - * reception of a channel sample) to evaluate this new condition, but - * that was broken. - * - * The reason it was broken is that waiting for the next sample - * does not allow us to properly handle transitions for edge-triggered - * conditions. - * - * Consider this example: when we handle a new channel sample, we - * evaluate each conditions twice: once with the previous state, and - * again with the newest state. We then use those two results to - * determine whether a state change happened: a condition was false and - * became true. If a state change happened, we have to notify clients. - * - * Now, if a client subscribes to a given notification and registers - * a trigger *after* that subscription, we have to make sure the - * condition is evaluated at this point while considering only the - * 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. - */ - switch (get_condition_binding_object(condition)) { - case LTTNG_OBJECT_TYPE_SESSION: - ret = evaluate_session_condition_for_client(condition, state, - &evaluation, &object_uid, - &object_gid); - LTTNG_OPTIONAL_SET(&object_creds.uid, object_uid); - LTTNG_OPTIONAL_SET(&object_creds.gid, object_gid); - break; - case LTTNG_OBJECT_TYPE_CHANNEL: - ret = evaluate_channel_condition_for_client(condition, state, - &evaluation, &object_uid, - &object_gid); - LTTNG_OPTIONAL_SET(&object_creds.uid, object_uid); - LTTNG_OPTIONAL_SET(&object_creds.gid, object_gid); - break; - case LTTNG_OBJECT_TYPE_NONE: - ret = 0; - break; - case LTTNG_OBJECT_TYPE_UNKNOWN: - default: - ret = -1; - break; - } - - if (ret) { - /* Fatal error. */ - goto error_free_ht_element; - } - - DBG("Newly registered trigger's condition evaluated to %s", - evaluation ? "true" : "false"); - if (!evaluation) { - /* Evaluation yielded nothing. Normal exit. */ - ret = 0; - goto success; - } - - /* - * Ownership of `evaluation` transferred to the action executor - * no matter the result. - */ - executor_status = action_executor_enqueue_trigger(state->executor, - trigger, evaluation, &object_creds, client_list); - evaluation = NULL; - switch (executor_status) { - case ACTION_EXECUTOR_STATUS_OK: - break; - case ACTION_EXECUTOR_STATUS_ERROR: - case ACTION_EXECUTOR_STATUS_INVALID: - /* - * TODO Add trigger identification (name/id) when - * it is added to the API. - */ - ERR("Fatal error occurred while enqueuing action associated to newly registered trigger"); - ret = -1; - goto error_free_ht_element; - case ACTION_EXECUTOR_STATUS_OVERFLOW: - /* - * TODO Add trigger identification (name/id) when - * it is added to the API. - * - * Not a fatal error. - */ - WARN("No space left when enqueuing action associated to newly registered trigger"); - ret = 0; - goto success; - default: - abort(); - } - -success: - *cmd_result = LTTNG_OK; - DBG("Registered trigger: name = `%s`, tracer token = %" PRIu64, - trigger_name, trigger_tracer_token); - goto end; - -error_free_ht_element: - if (trigger_ht_element) { - /* Delayed removal due to RCU constraint on delete. */ - call_rcu(&trigger_ht_element->rcu_node, - free_lttng_trigger_ht_element_rcu); - } -error: - if (free_trigger) { - /* - * Other objects might have a reference to the trigger, mark it - * as unregistered. - */ - lttng_trigger_set_as_unregistered(trigger); - lttng_trigger_destroy(trigger); - } -end: - rcu_read_unlock(); - return ret; -} - -static -void free_lttng_trigger_ht_element_rcu(struct rcu_head *node) -{ - free(caa_container_of(node, struct lttng_trigger_ht_element, - rcu_node)); -} - -static -void free_notification_trigger_tokens_ht_element_rcu(struct rcu_head *node) -{ - free(caa_container_of(node, struct notification_trigger_tokens_ht_element, - rcu_node)); -} - -static -void teardown_tracer_notifier(struct notification_thread_state *state, - const struct lttng_trigger *trigger) -{ - struct cds_lfht_iter iter; - 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; - } - - event_notifier_error_accounting_unregister_event_notifier( - trigger_tokens_ht_element->trigger); - - /* TODO talk to all app and remove it */ - DBG("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; - } -} - -static -int handle_notification_thread_command_unregister_trigger( - struct notification_thread_state *state, - const struct lttng_trigger *trigger, - enum lttng_error_code *_cmd_reply) -{ - struct cds_lfht_iter iter; - struct cds_lfht_node *triggers_ht_node; - struct lttng_channel_trigger_list *trigger_list; - struct notification_client_list *client_list; - struct lttng_trigger_ht_element *trigger_ht_element = NULL; - const struct lttng_condition *condition = lttng_trigger_get_const_condition( - trigger); - enum lttng_error_code cmd_reply; - - rcu_read_lock(); - - cds_lfht_lookup(state->triggers_ht, - lttng_condition_hash(condition), - match_trigger, - trigger, - &iter); - triggers_ht_node = cds_lfht_iter_get_node(&iter); - if (!triggers_ht_node) { - cmd_reply = LTTNG_ERR_TRIGGER_NOT_FOUND; - goto end; - } else { - cmd_reply = LTTNG_OK; - } - - trigger_ht_element = caa_container_of(triggers_ht_node, - struct lttng_trigger_ht_element, node); - - /* Remove trigger from channel_triggers_ht. */ - cds_lfht_for_each_entry(state->channel_triggers_ht, &iter, trigger_list, - channel_triggers_ht_node) { - struct lttng_trigger_list_element *trigger_element, *tmp; - - cds_list_for_each_entry_safe(trigger_element, tmp, - &trigger_list->list, node) { - if (!lttng_trigger_is_equal(trigger, trigger_element->trigger)) { - continue; - } - - DBG("Removed trigger from channel_triggers_ht"); - cds_list_del(&trigger_element->node); - /* A trigger can only appear once per channel */ - break; - } - } - - if (lttng_trigger_needs_tracer_notifier(trigger)) { - teardown_tracer_notifier(state, trigger); - } - - if (is_trigger_action_notify(trigger)) { - /* - * Remove and release the client list from - * notification_trigger_clients_ht. - */ - client_list = get_client_list_from_condition(state, condition); - LTTNG_ASSERT(client_list); - - pthread_mutex_lock(&client_list->lock); - cds_list_del(&trigger_ht_element->client_list_trigger_node); - pthread_mutex_unlock(&client_list->lock); - - /* Put new reference and the hashtable's reference. */ - notification_client_list_put(client_list); - notification_client_list_put(client_list); - client_list = NULL; - } - - /* Remove trigger from triggers_ht. */ - notif_thread_state_remove_trigger_ht_elem(state, trigger_ht_element); - - /* 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: - rcu_read_unlock(); - if (_cmd_reply) { - *_cmd_reply = cmd_reply; - } - return 0; -} - -/* Returns 0 on success, 1 on exit requested, negative value on error. */ -int handle_notification_thread_command( - struct notification_thread_handle *handle, - struct notification_thread_state *state) -{ - int ret; - uint64_t counter; - struct notification_thread_command *cmd; - - /* Read the event pipe to put it back into a quiescent state. */ - ret = lttng_read(lttng_pipe_get_readfd(handle->cmd_queue.event_pipe), &counter, - sizeof(counter)); - if (ret != sizeof(counter)) { - goto error; - } - - pthread_mutex_lock(&handle->cmd_queue.lock); - cmd = cds_list_first_entry(&handle->cmd_queue.list, - struct notification_thread_command, cmd_list_node); - cds_list_del(&cmd->cmd_list_node); - pthread_mutex_unlock(&handle->cmd_queue.lock); - - DBG("Received `%s` command", - notification_command_type_str(cmd->type)); - switch (cmd->type) { - case NOTIFICATION_COMMAND_TYPE_REGISTER_TRIGGER: - ret = handle_notification_thread_command_register_trigger(state, - cmd->parameters.register_trigger.trigger, - cmd->parameters.register_trigger.is_trigger_anonymous, - &cmd->reply_code); - break; - case NOTIFICATION_COMMAND_TYPE_UNREGISTER_TRIGGER: - ret = handle_notification_thread_command_unregister_trigger( - state, - cmd->parameters.unregister_trigger.trigger, - &cmd->reply_code); - break; - case NOTIFICATION_COMMAND_TYPE_ADD_CHANNEL: - ret = handle_notification_thread_command_add_channel( - state, - cmd->parameters.add_channel.session.name, - cmd->parameters.add_channel.session.uid, - cmd->parameters.add_channel.session.gid, - cmd->parameters.add_channel.channel.name, - cmd->parameters.add_channel.channel.domain, - cmd->parameters.add_channel.channel.key, - cmd->parameters.add_channel.channel.capacity, - &cmd->reply_code); - break; - case NOTIFICATION_COMMAND_TYPE_REMOVE_CHANNEL: - ret = handle_notification_thread_command_remove_channel( - state, cmd->parameters.remove_channel.key, - cmd->parameters.remove_channel.domain, - &cmd->reply_code); - break; - case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING: - case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED: - ret = handle_notification_thread_command_session_rotation( - state, - cmd->type, - cmd->parameters.session_rotation.session_name, - cmd->parameters.session_rotation.uid, - cmd->parameters.session_rotation.gid, - cmd->parameters.session_rotation.trace_archive_chunk_id, - cmd->parameters.session_rotation.location, - &cmd->reply_code); - break; - case NOTIFICATION_COMMAND_TYPE_ADD_TRACER_EVENT_SOURCE: - ret = handle_notification_thread_command_add_tracer_event_source( - state, - cmd->parameters.tracer_event_source.tracer_event_source_fd, - cmd->parameters.tracer_event_source.domain, - &cmd->reply_code); - break; - case NOTIFICATION_COMMAND_TYPE_REMOVE_TRACER_EVENT_SOURCE: - ret = handle_notification_thread_command_remove_tracer_event_source( - state, - cmd->parameters.tracer_event_source.tracer_event_source_fd, - &cmd->reply_code); - 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, - &triggers, - &cmd->reply_code); - cmd->reply.list_triggers.triggers = triggers; - ret = 0; - break; - } - case NOTIFICATION_COMMAND_TYPE_QUIT: - cmd->reply_code = LTTNG_OK; - ret = 1; - goto end; - case NOTIFICATION_COMMAND_TYPE_GET_TRIGGER: - { - struct lttng_trigger *trigger = NULL; - - ret = handle_notification_thread_command_get_trigger(state, - cmd->parameters.get_trigger.trigger, &trigger, - &cmd->reply_code); - cmd->reply.get_trigger.trigger = trigger; - break; - } - 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 { - ret = client_handle_transmission_status( - client, client_status, state); - } - rcu_read_unlock(); - break; - } - default: - ERR("Unknown internal command received"); - goto error_unlock; - } - - if (ret) { - goto error_unlock; - } -end: - if (cmd->is_async) { - free(cmd); - cmd = NULL; - } else { - lttng_waiter_wake_up(&cmd->reply_waiter); - } - return ret; -error_unlock: - /* Wake-up and return a fatal error to the calling thread. */ - lttng_waiter_wake_up(&cmd->reply_waiter); - cmd->reply_code = LTTNG_ERR_FATAL; -error: - /* Indicate a fatal error to the caller. */ - return -1; -} - -static -int socket_set_non_blocking(int socket) -{ - int ret, flags; - - /* Set the pipe as non-blocking. */ - ret = fcntl(socket, F_GETFL, 0); - if (ret == -1) { - PERROR("fcntl get socket flags"); - goto end; - } - flags = ret; - - ret = fcntl(socket, F_SETFL, flags | O_NONBLOCK); - if (ret == -1) { - PERROR("fcntl set O_NONBLOCK socket flag"); - goto end; - } - DBG("Client socket (fd = %i) set as non-blocking", socket); -end: - return ret; -} - -static -int client_reset_inbound_state(struct notification_client *client) -{ - int ret; - - - lttng_payload_clear(&client->communication.inbound.payload); - - client->communication.inbound.bytes_to_receive = - sizeof(struct lttng_notification_channel_message); - client->communication.inbound.msg_type = - LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNKNOWN; - LTTNG_SOCK_SET_UID_CRED(&client->communication.inbound.creds, -1); - LTTNG_SOCK_SET_GID_CRED(&client->communication.inbound.creds, -1); - ret = lttng_dynamic_buffer_set_size( - &client->communication.inbound.payload.buffer, - client->communication.inbound.bytes_to_receive); - - return ret; -} - -int handle_notification_thread_client_connect( - struct notification_thread_state *state) -{ - int ret; - struct notification_client *client; - - DBG("Handling new notification channel client connection"); - - client = zmalloc(sizeof(*client)); - if (!client) { - /* Fatal error. */ - 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_payload_init(&client->communication.inbound.payload); - lttng_payload_init(&client->communication.outbound.payload); - client->communication.inbound.expect_creds = true; - - ret = client_reset_inbound_state(client); - if (ret) { - ERR("Failed to reset client communication's inbound state"); - ret = 0; - goto error; - } - - ret = lttcomm_accept_unix_sock(state->notification_channel_socket); - if (ret < 0) { - ERR("Failed to accept new notification channel client connection"); - ret = 0; - goto error; - } - - client->socket = ret; - - ret = socket_set_non_blocking(client->socket); - if (ret) { - ERR("Failed to set new notification channel client connection socket as non-blocking"); - goto error; - } - - ret = lttcomm_setsockopt_creds_unix_sock(client->socket); - if (ret < 0) { - ERR("Failed to set socket options on new notification channel client socket"); - ret = 0; - goto error; - } - - ret = lttng_poll_add(&state->events, client->socket, - LPOLLIN | LPOLLERR | - LPOLLHUP | LPOLLRDHUP); - if (ret < 0) { - ERR("Failed to add notification channel client socket to poll set"); - ret = 0; - goto error; - } - DBG("Added new notification channel client socket (%i) to poll set", - client->socket); - - rcu_read_lock(); - 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; - -error: - notification_client_destroy(client, state); - return ret; -} - -/* - * RCU read-lock must be held by the caller. - * Client lock must _not_ 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. */ - pthread_mutex_lock(&client->lock); - client->communication.active = false; - cds_lfht_del(state->client_socket_ht, &client->client_socket_ht_node); - cds_lfht_del(state->client_id_ht, &client->client_id_ht_node); - pthread_mutex_unlock(&client->lock); - - ret = lttng_poll_del(&state->events, client->socket); - if (ret) { - ERR("Failed to remove client socket %d from poll set", - client->socket); - } - - /* 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; - - rcu_read_lock(); - DBG("Closing client connection (socket fd = %i)", - client_socket); - client = get_client_from_socket(client_socket, state); - if (!client) { - /* Internal state corruption, fatal error. */ - ERR("Unable to find client (socket fd = %i)", - client_socket); - ret = -1; - goto end; - } - - ret = notification_thread_client_disconnect(client, state); -end: - rcu_read_unlock(); - return ret; -} - -int handle_notification_thread_client_disconnect_all( - struct notification_thread_state *state) -{ - struct cds_lfht_iter iter; - struct notification_client *client; - bool error_encoutered = false; - - rcu_read_lock(); - DBG("Closing all client connections"); - cds_lfht_for_each_entry(state->client_socket_ht, &iter, client, - client_socket_ht_node) { - int ret; - - ret = notification_thread_client_disconnect( - client, state); - if (ret) { - error_encoutered = true; - } - } - rcu_read_unlock(); - return error_encoutered ? 1 : 0; -} - -int handle_notification_thread_trigger_unregister_all( - struct notification_thread_state *state) -{ - bool error_occurred = false; - struct cds_lfht_iter iter; - struct lttng_trigger_ht_element *trigger_ht_element; - - rcu_read_lock(); - cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element, - node) { - int ret = handle_notification_thread_command_unregister_trigger( - state, trigger_ht_element->trigger, NULL); - if (ret) { - error_occurred = true; - } - } - rcu_read_unlock(); - return error_occurred ? -1 : 0; -} - -static -int client_handle_transmission_status( - struct notification_client *client, - enum client_transmission_status transmission_status, - struct notification_thread_state *state) -{ - int ret = 0; - - switch (transmission_status) { - case CLIENT_TRANSMISSION_STATUS_COMPLETE: - ret = lttng_poll_mod(&state->events, client->socket, - CLIENT_POLL_MASK_IN); - if (ret) { - goto end; - } - - 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; - struct lttng_payload_view pv = lttng_payload_view_from_payload( - &client->communication.outbound.payload, 0, -1); - const int fds_to_send_count = - lttng_payload_view_get_fd_handle_count(&pv); - - ASSERT_LOCKED(client->lock); - - if (!client->communication.active) { - status = CLIENT_TRANSMISSION_STATUS_FAIL; - goto end; - } - - if (pv.buffer.size == 0) { - /* - * If both data and fds are equal to zero, we are in an invalid - * state. - */ - LTTNG_ASSERT(fds_to_send_count != 0); - goto send_fds; - } - - /* Send data. */ - to_send_count = pv.buffer.size; - DBG("Flushing client (socket fd = %i) outgoing queue", - client->socket); - - ret = lttcomm_send_unix_sock_non_block(client->socket, - pv.buffer.data, - to_send_count); - if ((ret >= 0 && ret < to_send_count)) { - DBG("Client (socket fd = %i) outgoing queue could not be completely flushed", - client->socket); - to_send_count -= max(ret, 0); - - memmove(client->communication.outbound.payload.buffer.data, - pv.buffer.data + - pv.buffer.size - to_send_count, - to_send_count); - ret = lttng_dynamic_buffer_set_size( - &client->communication.outbound.payload.buffer, - to_send_count); - if (ret) { - goto error; - } - - status = CLIENT_TRANSMISSION_STATUS_QUEUED; - goto end; - } else if (ret < 0) { - /* Generic error, disable the client's communication. */ - ERR("Failed to flush outgoing queue, disconnecting client (socket fd = %i)", - client->socket); - client->communication.active = false; - status = CLIENT_TRANSMISSION_STATUS_FAIL; - goto end; - } else { - /* - * No error and flushed the queue completely. - * - * The payload buffer size is used later to - * check if there is notifications queued. So albeit that the - * direct caller knows that the transmission is complete, we - * need to set the buffer size to zero. - */ - ret = lttng_dynamic_buffer_set_size( - &client->communication.outbound.payload.buffer, 0); - if (ret) { - goto error; - } - } - -send_fds: - /* No fds to send, transmission is complete. */ - if (fds_to_send_count == 0) { - status = CLIENT_TRANSMISSION_STATUS_COMPLETE; - goto end; - } - - ret = lttcomm_send_payload_view_fds_unix_sock_non_block( - client->socket, &pv); - if (ret < 0) { - /* Generic error, disable the client's communication. */ - ERR("Failed to flush outgoing fds queue, disconnecting client (socket fd = %i)", - client->socket); - client->communication.active = false; - status = CLIENT_TRANSMISSION_STATUS_FAIL; - goto end; - } else if (ret == 0) { - /* Nothing could be sent. */ - status = CLIENT_TRANSMISSION_STATUS_QUEUED; - } else { - /* Fd passing is an all or nothing kind of thing. */ - status = CLIENT_TRANSMISSION_STATUS_COMPLETE; - /* - * The payload _fd_array count is used later to - * check if there is notifications queued. So although the - * direct caller knows that the transmission is complete, we - * need to clear the _fd_array for the queuing check. - */ - lttng_dynamic_pointer_array_clear( - &client->communication.outbound.payload - ._fd_handles); - } - -end: - if (status == CLIENT_TRANSMISSION_STATUS_COMPLETE) { - client->communication.outbound.queued_command_reply = false; - client->communication.outbound.dropped_notification = false; - lttng_payload_clear(&client->communication.outbound.payload); - } - - return status; -error: - return CLIENT_TRANSMISSION_STATUS_ERROR; -} - -static -bool client_has_outbound_data_left( - const struct notification_client *client) -{ - const struct lttng_payload_view pv = lttng_payload_view_from_payload( - &client->communication.outbound.payload, 0, -1); - const bool has_data = pv.buffer.size != 0; - const bool has_fds = lttng_payload_view_get_fd_handle_count(&pv); - - return has_data || has_fds; -} - -/* Client lock must _not_ be held by the 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; - - memcpy(buffer, &msg, sizeof(msg)); - memcpy(buffer + sizeof(msg), &reply, sizeof(reply)); - DBG("Send command reply (%i)", (int) status); - - pthread_mutex_lock(&client->lock); - if (client->communication.outbound.queued_command_reply) { - /* Protocol error. */ - goto error_unlock; - } - - /* Enqueue buffer to outgoing queue and flush it. */ - ret = lttng_dynamic_buffer_append( - &client->communication.outbound.payload.buffer, - buffer, sizeof(buffer)); - if (ret) { - goto error_unlock; - } - - transmission_status = client_flush_outgoing_queue(client); - - if (client_has_outbound_data_left(client)) { - /* Queue could not be emptied. */ - client->communication.outbound.queued_command_reply = true; - } - - pthread_mutex_unlock(&client->lock); - ret = client_handle_transmission_status( - client, transmission_status, state); - if (ret) { - goto error; - } - - return 0; -error_unlock: - pthread_mutex_unlock(&client->lock); -error: - return -1; -} - -static -int client_handle_message_unknown(struct notification_client *client, - struct notification_thread_state *state) -{ - int ret; - /* - * 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; - - LTTNG_ASSERT(sizeof(*msg) == client->communication.inbound.payload.buffer.size); - msg = (const struct lttng_notification_channel_message *) - client->communication.inbound.payload.buffer.data; - - if (msg->size == 0 || - msg->size > DEFAULT_MAX_NOTIFICATION_CLIENT_MESSAGE_PAYLOAD_SIZE) { - ERR("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("Invalid notification channel message: unexpected message type"); - goto end; - } - - client->communication.inbound.bytes_to_receive = msg->size; - client->communication.inbound.fds_to_receive = msg->fds; - client->communication.inbound.msg_type = - (enum lttng_notification_channel_message_type) msg->type; - ret = lttng_dynamic_buffer_set_size( - &client->communication.inbound.payload.buffer, msg->size); - - /* msg is not valid anymore due to lttng_dynamic_buffer_set_size. */ - msg = NULL; -end: - 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)]; - - 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.payload.buffer - .data; - client->major = handshake_client->major; - client->minor = handshake_client->minor; - if (!client->communication.inbound.creds_received) { - ERR("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); - client->is_sessiond = LTTNG_SOCK_GET_PID_CRED(&client->communication.inbound.creds) == getpid(); - DBG("Received handshake from client: uid = %u, gid = %u, protocol version = %i.%i, client is sessiond = %s", - client->uid, client->gid, (int) client->major, - (int) client->minor, - client->is_sessiond ? "true" : "false"); - - if (handshake_client->major != - LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR) { - status = LTTNG_NOTIFICATION_CHANNEL_STATUS_UNSUPPORTED_VERSION; - } - - pthread_mutex_lock(&client->lock); - /* Outgoing queue will be flushed when the command reply is sent. */ - ret = lttng_dynamic_buffer_append( - &client->communication.outbound.payload.buffer, send_buffer, - sizeof(send_buffer)); - if (ret) { - ERR("Failed to send protocol version to notification channel client"); - goto end_unlock; - } - - client->validated = true; - client->communication.active = true; - pthread_mutex_unlock(&client->lock); - - /* Set reception state to receive the next message header. */ - ret = client_reset_inbound_state(client); - if (ret) { - ERR("Failed to reset client communication's inbound state"); - goto end; - } - - /* Flushes the outgoing queue. */ - ret = client_send_command_reply(client, state, status); - if (ret) { - ERR("Failed to send reply to notification channel client"); - goto end; - } - - goto end; -end_unlock: - pthread_mutex_unlock(&client->lock); -end: - return ret; -} - -static -int client_handle_message_subscription( - struct notification_client *client, - enum lttng_notification_channel_message_type msg_type, - struct notification_thread_state *state) -{ - int ret; - struct lttng_condition *condition; - enum lttng_notification_channel_status status = - LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; - struct lttng_payload_view condition_view = - lttng_payload_view_from_payload( - &client->communication.inbound.payload, - 0, -1); - size_t expected_condition_size; - - /* - * No need to lock client to sample the inbound state as the only - * other thread accessing clients (action executor) only uses the - * outbound state. - */ - expected_condition_size = client->communication.inbound.payload.buffer.size; - ret = lttng_condition_create_from_payload(&condition_view, &condition); - if (ret != expected_condition_size) { - ERR("Malformed condition received from client"); - goto end; - } - - /* Ownership of condition is always transferred. */ - 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 end; - } - - /* Set reception state to receive the next message header. */ - ret = client_reset_inbound_state(client); - if (ret) { - ERR("Failed to reset client communication's inbound state"); - goto end; - } - - ret = client_send_command_reply(client, state, status); - if (ret) { - ERR("Failed to send reply to notification channel client"); - goto end; - } - -end: - return ret; -} - -static -int client_dispatch_message(struct notification_client *client, - struct notification_thread_state *state) -{ - int ret = 0; - - if (client->communication.inbound.msg_type != - LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE && - client->communication.inbound.msg_type != - LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNKNOWN && - !client->validated) { - WARN("client attempted a command before handshake"); - ret = -1; - goto end; - } - - switch (client->communication.inbound.msg_type) { - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNKNOWN: - { - ret = client_handle_message_unknown(client, state); - break; - } - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE: - { - ret = client_handle_message_handshake(client, state); - break; - } - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE: - case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNSUBSCRIBE: - { - ret = client_handle_message_subscription(client, - client->communication.inbound.msg_type, state); - break; - } - default: - abort(); - } -end: - return ret; -} - -/* Incoming data from client. */ -int handle_notification_thread_client_in( - struct notification_thread_state *state, int socket) -{ - int ret = 0; - struct notification_client *client; - ssize_t recv_ret; - size_t offset; - - rcu_read_lock(); - client = get_client_from_socket(socket, state); - if (!client) { - /* Internal error, abort. */ - ret = -1; - goto end; - } - - if (client->communication.inbound.bytes_to_receive == 0 && - client->communication.inbound.fds_to_receive != 0) { - /* Only FDs left to receive. */ - goto receive_fds; - } - - offset = client->communication.inbound.payload.buffer.size - - client->communication.inbound.bytes_to_receive; - if (client->communication.inbound.expect_creds) { - recv_ret = lttcomm_recv_creds_unix_sock(socket, - client->communication.inbound.payload.buffer.data + offset, - client->communication.inbound.bytes_to_receive, - &client->communication.inbound.creds); - if (recv_ret > 0) { - client->communication.inbound.expect_creds = false; - client->communication.inbound.creds_received = true; - } - } else { - recv_ret = lttcomm_recv_unix_sock_non_block(socket, - client->communication.inbound.payload.buffer.data + offset, - client->communication.inbound.bytes_to_receive); - } - if (recv_ret >= 0) { - client->communication.inbound.bytes_to_receive -= recv_ret; - } else { - goto error_disconnect_client; - } - - if (client->communication.inbound.bytes_to_receive != 0) { - /* Message incomplete wait for more data. */ - ret = 0; - goto end; - } - -receive_fds: - LTTNG_ASSERT(client->communication.inbound.bytes_to_receive == 0); - - /* Receive fds. */ - if (client->communication.inbound.fds_to_receive != 0) { - ret = lttcomm_recv_payload_fds_unix_sock_non_block( - client->socket, - client->communication.inbound.fds_to_receive, - &client->communication.inbound.payload); - if (ret > 0) { - /* - * Fds received. non blocking fds passing is all - * or nothing. - */ - ssize_t expected_size; - - expected_size = sizeof(int) * - client->communication.inbound - .fds_to_receive; - LTTNG_ASSERT(ret == expected_size); - client->communication.inbound.fds_to_receive = 0; - } else if (ret == 0) { - /* Received nothing. */ - ret = 0; - goto end; - } else { - goto error_disconnect_client; - } - } - - /* At this point the message is complete.*/ - LTTNG_ASSERT(client->communication.inbound.bytes_to_receive == 0 && - client->communication.inbound.fds_to_receive == 0); - ret = client_dispatch_message(client, state); - if (ret) { - /* - * Only returns an error if this client must be - * disconnected. - */ - goto error_disconnect_client; - } - -end: - rcu_read_unlock(); - return ret; - -error_disconnect_client: - ret = notification_thread_client_disconnect(client, state); - goto end; -} - -/* Client ready to receive outgoing data. */ -int handle_notification_thread_client_out( - struct notification_thread_state *state, int socket) -{ - int ret; - struct notification_client *client; - enum client_transmission_status transmission_status; - - rcu_read_lock(); - client = get_client_from_socket(socket, state); - if (!client) { - /* Internal error, abort. */ - ret = -1; - goto end; - } - - pthread_mutex_lock(&client->lock); - transmission_status = client_flush_outgoing_queue(client); - pthread_mutex_unlock(&client->lock); - - ret = client_handle_transmission_status( - client, transmission_status, state); - if (ret) { - goto end; - } -end: - rcu_read_unlock(); - return ret; -} - -static -bool evaluate_buffer_usage_condition(const struct lttng_condition *condition, - const struct channel_state_sample *sample, - uint64_t buffer_capacity) -{ - bool result = false; - uint64_t threshold; - enum lttng_condition_type condition_type; - const struct lttng_condition_buffer_usage *use_condition = container_of( - condition, struct lttng_condition_buffer_usage, - parent); - - if (use_condition->threshold_bytes.set) { - threshold = use_condition->threshold_bytes.value; - } else { - /* - * Threshold was expressed as a ratio. - * - * TODO the threshold (in bytes) of conditions expressed - * as a ratio of total buffer size could be cached to - * forego this double-multiplication or it could be performed - * as fixed-point math. - * - * Note that caching should accommodates the case where the - * condition applies to multiple channels (i.e. don't assume - * that all channels matching my_chann* have the same size...) - */ - threshold = (uint64_t) (use_condition->threshold_ratio.value * - (double) buffer_capacity); - } - - condition_type = lttng_condition_get_type(condition); - if (condition_type == LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW) { - DBG("Low buffer usage condition being evaluated: threshold = %" PRIu64 ", highest usage = %" PRIu64, - threshold, sample->highest_usage); - - /* - * The low condition should only be triggered once _all_ of the - * streams in a channel have gone below the "low" threshold. - */ - if (sample->highest_usage <= threshold) { - result = true; - } - } else { - DBG("High buffer usage condition being evaluated: threshold = %" PRIu64 ", highest usage = %" PRIu64, - threshold, sample->highest_usage); - - /* - * For high buffer usage scenarios, we want to trigger whenever - * _any_ of the streams has reached the "high" threshold. - */ - if (sample->highest_usage >= threshold) { - result = true; - } - } - - return result; -} - -static -bool evaluate_session_consumed_size_condition( - const struct lttng_condition *condition, - uint64_t session_consumed_size) -{ - uint64_t threshold; - const struct lttng_condition_session_consumed_size *size_condition = - container_of(condition, - struct lttng_condition_session_consumed_size, - parent); - - threshold = size_condition->consumed_threshold_bytes.value; - DBG("Session consumed size condition being evaluated: threshold = %" PRIu64 ", current size = %" PRIu64, - threshold, session_consumed_size); - return session_consumed_size >= threshold; -} - -static -int evaluate_buffer_condition(const struct lttng_condition *condition, - struct lttng_evaluation **evaluation, - const struct notification_thread_state *state, - const struct channel_state_sample *previous_sample, - const struct channel_state_sample *latest_sample, - uint64_t previous_session_consumed_total, - uint64_t latest_session_consumed_total, - struct channel_info *channel_info) -{ - int ret = 0; - enum lttng_condition_type condition_type; - const bool previous_sample_available = !!previous_sample; - bool previous_sample_result = false; - bool latest_sample_result; - - condition_type = lttng_condition_get_type(condition); - - switch (condition_type) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - if (caa_likely(previous_sample_available)) { - previous_sample_result = - evaluate_buffer_usage_condition(condition, - previous_sample, channel_info->capacity); - } - latest_sample_result = evaluate_buffer_usage_condition( - condition, latest_sample, - channel_info->capacity); - break; - case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: - if (caa_likely(previous_sample_available)) { - previous_sample_result = - evaluate_session_consumed_size_condition( - condition, - previous_session_consumed_total); - } - latest_sample_result = - evaluate_session_consumed_size_condition( - condition, - latest_session_consumed_total); - break; - default: - /* Unknown condition type; internal error. */ - abort(); - } - - if (!latest_sample_result || - (previous_sample_result == latest_sample_result)) { - /* - * Only trigger on a condition evaluation transition. - * - * NOTE: This edge-triggered logic may not be appropriate for - * future condition types. - */ - goto end; - } - - if (!evaluation || !latest_sample_result) { - goto end; - } - - switch (condition_type) { - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: - case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: - *evaluation = lttng_evaluation_buffer_usage_create( - condition_type, - latest_sample->highest_usage, - channel_info->capacity); - break; - case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: - *evaluation = lttng_evaluation_session_consumed_size_create( - latest_session_consumed_total); - break; - default: - abort(); - } - - if (!*evaluation) { - ret = -1; - goto end; - } -end: - return ret; -} - -static -int client_notification_overflow(struct notification_client *client) -{ - int ret = 0; - const struct lttng_notification_channel_message msg = { - .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION_DROPPED, - }; - - 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.payload.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 object_uid, gid_t object_gid) -{ - const struct lttng_credentials creds = { - .uid = LTTNG_OPTIONAL_INIT_VALUE(object_uid), - .gid = LTTNG_OPTIONAL_INIT_VALUE(object_gid), - }; - - return notification_client_list_send_evaluation(client_list, - trigger, evaluation, - &creds, - client_handle_transmission_status_wrapper, state); -} - -/* - * Permission checks relative to notification channel clients are performed - * here. Notice how object, client, and trigger credentials are involved in - * this check. - * - * The `object` credentials are the credentials associated with the "subject" - * of a condition. For instance, a `rotation completed` condition applies - * to a session. When that condition is met, it will produce an evaluation - * against a session. Hence, in this case, the `object` credentials are the - * credentials of the "subject" session. - * - * The `trigger` credentials are the credentials of the user that registered the - * trigger. - * - * The `client` credentials are the credentials of the user that created a given - * notification channel. - * - * In terms of visibility, it is expected that non-privilieged users can only - * register triggers against "their" objects (their own sessions and - * applications they are allowed to interact with). They can then open a - * notification channel and subscribe to notifications associated with those - * triggers. - * - * As for privilieged users, they can register triggers against the objects of - * other users. They can then subscribe to the notifications associated to their - * triggers. Privilieged users _can't_ subscribe to the notifications of - * triggers owned by other users; they must create their own triggers. - * - * This is more a concern of usability than security. It would be difficult for - * a root user reliably subscribe to a specific set of conditions without - * interference from external users (those could, for instance, unregister - * their triggers). - */ -int notification_client_list_send_evaluation( - struct notification_client_list *client_list, - const struct lttng_trigger *trigger, - const struct lttng_evaluation *evaluation, - const struct lttng_credentials *source_object_creds, - report_client_transmission_result_cb client_report, - void *user_data) -{ - int ret = 0; - struct lttng_payload msg_payload; - struct notification_client_list_element *client_list_element, *tmp; - const struct lttng_notification notification = { - .trigger = (struct lttng_trigger *) trigger, - .evaluation = (struct lttng_evaluation *) evaluation, - }; - struct lttng_notification_channel_message msg_header = { - .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION, - }; - const struct lttng_credentials *trigger_creds = - lttng_trigger_get_credentials(trigger); - - lttng_payload_init(&msg_payload); - - ret = lttng_dynamic_buffer_append(&msg_payload.buffer, &msg_header, - sizeof(msg_header)); - if (ret) { - goto end; - } - - ret = lttng_notification_serialize(¬ification, &msg_payload); - if (ret) { - ERR("Failed to serialize notification"); - ret = -1; - goto end; - } - - /* Update payload size. */ - ((struct lttng_notification_channel_message *) msg_payload.buffer.data) - ->size = (uint32_t)( - msg_payload.buffer.size - sizeof(msg_header)); - - /* Update the payload number of fds. */ - { - const struct lttng_payload_view pv = lttng_payload_view_from_payload( - &msg_payload, 0, -1); - - ((struct lttng_notification_channel_message *) - msg_payload.buffer.data)->fds = (uint32_t) - lttng_payload_view_get_fd_handle_count(&pv); - } - - pthread_mutex_lock(&client_list->lock); - cds_list_for_each_entry_safe(client_list_element, tmp, - &client_list->clients_list, node) { - enum client_transmission_status transmission_status; - struct notification_client *client = - client_list_element->client; - - ret = 0; - pthread_mutex_lock(&client->lock); - if (!client->communication.active) { - /* - * Skip inactive client (protocol error or - * disconnecting). - */ - DBG("Skipping client at it is marked as inactive"); - goto skip_client; - } - - if (lttng_trigger_is_hidden(trigger) && !client->is_sessiond) { - /* - * Notifications resulting from an hidden trigger are - * only sent to the session daemon. - */ - DBG("Skipping client as the trigger is hidden and the client is not the session daemon"); - goto skip_client; - } - - if (source_object_creds) { - if (client->uid != lttng_credentials_get_uid(source_object_creds) && - client->gid != lttng_credentials_get_gid(source_object_creds) && - client->uid != 0) { - /* - * Client is not allowed to monitor this - * object. - */ - DBG("Skipping client at it does not have the object permission to receive notification for this trigger"); - goto skip_client; - } - } - - if (client->uid != lttng_credentials_get_uid(trigger_creds)) { - DBG("Skipping client at it does not have the permission to receive notification for this trigger"); - goto skip_client; - } - - DBG("Sending notification to client (fd = %i, %zu bytes)", - client->socket, msg_payload.buffer.size); - - if (client_has_outbound_data_left(client)) { - /* - * Outgoing data is already buffered for this client; - * drop the notification and enqueue a "dropped - * notification" message if this is the first dropped - * notification since the socket spilled-over to the - * queue. - */ - ret = client_notification_overflow(client); - if (ret) { - /* Fatal error. */ - goto skip_client; - } - } - - ret = lttng_payload_copy(&msg_payload, &client->communication.outbound.payload); - if (ret) { - /* Fatal error. */ - goto skip_client; - } - - transmission_status = client_flush_outgoing_queue(client); - pthread_mutex_unlock(&client->lock); - ret = client_report(client, transmission_status, user_data); - if (ret) { - /* Fatal error. */ - goto end_unlock_list; - } - - continue; - -skip_client: - pthread_mutex_unlock(&client->lock); - if (ret) { - /* Fatal error. */ - goto end_unlock_list; - } - } - ret = 0; - -end_unlock_list: - pthread_mutex_unlock(&client_list->lock); -end: - lttng_payload_reset(&msg_payload); - return ret; -} - -static -struct lttng_event_notifier_notification *recv_one_event_notifier_notification( - int notification_pipe_read_fd, enum lttng_domain_type domain) -{ - int ret; - uint64_t token; - struct lttng_event_notifier_notification *notification = NULL; - char *capture_buffer = NULL; - size_t capture_buffer_size; - void *reception_buffer; - size_t reception_size; - - struct lttng_ust_abi_event_notifier_notification ust_notification; - struct lttng_kernel_abi_event_notifier_notification kernel_notification; - - /* Init lttng_event_notifier_notification */ - switch(domain) { - case LTTNG_DOMAIN_UST: - reception_buffer = (void *) &ust_notification; - reception_size = sizeof(ust_notification); - break; - case LTTNG_DOMAIN_KERNEL: - reception_buffer = (void *) &kernel_notification; - reception_size = sizeof(kernel_notification); - break; - default: - abort(); - } - - /* - * The monitoring pipe only holds messages smaller than PIPE_BUF, - * ensuring that read/write of tracer notifications are atomic. - */ - ret = lttng_read(notification_pipe_read_fd, reception_buffer, - reception_size); - if (ret != reception_size) { - PERROR("Failed to read from event source notification pipe: fd = %d, size to read = %zu, ret = %d", - notification_pipe_read_fd, reception_size, ret); - ret = -1; - goto end; - } - - switch(domain) { - case LTTNG_DOMAIN_UST: - token = ust_notification.token; - capture_buffer_size = ust_notification.capture_buf_size; - break; - case LTTNG_DOMAIN_KERNEL: - token = kernel_notification.token; - capture_buffer_size = kernel_notification.capture_buf_size; - break; - default: - abort(); - } - - if (capture_buffer_size == 0) { - capture_buffer = NULL; - goto skip_capture; - } - - if (capture_buffer_size > MAX_CAPTURE_SIZE) { - ERR("Event notifier has a capture payload size which exceeds the maximum allowed size: capture_payload_size = %zu bytes, max allowed size = %d bytes", - capture_buffer_size, MAX_CAPTURE_SIZE); - goto end; - } - - capture_buffer = zmalloc(capture_buffer_size); - if (!capture_buffer) { - ERR("Failed to allocate capture buffer"); - goto end; - } - - /* Fetch additional payload (capture). */ - ret = lttng_read(notification_pipe_read_fd, capture_buffer, capture_buffer_size); - if (ret != capture_buffer_size) { - ERR("Failed to read from event source pipe (fd = %i)", - notification_pipe_read_fd); - goto end; - } - -skip_capture: - notification = lttng_event_notifier_notification_create(token, domain, - capture_buffer, capture_buffer_size); - if (notification == NULL) { - goto end; - } - - /* - * Ownership transfered to the lttng_event_notifier_notification object. - */ - capture_buffer = NULL; - -end: - free(capture_buffer); - return notification; -} - -static -int dispatch_one_event_notifier_notification(struct notification_thread_state *state, - struct lttng_event_notifier_notification *notification) -{ - struct cds_lfht_node *node; - struct cds_lfht_iter iter; - struct notification_trigger_tokens_ht_element *element; - struct lttng_evaluation *evaluation = NULL; - enum action_executor_status executor_status; - struct notification_client_list *client_list = NULL; - int ret; - unsigned int capture_count = 0; - - /* Find triggers associated with this token. */ - rcu_read_lock(); - cds_lfht_lookup(state->trigger_tokens_ht, - hash_key_u64(¬ification->tracer_token, lttng_ht_seed), - match_trigger_token, ¬ification->tracer_token, &iter); - node = cds_lfht_iter_get_node(&iter); - if (caa_unlikely(!node)) { - /* - * This is not an error, slow consumption of the tracer - * notifications can lead to situations where a trigger is - * removed but we still get tracer notifications matching a - * trigger that no longer exists. - */ - ret = 0; - goto end_unlock; - } - - element = caa_container_of(node, - struct notification_trigger_tokens_ht_element, - node); - - if (lttng_condition_event_rule_matches_get_capture_descriptor_count( - lttng_trigger_get_const_condition(element->trigger), - &capture_count) != LTTNG_CONDITION_STATUS_OK) { - ERR("Failed to get capture count"); - ret = -1; - goto end; - } - - if (!notification->capture_buffer && capture_count != 0) { - ERR("Expected capture but capture buffer is null"); - ret = -1; - goto end; - } - - evaluation = lttng_evaluation_event_rule_matches_create( - container_of(lttng_trigger_get_const_condition( - element->trigger), - struct lttng_condition_event_rule_matches, - parent), - notification->capture_buffer, - notification->capture_buf_size, false); - - if (evaluation == NULL) { - ERR("Failed to create event rule matches evaluation while creating and enqueuing action executor job"); - ret = -1; - goto end_unlock; - } - - client_list = get_client_list_from_condition(state, - lttng_trigger_get_const_condition(element->trigger)); - executor_status = action_executor_enqueue_trigger(state->executor, - element->trigger, evaluation, NULL, 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; - - /* No clients subscribed to notifications for this trigger. */ - 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->clients_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_unlock(&client_list->lock); - break; - } - case ACTION_EXECUTOR_STATUS_INVALID: - case ACTION_EXECUTOR_STATUS_ERROR: - /* Fatal error, shut down everything. */ - ERR("Fatal error encoutered while enqueuing action to the action executor"); - ret = -1; - goto end_unlock; - default: - /* Unhandled error. */ - abort(); - } - -end_unlock: - notification_client_list_put(client_list); - rcu_read_unlock(); -end: - return ret; -} - -static -int handle_one_event_notifier_notification( - struct notification_thread_state *state, - int pipe, enum lttng_domain_type domain) -{ - int ret = 0; - struct lttng_event_notifier_notification *notification = NULL; - - notification = recv_one_event_notifier_notification(pipe, domain); - if (notification == NULL) { - /* Reception failed, don't consider it fatal. */ - ERR("Error receiving an event notifier notification from tracer: fd = %i, domain = %s", - pipe, lttng_domain_type_str(domain)); - goto end; - } - - ret = dispatch_one_event_notifier_notification(state, notification); - if (ret) { - ERR("Error dispatching an event notifier notification from tracer: fd = %i, domain = %s", - pipe, lttng_domain_type_str(domain)); - goto end; - } - -end: - lttng_event_notifier_notification_destroy(notification); - return ret; -} - -int handle_notification_thread_event_notification(struct notification_thread_state *state, - int pipe, enum lttng_domain_type domain) -{ - return handle_one_event_notifier_notification(state, pipe, domain); -} - -int handle_notification_thread_channel_sample( - struct notification_thread_state *state, int pipe, - enum lttng_domain_type domain) -{ - int ret = 0; - struct lttcomm_consumer_channel_monitor_msg sample_msg; - struct channel_info *channel_info; - struct cds_lfht_node *node; - struct cds_lfht_iter iter; - struct lttng_channel_trigger_list *trigger_list; - struct lttng_trigger_list_element *trigger_list_element; - bool previous_sample_available = false; - struct channel_state_sample previous_sample, latest_sample; - uint64_t previous_session_consumed_total, latest_session_consumed_total; - struct lttng_credentials channel_creds; - - /* - * The monitoring pipe only holds messages smaller than PIPE_BUF, - * ensuring that read/write of sampling messages are atomic. - */ - ret = lttng_read(pipe, &sample_msg, sizeof(sample_msg)); - if (ret != sizeof(sample_msg)) { - ERR("Failed to read from monitoring pipe (fd = %i)", - pipe); - ret = -1; - goto end; - } - - ret = 0; - latest_sample.key.key = sample_msg.key; - latest_sample.key.domain = domain; - latest_sample.highest_usage = sample_msg.highest; - latest_sample.lowest_usage = sample_msg.lowest; - latest_sample.channel_total_consumed = sample_msg.total_consumed; - - rcu_read_lock(); - - /* Retrieve the channel's informations */ - cds_lfht_lookup(state->channels_ht, - hash_channel_key(&latest_sample.key), - match_channel_info, - &latest_sample.key, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (caa_unlikely(!node)) { - /* - * Not an error since the consumer can push a sample to the pipe - * and the rest of the session daemon could notify us of the - * channel's destruction before we get a chance to process that - * sample. - */ - DBG("Received a sample for an unknown channel from consumerd, key = %" PRIu64 " in %s domain", - latest_sample.key.key, - lttng_domain_type_str(domain)); - goto end_unlock; - } - channel_info = caa_container_of(node, struct channel_info, - channels_ht_node); - DBG("Handling channel sample for channel %s (key = %" PRIu64 ") in session %s (highest usage = %" PRIu64 ", lowest usage = %" PRIu64", total consumed = %" PRIu64")", - channel_info->name, - latest_sample.key.key, - channel_info->session_info->name, - latest_sample.highest_usage, - latest_sample.lowest_usage, - latest_sample.channel_total_consumed); - - previous_session_consumed_total = - channel_info->session_info->consumed_data_size; - - /* Retrieve the channel's last sample, if it exists, and update it. */ - cds_lfht_lookup(state->channel_state_ht, - hash_channel_key(&latest_sample.key), - match_channel_state_sample, - &latest_sample.key, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (caa_likely(node)) { - struct channel_state_sample *stored_sample; - - /* Update the sample stored. */ - stored_sample = caa_container_of(node, - struct channel_state_sample, - channel_state_ht_node); - - memcpy(&previous_sample, stored_sample, - sizeof(previous_sample)); - stored_sample->highest_usage = latest_sample.highest_usage; - stored_sample->lowest_usage = latest_sample.lowest_usage; - stored_sample->channel_total_consumed = latest_sample.channel_total_consumed; - previous_sample_available = true; - - latest_session_consumed_total = - previous_session_consumed_total + - (latest_sample.channel_total_consumed - previous_sample.channel_total_consumed); - } else { - /* - * This is the channel's first sample, allocate space for and - * store the new sample. - */ - struct channel_state_sample *stored_sample; - - stored_sample = zmalloc(sizeof(*stored_sample)); - if (!stored_sample) { - ret = -1; - goto end_unlock; - } - - memcpy(stored_sample, &latest_sample, sizeof(*stored_sample)); - cds_lfht_node_init(&stored_sample->channel_state_ht_node); - cds_lfht_add(state->channel_state_ht, - hash_channel_key(&stored_sample->key), - &stored_sample->channel_state_ht_node); - - latest_session_consumed_total = - previous_session_consumed_total + - latest_sample.channel_total_consumed; - } - - channel_info->session_info->consumed_data_size = - latest_session_consumed_total; - - /* Find triggers associated with this channel. */ - cds_lfht_lookup(state->channel_triggers_ht, - hash_channel_key(&latest_sample.key), - match_channel_trigger_list, - &latest_sample.key, - &iter); - node = cds_lfht_iter_get_node(&iter); - if (caa_likely(!node)) { - goto end_unlock; - } - - channel_creds = (typeof(channel_creds)) { - .uid = LTTNG_OPTIONAL_INIT_VALUE(channel_info->session_info->uid), - .gid = LTTNG_OPTIONAL_INIT_VALUE(channel_info->session_info->gid), - }; - - trigger_list = caa_container_of(node, struct lttng_channel_trigger_list, - channel_triggers_ht_node); - cds_list_for_each_entry(trigger_list_element, &trigger_list->list, - node) { - const struct lttng_condition *condition; - struct lttng_trigger *trigger; - struct notification_client_list *client_list = NULL; - struct lttng_evaluation *evaluation = NULL; - enum action_executor_status executor_status; - - ret = 0; - trigger = trigger_list_element->trigger; - condition = lttng_trigger_get_const_condition(trigger); - LTTNG_ASSERT(condition); - - /* - * Check if any client is subscribed to the result of this - * evaluation. - */ - client_list = get_client_list_from_condition(state, condition); - - ret = evaluate_buffer_condition(condition, &evaluation, state, - previous_sample_available ? &previous_sample : NULL, - &latest_sample, - previous_session_consumed_total, - latest_session_consumed_total, - channel_info); - if (caa_unlikely(ret)) { - goto put_list; - } - - if (caa_likely(!evaluation)) { - goto put_list; - } - - /* - * Ownership of `evaluation` transferred to the action executor - * no matter the result. - */ - executor_status = action_executor_enqueue_trigger( - state->executor, trigger, evaluation, - &channel_creds, client_list); - evaluation = NULL; - switch (executor_status) { - case ACTION_EXECUTOR_STATUS_OK: - break; - case ACTION_EXECUTOR_STATUS_ERROR: - case ACTION_EXECUTOR_STATUS_INVALID: - /* - * TODO Add trigger identification (name/id) when - * it is added to the API. - */ - ERR("Fatal error occurred while enqueuing action associated with buffer-condition trigger"); - ret = -1; - goto put_list; - case ACTION_EXECUTOR_STATUS_OVERFLOW: - /* - * TODO Add trigger identification (name/id) when - * it is added to the API. - * - * Not a fatal error. - */ - WARN("No space left when enqueuing action associated with buffer-condition trigger"); - ret = 0; - goto put_list; - default: - abort(); - } - -put_list: - notification_client_list_put(client_list); - if (caa_unlikely(ret)) { - break; - } - } -end_unlock: - rcu_read_unlock(); -end: - return ret; -} diff --git a/src/bin/lttng-sessiond/notification-thread-events.cpp b/src/bin/lttng-sessiond/notification-thread-events.cpp new file mode 100644 index 000000000..6f449fc63 --- /dev/null +++ b/src/bin/lttng-sessiond/notification-thread-events.cpp @@ -0,0 +1,4983 @@ +/* + * Copyright (C) 2017 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "lttng/action/action.h" +#include "lttng/trigger/trigger-internal.h" +#define _LGPL_SOURCE +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "condition-internal.h" +#include "event-notifier-error-accounting.h" +#include "notification-thread.h" +#include "notification-thread-events.h" +#include "notification-thread-commands.h" +#include "lttng-sessiond.h" +#include "kernel.h" + +#define CLIENT_POLL_MASK_IN (LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP) +#define CLIENT_POLL_MASK_IN_OUT (CLIENT_POLL_MASK_IN | LPOLLOUT) + +/* The tracers currently limit the capture size to PIPE_BUF (4kb on linux). */ +#define MAX_CAPTURE_SIZE (PIPE_BUF) + +enum lttng_object_type { + LTTNG_OBJECT_TYPE_UNKNOWN, + LTTNG_OBJECT_TYPE_NONE, + LTTNG_OBJECT_TYPE_CHANNEL, + LTTNG_OBJECT_TYPE_SESSION, +}; + +struct lttng_trigger_list_element { + /* No ownership of the trigger object is assumed. */ + struct lttng_trigger *trigger; + struct cds_list_head node; +}; + +struct lttng_channel_trigger_list { + struct channel_key channel_key; + /* List of struct lttng_trigger_list_element. */ + struct cds_list_head list; + /* Node in the channel_triggers_ht */ + struct cds_lfht_node channel_triggers_ht_node; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; +}; + +/* + * List of triggers applying to a given session. + * + * See: + * - lttng_session_trigger_list_create() + * - lttng_session_trigger_list_build() + * - lttng_session_trigger_list_destroy() + * - lttng_session_trigger_list_add() + */ +struct lttng_session_trigger_list { + /* + * Not owned by this; points to the session_info structure's + * session name. + */ + const char *session_name; + /* List of struct lttng_trigger_list_element. */ + struct cds_list_head list; + /* Node in the session_triggers_ht */ + struct cds_lfht_node session_triggers_ht_node; + /* + * Weak reference to the notification system's session triggers + * hashtable. + * + * The session trigger list structure structure is owned by + * the session's session_info. + * + * The session_info is kept alive the the channel_infos holding a + * reference to it (reference counting). When those channels are + * destroyed (at runtime or on teardown), the reference they hold + * to the session_info are released. On destruction of session_info, + * session_info_destroy() will remove the list of triggers applying + * to this session from the notification system's state. + * + * This implies that the session_triggers_ht must be destroyed + * after the channels. + */ + struct cds_lfht *session_triggers_ht; + /* Used for delayed RCU reclaim. */ + struct rcu_head rcu_node; +}; + +struct lttng_trigger_ht_element { + struct lttng_trigger *trigger; + struct cds_lfht_node node; + struct cds_lfht_node node_by_name_uid; + struct cds_list_head client_list_trigger_node; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; +}; + +struct lttng_condition_list_element { + struct lttng_condition *condition; + struct cds_list_head node; +}; + +struct channel_state_sample { + struct channel_key key; + struct cds_lfht_node channel_state_ht_node; + uint64_t highest_usage; + uint64_t lowest_usage; + uint64_t channel_total_consumed; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; +}; + +static unsigned long hash_channel_key(struct channel_key *key); +static int evaluate_buffer_condition(const struct lttng_condition *condition, + struct lttng_evaluation **evaluation, + const struct notification_thread_state *state, + const struct channel_state_sample *previous_sample, + const struct channel_state_sample *latest_sample, + uint64_t previous_session_consumed_total, + uint64_t latest_session_consumed_total, + struct channel_info *channel_info); +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); + + +/* session_info API */ +static +void session_info_destroy(void *_data); +static +void session_info_get(struct session_info *session_info); +static +void session_info_put(struct session_info *session_info); +static +struct session_info *session_info_create(const char *name, + uid_t uid, gid_t gid, + struct lttng_session_trigger_list *trigger_list, + struct cds_lfht *sessions_ht); +static +void session_info_add_channel(struct session_info *session_info, + struct channel_info *channel_info); +static +void session_info_remove_channel(struct session_info *session_info, + struct channel_info *channel_info); + +/* lttng_session_trigger_list API */ +static +struct lttng_session_trigger_list *lttng_session_trigger_list_create( + const char *session_name, + struct cds_lfht *session_triggers_ht); +static +struct lttng_session_trigger_list *lttng_session_trigger_list_build( + const struct notification_thread_state *state, + const char *session_name); +static +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, + 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 handle_one_event_notifier_notification( + struct notification_thread_state *state, + int pipe, enum lttng_domain_type domain); + +static +void free_lttng_trigger_ht_element_rcu(struct rcu_head *node); + +static +int match_client_socket(struct cds_lfht_node *node, const void *key) +{ + /* This double-cast is intended to supress pointer-to-cast warning. */ + const int socket = (int) (intptr_t) key; + const struct notification_client *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->id == id; +} + +static +int match_channel_trigger_list(struct cds_lfht_node *node, const void *key) +{ + struct channel_key *channel_key = (struct channel_key *) key; + struct lttng_channel_trigger_list *trigger_list; + + trigger_list = caa_container_of(node, struct lttng_channel_trigger_list, + channel_triggers_ht_node); + + return !!((channel_key->key == trigger_list->channel_key.key) && + (channel_key->domain == trigger_list->channel_key.domain)); +} + +static +int match_session_trigger_list(struct cds_lfht_node *node, const void *key) +{ + const char *session_name = (const char *) key; + struct lttng_session_trigger_list *trigger_list; + + trigger_list = caa_container_of(node, struct lttng_session_trigger_list, + session_triggers_ht_node); + + return !!(strcmp(trigger_list->session_name, session_name) == 0); +} + +static +int match_channel_state_sample(struct cds_lfht_node *node, const void *key) +{ + struct channel_key *channel_key = (struct channel_key *) key; + struct channel_state_sample *sample; + + sample = caa_container_of(node, struct channel_state_sample, + channel_state_ht_node); + + return !!((channel_key->key == sample->key.key) && + (channel_key->domain == sample->key.domain)); +} + +static +int match_channel_info(struct cds_lfht_node *node, const void *key) +{ + struct channel_key *channel_key = (struct channel_key *) key; + struct channel_info *channel_info; + + channel_info = caa_container_of(node, struct channel_info, + channels_ht_node); + + return !!((channel_key->key == channel_info->key.key) && + (channel_key->domain == channel_info->key.domain)); +} + +static +int match_trigger(struct cds_lfht_node *node, const void *key) +{ + struct lttng_trigger *trigger_key = (struct lttng_trigger *) key; + struct lttng_trigger_ht_element *trigger_ht_element; + + trigger_ht_element = caa_container_of(node, struct lttng_trigger_ht_element, + node); + + return !!lttng_trigger_is_equal(trigger_key, trigger_ht_element->trigger); +} + +static +int match_trigger_token(struct cds_lfht_node *node, const void *key) +{ + const uint64_t *_key = (uint64_t *) 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 +int match_client_list_condition(struct cds_lfht_node *node, const void *key) +{ + struct lttng_condition *condition_key = (struct lttng_condition *) key; + struct notification_client_list *client_list; + const struct lttng_condition *condition; + + LTTNG_ASSERT(condition_key); + + client_list = caa_container_of(node, struct notification_client_list, + notification_trigger_clients_ht_node); + condition = client_list->condition; + + return !!lttng_condition_is_equal(condition_key, condition); +} + +static +int match_session(struct cds_lfht_node *node, const void *key) +{ + const char *name = (const char *) key; + struct session_info *session_info = caa_container_of( + node, struct session_info, sessions_ht_node); + + return !strcmp(session_info->name, name); +} + +static +const char *notification_command_type_str( + enum notification_thread_command_type type) +{ + switch (type) { + case NOTIFICATION_COMMAND_TYPE_REGISTER_TRIGGER: + return "REGISTER_TRIGGER"; + case NOTIFICATION_COMMAND_TYPE_UNREGISTER_TRIGGER: + return "UNREGISTER_TRIGGER"; + case NOTIFICATION_COMMAND_TYPE_ADD_CHANNEL: + return "ADD_CHANNEL"; + case NOTIFICATION_COMMAND_TYPE_REMOVE_CHANNEL: + return "REMOVE_CHANNEL"; + case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING: + return "SESSION_ROTATION_ONGOING"; + case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED: + return "SESSION_ROTATION_COMPLETED"; + case NOTIFICATION_COMMAND_TYPE_ADD_TRACER_EVENT_SOURCE: + return "ADD_TRACER_EVENT_SOURCE"; + case NOTIFICATION_COMMAND_TYPE_REMOVE_TRACER_EVENT_SOURCE: + return "REMOVE_TRACER_EVENT_SOURCE"; + case NOTIFICATION_COMMAND_TYPE_LIST_TRIGGERS: + return "LIST_TRIGGERS"; + case NOTIFICATION_COMMAND_TYPE_GET_TRIGGER: + return "GET_TRIGGER"; + case NOTIFICATION_COMMAND_TYPE_QUIT: + return "QUIT"; + case NOTIFICATION_COMMAND_TYPE_CLIENT_COMMUNICATION_UPDATE: + return "CLIENT_COMMUNICATION_UPDATE"; + default: + abort(); + } +} + +/* + * Match trigger based on name and credentials only. + * Name duplication is NOT allowed for the same uid. + */ +static +int match_trigger_by_name_uid(struct cds_lfht_node *node, + const void *key) +{ + bool match = false; + const char *element_trigger_name; + const char *key_name; + enum lttng_trigger_status status; + const struct lttng_credentials *key_creds; + const struct lttng_credentials *node_creds; + const struct lttng_trigger *trigger_key = + (const struct lttng_trigger *) key; + const struct lttng_trigger_ht_element *trigger_ht_element = + caa_container_of(node, + struct lttng_trigger_ht_element, + node_by_name_uid); + + status = lttng_trigger_get_name(trigger_ht_element->trigger, + &element_trigger_name); + element_trigger_name = status == LTTNG_TRIGGER_STATUS_OK ? + element_trigger_name : NULL; + + status = lttng_trigger_get_name(trigger_key, &key_name); + key_name = status == LTTNG_TRIGGER_STATUS_OK ? key_name : NULL; + + /* + * Compare the names. + * Consider null names as not equal. This is to maintain backwards + * compatibility with pre-2.13 anonymous triggers. Multiples anonymous + * triggers are allowed for a given user. + */ + if (!element_trigger_name || !key_name) { + goto end; + } + + if (strcmp(element_trigger_name, key_name) != 0) { + goto end; + } + + /* Compare the owners' UIDs. */ + key_creds = lttng_trigger_get_credentials(trigger_key); + node_creds = lttng_trigger_get_credentials(trigger_ht_element->trigger); + + match = lttng_credentials_is_equal_uid(key_creds, node_creds); + +end: + return match; +} + +/* + * Hash trigger based on name and credentials only. + */ +static +unsigned long hash_trigger_by_name_uid(const struct lttng_trigger *trigger) +{ + unsigned long hash = 0; + const struct lttng_credentials *trigger_creds; + const char *trigger_name; + enum lttng_trigger_status status; + + status = lttng_trigger_get_name(trigger, &trigger_name); + if (status == LTTNG_TRIGGER_STATUS_OK) { + hash = hash_key_str(trigger_name, lttng_ht_seed); + } + + trigger_creds = lttng_trigger_get_credentials(trigger); + hash ^= hash_key_ulong((void *) (unsigned long) LTTNG_OPTIONAL_GET(trigger_creds->uid), + lttng_ht_seed); + + return hash; +} + +static +unsigned long hash_channel_key(struct channel_key *key) +{ + unsigned long key_hash = hash_key_u64(&key->key, lttng_ht_seed); + unsigned long domain_hash = hash_key_ulong( + (void *) (unsigned long) key->domain, lttng_ht_seed); + + 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 + * object's state is updated. + * + * For instance, a condition bound to a channel will be evaluated everytime + * the channel's state is changed by a channel monitoring sample. + */ +static +enum lttng_object_type get_condition_binding_object( + const struct lttng_condition *condition) +{ + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + return LTTNG_OBJECT_TYPE_CHANNEL; + 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_MATCHES: + return LTTNG_OBJECT_TYPE_NONE; + default: + return LTTNG_OBJECT_TYPE_UNKNOWN; + } +} + +static +void free_channel_info_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct channel_info, rcu_node)); +} + +static +void channel_info_destroy(struct channel_info *channel_info) +{ + if (!channel_info) { + return; + } + + if (channel_info->session_info) { + session_info_remove_channel(channel_info->session_info, + channel_info); + session_info_put(channel_info->session_info); + } + if (channel_info->name) { + free(channel_info->name); + } + call_rcu(&channel_info->rcu_node, free_channel_info_rcu); +} + +static +void free_session_info_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct session_info, rcu_node)); +} + +/* Don't call directly, use the ref-counting mechanism. */ +static +void session_info_destroy(void *_data) +{ + struct session_info *session_info = (struct session_info *) _data; + int ret; + + LTTNG_ASSERT(session_info); + if (session_info->channel_infos_ht) { + ret = cds_lfht_destroy(session_info->channel_infos_ht, NULL); + if (ret) { + ERR("Failed to destroy channel information hash table"); + } + } + lttng_session_trigger_list_destroy(session_info->trigger_list); + + rcu_read_lock(); + cds_lfht_del(session_info->sessions_ht, + &session_info->sessions_ht_node); + rcu_read_unlock(); + free(session_info->name); + call_rcu(&session_info->rcu_node, free_session_info_rcu); +} + +static +void session_info_get(struct session_info *session_info) +{ + if (!session_info) { + return; + } + lttng_ref_get(&session_info->ref); +} + +static +void session_info_put(struct session_info *session_info) +{ + if (!session_info) { + return; + } + lttng_ref_put(&session_info->ref); +} + +static +struct session_info *session_info_create(const char *name, uid_t uid, gid_t gid, + struct lttng_session_trigger_list *trigger_list, + struct cds_lfht *sessions_ht) +{ + struct session_info *session_info; + + LTTNG_ASSERT(name); + + session_info = (struct session_info *) zmalloc(sizeof(*session_info)); + if (!session_info) { + goto end; + } + lttng_ref_init(&session_info->ref, session_info_destroy); + + session_info->channel_infos_ht = cds_lfht_new(DEFAULT_HT_SIZE, + 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!session_info->channel_infos_ht) { + goto error; + } + + cds_lfht_node_init(&session_info->sessions_ht_node); + session_info->name = strdup(name); + if (!session_info->name) { + goto error; + } + session_info->uid = uid; + session_info->gid = gid; + session_info->trigger_list = trigger_list; + session_info->sessions_ht = sessions_ht; +end: + return session_info; +error: + session_info_put(session_info); + return NULL; +} + +static +void session_info_add_channel(struct session_info *session_info, + struct channel_info *channel_info) +{ + rcu_read_lock(); + cds_lfht_add(session_info->channel_infos_ht, + hash_channel_key(&channel_info->key), + &channel_info->session_info_channels_ht_node); + rcu_read_unlock(); +} + +static +void session_info_remove_channel(struct session_info *session_info, + struct channel_info *channel_info) +{ + rcu_read_lock(); + cds_lfht_del(session_info->channel_infos_ht, + &channel_info->session_info_channels_ht_node); + rcu_read_unlock(); +} + +static +struct channel_info *channel_info_create(const char *channel_name, + struct channel_key *channel_key, uint64_t channel_capacity, + struct session_info *session_info) +{ + struct channel_info *channel_info = (struct channel_info *) zmalloc(sizeof(*channel_info)); + + if (!channel_info) { + goto end; + } + + cds_lfht_node_init(&channel_info->channels_ht_node); + cds_lfht_node_init(&channel_info->session_info_channels_ht_node); + memcpy(&channel_info->key, channel_key, sizeof(*channel_key)); + channel_info->capacity = channel_capacity; + + channel_info->name = strdup(channel_name); + if (!channel_info->name) { + goto error; + } + + /* + * Set the references between session and channel infos: + * - channel_info holds a strong reference to session_info + * - session_info holds a weak reference to channel_info + */ + session_info_get(session_info); + session_info_add_channel(session_info, channel_info); + channel_info->session_info = session_info; +end: + return channel_info; +error: + channel_info_destroy(channel_info); + return NULL; +} + +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; + + lttng_condition_put(list->condition); + + 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->clients_list, node) { + free(client_list_element); + } + + LTTNG_ASSERT(cds_list_empty(&list->triggers_list)); + + pthread_mutex_destroy(&list->lock); + call_rcu(&list->rcu_node, free_notification_client_list_rcu); +} + +static +bool condition_applies_to_client(const struct lttng_condition *condition, + struct notification_client *client) +{ + bool applies = false; + struct lttng_condition_list_element *condition_list_element; + + cds_list_for_each_entry(condition_list_element, &client->condition_list, + node) { + applies = lttng_condition_is_equal( + condition_list_element->condition, + condition); + if (applies) { + break; + } + } + + return applies; +} + +static +struct notification_client_list *notification_client_list_create( + struct notification_thread_state *state, + const struct lttng_condition *condition) +{ + struct notification_client *client; + struct cds_lfht_iter iter; + struct notification_client_list *client_list; + + client_list = (notification_client_list *) zmalloc(sizeof(*client_list)); + if (!client_list) { + PERROR("Failed to allocate notification client list"); + goto end; + } + + pthread_mutex_init(&client_list->lock, NULL); + /* + * The trigger that owns the condition has the first reference to this + * client list. + */ + urcu_ref_init(&client_list->ref); + cds_lfht_node_init(&client_list->notification_trigger_clients_ht_node); + CDS_INIT_LIST_HEAD(&client_list->clients_list); + CDS_INIT_LIST_HEAD(&client_list->triggers_list); + + /* + * Create a copy of the condition so that it's independent of any + * trigger. The client list may outlive the trigger object (which owns + * the condition) that is used to create it. + */ + client_list->condition = lttng_condition_copy(condition); + + /* Build a list of clients to which this new condition applies. */ + cds_lfht_for_each_entry (state->client_socket_ht, &iter, client, + client_socket_ht_node) { + struct notification_client_list_element *client_list_element; + + if (!condition_applies_to_client(condition, client)) { + continue; + } + + client_list_element = (notification_client_list_element *) zmalloc(sizeof(*client_list_element)); + if (!client_list_element) { + 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->clients_list); + } + + client_list->notification_trigger_clients_ht = + state->notification_trigger_clients_ht; + + rcu_read_lock(); + /* + * Add the client list to the global list of client list. + */ + cds_lfht_add_unique(state->notification_trigger_clients_ht, + lttng_condition_hash(client_list->condition), + match_client_list_condition, + client_list->condition, + &client_list->notification_trigger_clients_ht_node); + rcu_read_unlock(); + goto end; + +error_put_client_list: + notification_client_list_put(client_list); + client_list = NULL; + +end: + return client_list; +} + +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, + const struct lttng_condition *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); + 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; +} + +static +int evaluate_channel_condition_for_client( + const struct lttng_condition *condition, + struct notification_thread_state *state, + struct lttng_evaluation **evaluation, + uid_t *session_uid, gid_t *session_gid) +{ + int ret; + struct cds_lfht_iter iter; + struct cds_lfht_node *node; + struct channel_info *channel_info = NULL; + struct channel_key *channel_key = NULL; + 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) { + struct lttng_trigger_list_element *element; + + cds_list_for_each_entry(element, &channel_trigger_list->list, node) { + const struct lttng_condition *current_condition = + lttng_trigger_get_const_condition( + element->trigger); + + LTTNG_ASSERT(current_condition); + if (!lttng_condition_is_equal(condition, + current_condition)) { + continue; + } + + /* Found the trigger, save the channel key. */ + channel_key = &channel_trigger_list->channel_key; + break; + } + if (channel_key) { + /* The channel key was found stop iteration. */ + break; + } + } + + if (!channel_key){ + /* No channel found; normal exit. */ + DBG("No known channel associated with newly subscribed-to condition"); + ret = 0; + goto end; + } + + /* Fetch channel info for the matching channel. */ + cds_lfht_lookup(state->channels_ht, + hash_channel_key(channel_key), + match_channel_info, + channel_key, + &iter); + node = cds_lfht_iter_get_node(&iter); + LTTNG_ASSERT(node); + channel_info = caa_container_of(node, struct channel_info, + channels_ht_node); + + /* Retrieve the channel's last sample, if it exists. */ + cds_lfht_lookup(state->channel_state_ht, + hash_channel_key(channel_key), + match_channel_state_sample, + channel_key, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (node) { + last_sample = caa_container_of(node, + struct channel_state_sample, + channel_state_ht_node); + } else { + /* Nothing to evaluate, no sample was ever taken. Normal exit */ + DBG("No channel sample associated with newly subscribed-to condition"); + ret = 0; + goto end; + } + + ret = evaluate_buffer_condition(condition, evaluation, state, + NULL, last_sample, + 0, channel_info->session_info->consumed_data_size, + channel_info); + if (ret) { + WARN("Fatal error occurred while evaluating a newly subscribed-to condition"); + goto end; + } + + *session_uid = channel_info->session_info->uid; + *session_gid = channel_info->session_info->gid; +end: + rcu_read_unlock(); + return ret; +} + +static +const char *get_condition_session_name(const struct lttng_condition *condition) +{ + const char *session_name = NULL; + enum lttng_condition_status status; + + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + status = lttng_condition_buffer_usage_get_session_name( + condition, &session_name); + break; + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + status = lttng_condition_session_consumed_size_get_session_name( + condition, &session_name); + break; + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED: + status = lttng_condition_session_rotation_get_session_name( + condition, &session_name); + break; + default: + abort(); + } + if (status != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to retrieve session rotation condition's session name"); + goto end; + } +end: + return session_name; +} + +static +int evaluate_session_condition_for_client( + const struct lttng_condition *condition, + struct notification_thread_state *state, + struct lttng_evaluation **evaluation, + uid_t *session_uid, gid_t *session_gid) +{ + int ret; + struct cds_lfht_iter iter; + struct cds_lfht_node *node; + 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. */ + cds_lfht_lookup(state->sessions_ht, + hash_key_str(session_name, lttng_ht_seed), + match_session, + session_name, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (!node) { + DBG("No known session matching name \"%s\"", + session_name); + ret = 0; + goto end; + } + + session_info = caa_container_of(node, struct session_info, + sessions_ht_node); + session_info_get(session_info); + + /* + * Evaluation is performed in-line here since only one type of + * session-bound condition is handled for the moment. + */ + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING: + if (!session_info->rotation.ongoing) { + ret = 0; + goto end_session_put; + } + + *evaluation = lttng_evaluation_session_rotation_ongoing_create( + session_info->rotation.id); + if (!*evaluation) { + /* Fatal error. */ + ERR("Failed to create session rotation ongoing evaluation for session \"%s\"", + session_info->name); + ret = -1; + goto end_session_put; + } + ret = 0; + break; + default: + ret = 0; + goto end_session_put; + } + + *session_uid = session_info->uid; + *session_gid = session_info->gid; + +end_session_put: + session_info_put(session_info); +end: + rcu_read_unlock(); + return ret; +} + +static +int evaluate_condition_for_client(const struct lttng_trigger *trigger, + const struct lttng_condition *condition, + struct notification_client *client, + struct notification_thread_state *state) +{ + int ret; + struct lttng_evaluation *evaluation = NULL; + 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; + + LTTNG_ASSERT(trigger); + LTTNG_ASSERT(condition); + LTTNG_ASSERT(client); + LTTNG_ASSERT(state); + + switch (get_condition_binding_object(condition)) { + case LTTNG_OBJECT_TYPE_SESSION: + ret = evaluate_session_condition_for_client(condition, state, + &evaluation, &object_uid, &object_gid); + break; + case LTTNG_OBJECT_TYPE_CHANNEL: + ret = evaluate_channel_condition_for_client(condition, state, + &evaluation, &object_uid, &object_gid); + break; + case LTTNG_OBJECT_TYPE_NONE: + DBG("Newly subscribed-to condition not bound to object, nothing to evaluate"); + ret = 0; + goto end; + case LTTNG_OBJECT_TYPE_UNKNOWN: + default: + ret = -1; + goto end; + } + if (ret) { + /* Fatal error. */ + goto end; + } + if (!evaluation) { + /* Evaluation yielded nothing. Normal exit. */ + DBG("Newly subscribed-to condition evaluated to false, nothing to report to client"); + ret = 0; + goto end; + } + + /* + * Create a temporary client list with the client currently + * subscribing. + */ + cds_lfht_node_init(&client_list.notification_trigger_clients_ht_node); + CDS_INIT_LIST_HEAD(&client_list.clients_list); + + CDS_INIT_LIST_HEAD(&client_list_element.node); + client_list_element.client = client; + cds_list_add(&client_list_element.node, &client_list.clients_list); + + /* Send evaluation result to the newly-subscribed client. */ + DBG("Newly subscribed-to condition evaluated to true, notifying client"); + ret = send_evaluation_to_clients(trigger, evaluation, &client_list, + state, object_uid, object_gid); + +end: + return ret; +} + +static +int notification_thread_client_subscribe(struct notification_client *client, + struct lttng_condition *condition, + struct notification_thread_state *state, + enum lttng_notification_channel_status *_status) +{ + int ret = 0; + 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; + struct lttng_trigger_ht_element *trigger_ht_element; + enum lttng_notification_channel_status status = + LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; + + /* + * Ensure that the client has not already subscribed to this condition + * before. + */ + cds_list_for_each_entry(condition_list_element, &client->condition_list, node) { + if (lttng_condition_is_equal(condition_list_element->condition, + condition)) { + status = LTTNG_NOTIFICATION_CHANNEL_STATUS_ALREADY_SUBSCRIBED; + goto end; + } + } + + condition_list_element = (lttng_condition_list_element *) zmalloc(sizeof(*condition_list_element)); + if (!condition_list_element) { + ret = -1; + goto error; + } + client_list_element = (notification_client_list_element *) zmalloc(sizeof(*client_list_element)); + if (!client_list_element) { + ret = -1; + goto error; + } + + /* + * Add the newly-subscribed condition to the client's subscription list. + */ + CDS_INIT_LIST_HEAD(&condition_list_element->node); + condition_list_element->condition = condition; + condition = NULL; + cds_list_add(&condition_list_element->node, &client->condition_list); + + client_list = get_client_list_from_condition( + state, condition_list_element->condition); + if (!client_list) { + /* + * No notification-emiting trigger registered with this + * condition. We don't evaluate the condition right away + * since this trigger is not registered yet. + */ + free(client_list_element); + 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. + * + * Note the iteration on all triggers which share an identical + * `condition` than the one to which the client is registering. This is + * done to ensure that the client receives a distinct notification for + * all triggers that have a `notify` action that have this condition. + */ + pthread_mutex_lock(&client_list->lock); + cds_list_for_each_entry(trigger_ht_element, + &client_list->triggers_list, client_list_trigger_node) { + if (evaluate_condition_for_client(trigger_ht_element->trigger, condition_list_element->condition, + client, state)) { + WARN("Evaluation of a condition on client subscription failed, aborting."); + ret = -1; + free(client_list_element); + pthread_mutex_unlock(&client_list->lock); + goto end; + } + } + pthread_mutex_unlock(&client_list->lock); + + /* + * Add the client to the list of clients interested in a given trigger + * if a "notification" trigger with a corresponding condition was + * added prior. + */ + 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->clients_list); + pthread_mutex_unlock(&client_list->lock); +end: + if (_status) { + *_status = status; + } + if (client_list) { + notification_client_list_put(client_list); + } + lttng_condition_destroy(condition); + return ret; +error: + free(condition_list_element); + free(client_list_element); + lttng_condition_destroy(condition); + return ret; +} + +static +int notification_thread_client_unsubscribe( + struct notification_client *client, + struct lttng_condition *condition, + struct notification_thread_state *state, + enum lttng_notification_channel_status *_status) +{ + struct notification_client_list *client_list; + struct lttng_condition_list_element *condition_list_element, + *condition_tmp; + struct notification_client_list_element *client_list_element, + *client_tmp; + bool condition_found = false; + enum lttng_notification_channel_status status = + LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; + + /* Remove the condition from the client's condition list. */ + cds_list_for_each_entry_safe(condition_list_element, condition_tmp, + &client->condition_list, node) { + if (!lttng_condition_is_equal(condition_list_element->condition, + condition)) { + continue; + } + + cds_list_del(&condition_list_element->node); + /* + * The caller may be iterating on the client's conditions to + * tear down a client's connection. In this case, the condition + * will be destroyed at the end. + */ + if (condition != condition_list_element->condition) { + lttng_condition_destroy( + condition_list_element->condition); + } + free(condition_list_element); + condition_found = true; + break; + } + + if (!condition_found) { + status = LTTNG_NOTIFICATION_CHANNEL_STATUS_UNKNOWN_CONDITION; + goto end; + } + + /* + * Remove the client from the list of clients interested the trigger + * matching the condition. + */ + client_list = get_client_list_from_condition(state, condition); + if (!client_list) { + goto end; + } + + pthread_mutex_lock(&client_list->lock); + cds_list_for_each_entry_safe(client_list_element, client_tmp, + &client_list->clients_list, node) { + if (client_list_element->client->id != client->id) { + continue; + } + cds_list_del(&client_list_element->node); + free(client_list_element); + break; + } + pthread_mutex_unlock(&client_list->lock); + notification_client_list_put(client_list); + client_list = NULL; +end: + lttng_condition_destroy(condition); + if (_status) { + *_status = status; + } + return 0; +} + +static +void free_notification_client_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct notification_client, rcu_node)); +} + +static +void notification_client_destroy(struct notification_client *client, + struct notification_thread_state *state) +{ + if (!client) { + return; + } + + /* + * 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_payload_reset(&client->communication.inbound.payload); + lttng_payload_reset(&client->communication.outbound.payload); + pthread_mutex_destroy(&client->lock); + call_rcu(&client->rcu_node, free_notification_client_rcu); +} + +/* + * Call with rcu_read_lock held (and hold for the lifetime of the returned + * client pointer). + */ +static +struct notification_client *get_client_from_socket(int socket, + 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_socket_ht, + hash_client_socket(socket), + match_client_socket, + (void *) (unsigned long) socket, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (!node) { + goto end; + } + + client = caa_container_of(node, struct notification_client, + client_socket_ht_node); +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, + const struct channel_info *channel_info) +{ + enum lttng_condition_status status; + enum lttng_domain_type condition_domain; + const char *condition_session_name = NULL; + const char *condition_channel_name = NULL; + + status = lttng_condition_buffer_usage_get_domain_type(condition, + &condition_domain); + LTTNG_ASSERT(status == LTTNG_CONDITION_STATUS_OK); + if (channel_info->key.domain != condition_domain) { + goto fail; + } + + status = lttng_condition_buffer_usage_get_session_name( + condition, &condition_session_name); + LTTNG_ASSERT((status == LTTNG_CONDITION_STATUS_OK) && condition_session_name); + + status = lttng_condition_buffer_usage_get_channel_name( + condition, &condition_channel_name); + LTTNG_ASSERT((status == LTTNG_CONDITION_STATUS_OK) && condition_channel_name); + + if (strcmp(channel_info->session_info->name, condition_session_name)) { + goto fail; + } + if (strcmp(channel_info->name, condition_channel_name)) { + goto fail; + } + + return true; +fail: + return false; +} + +static +bool session_consumed_size_condition_applies_to_channel( + const struct lttng_condition *condition, + const struct channel_info *channel_info) +{ + enum lttng_condition_status status; + const char *condition_session_name = NULL; + + status = lttng_condition_session_consumed_size_get_session_name( + condition, &condition_session_name); + LTTNG_ASSERT((status == LTTNG_CONDITION_STATUS_OK) && condition_session_name); + + if (strcmp(channel_info->session_info->name, condition_session_name)) { + goto fail; + } + + return true; +fail: + return false; +} + +static +bool trigger_applies_to_channel(const struct lttng_trigger *trigger, + const struct channel_info *channel_info) +{ + const struct lttng_condition *condition; + bool trigger_applies; + + condition = lttng_trigger_get_const_condition(trigger); + if (!condition) { + goto fail; + } + + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + trigger_applies = buffer_usage_condition_applies_to_channel( + condition, channel_info); + break; + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + trigger_applies = session_consumed_size_condition_applies_to_channel( + condition, channel_info); + break; + default: + goto fail; + } + + return trigger_applies; +fail: + return false; +} + +/* Must be called with RCU read lock held. */ +static +struct lttng_session_trigger_list *get_session_trigger_list( + struct notification_thread_state *state, + const char *session_name) +{ + struct lttng_session_trigger_list *list = NULL; + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + + cds_lfht_lookup(state->session_triggers_ht, + hash_key_str(session_name, lttng_ht_seed), + match_session_trigger_list, + session_name, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (!node) { + /* + * Not an error, the list of triggers applying to that session + * will be initialized when the session is created. + */ + DBG("No trigger list found for session \"%s\" as it is not yet known to the notification system", + session_name); + goto end; + } + + list = caa_container_of(node, + struct lttng_session_trigger_list, + session_triggers_ht_node); +end: + return list; +} + +/* + * Allocate an empty lttng_session_trigger_list for the session named + * 'session_name'. + * + * No ownership of 'session_name' is assumed by the session trigger list. + * It is the caller's responsability to ensure the session name is alive + * for as long as this list is. + */ +static +struct lttng_session_trigger_list *lttng_session_trigger_list_create( + const char *session_name, + struct cds_lfht *session_triggers_ht) +{ + struct lttng_session_trigger_list *list; + + list = (lttng_session_trigger_list *) zmalloc(sizeof(*list)); + if (!list) { + goto end; + } + list->session_name = session_name; + CDS_INIT_LIST_HEAD(&list->list); + cds_lfht_node_init(&list->session_triggers_ht_node); + list->session_triggers_ht = session_triggers_ht; + + rcu_read_lock(); + /* Publish the list through the session_triggers_ht. */ + cds_lfht_add(session_triggers_ht, + hash_key_str(session_name, lttng_ht_seed), + &list->session_triggers_ht_node); + rcu_read_unlock(); +end: + return list; +} + +static +void free_session_trigger_list_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct lttng_session_trigger_list, + rcu_node)); +} + +static +void lttng_session_trigger_list_destroy(struct lttng_session_trigger_list *list) +{ + struct lttng_trigger_list_element *trigger_list_element, *tmp; + + /* Empty the list element by element, and then free the list itself. */ + cds_list_for_each_entry_safe(trigger_list_element, tmp, + &list->list, node) { + cds_list_del(&trigger_list_element->node); + free(trigger_list_element); + } + rcu_read_lock(); + /* Unpublish the list from the session_triggers_ht. */ + cds_lfht_del(list->session_triggers_ht, + &list->session_triggers_ht_node); + rcu_read_unlock(); + call_rcu(&list->rcu_node, free_session_trigger_list_rcu); +} + +static +int lttng_session_trigger_list_add(struct lttng_session_trigger_list *list, + struct lttng_trigger *trigger) +{ + int ret = 0; + struct lttng_trigger_list_element *new_element = + (lttng_trigger_list_element *) zmalloc(sizeof(*new_element)); + + if (!new_element) { + ret = -1; + goto end; + } + CDS_INIT_LIST_HEAD(&new_element->node); + new_element->trigger = trigger; + cds_list_add(&new_element->node, &list->list); +end: + return ret; +} + +static +bool trigger_applies_to_session(const struct lttng_trigger *trigger, + const char *session_name) +{ + bool applies = false; + const struct lttng_condition *condition; + + 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 condition_status; + const char *condition_session_name; + + condition_status = lttng_condition_session_rotation_get_session_name( + condition, &condition_session_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to retrieve session rotation condition's session name"); + goto end; + } + + LTTNG_ASSERT(condition_session_name); + applies = !strcmp(condition_session_name, session_name); + break; + } + default: + goto end; + } +end: + return applies; +} + +/* + * Allocate and initialize an lttng_session_trigger_list which contains + * all triggers that apply to the session named 'session_name'. + * + * No ownership of 'session_name' is assumed by the session trigger list. + * It is the caller's responsability to ensure the session name is alive + * for as long as this list is. + */ +static +struct lttng_session_trigger_list *lttng_session_trigger_list_build( + const struct notification_thread_state *state, + const char *session_name) +{ + int trigger_count = 0; + struct lttng_session_trigger_list *session_trigger_list = NULL; + struct lttng_trigger_ht_element *trigger_ht_element = NULL; + struct cds_lfht_iter iter; + + session_trigger_list = lttng_session_trigger_list_create(session_name, + state->session_triggers_ht); + + /* Add all triggers applying to the session named 'session_name'. */ + cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element, + node) { + int ret; + + if (!trigger_applies_to_session(trigger_ht_element->trigger, + session_name)) { + continue; + } + + ret = lttng_session_trigger_list_add(session_trigger_list, + trigger_ht_element->trigger); + if (ret) { + goto error; + } + + trigger_count++; + } + + DBG("Found %i triggers that apply to newly created session", + trigger_count); + return session_trigger_list; +error: + lttng_session_trigger_list_destroy(session_trigger_list); + return NULL; +} + +static +struct session_info *find_or_create_session_info( + struct notification_thread_state *state, + const char *name, uid_t uid, gid_t gid) +{ + struct session_info *session = NULL; + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + struct lttng_session_trigger_list *trigger_list; + + rcu_read_lock(); + cds_lfht_lookup(state->sessions_ht, + hash_key_str(name, lttng_ht_seed), + match_session, + name, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (node) { + DBG("Found session info of session \"%s\" (uid = %i, gid = %i)", + name, uid, gid); + session = caa_container_of(node, struct session_info, + sessions_ht_node); + LTTNG_ASSERT(session->uid == uid); + LTTNG_ASSERT(session->gid == gid); + session_info_get(session); + goto end; + } + + trigger_list = lttng_session_trigger_list_build(state, name); + if (!trigger_list) { + goto error; + } + + session = session_info_create(name, uid, gid, trigger_list, + state->sessions_ht); + if (!session) { + ERR("Failed to allocation session info for session \"%s\" (uid = %i, gid = %i)", + name, uid, gid); + lttng_session_trigger_list_destroy(trigger_list); + goto error; + } + trigger_list = NULL; + + cds_lfht_add(state->sessions_ht, hash_key_str(name, lttng_ht_seed), + &session->sessions_ht_node); +end: + rcu_read_unlock(); + return session; +error: + rcu_read_unlock(); + session_info_put(session); + return NULL; +} + +static +int handle_notification_thread_command_add_channel( + struct notification_thread_state *state, + const char *session_name, uid_t session_uid, gid_t session_gid, + const char *channel_name, enum lttng_domain_type channel_domain, + uint64_t channel_key_int, uint64_t channel_capacity, + enum lttng_error_code *cmd_result) +{ + struct cds_list_head trigger_list; + struct channel_info *new_channel_info = NULL; + struct channel_key channel_key = { + .key = channel_key_int, + .domain = channel_domain, + }; + struct lttng_channel_trigger_list *channel_trigger_list = NULL; + struct lttng_trigger_ht_element *trigger_ht_element = NULL; + int trigger_count = 0; + struct cds_lfht_iter iter; + struct session_info *session_info = NULL; + + DBG("Adding channel %s from session %s, channel key = %" PRIu64 " in %s domain", + channel_name, session_name, channel_key_int, + lttng_domain_type_str(channel_domain)); + + CDS_INIT_LIST_HEAD(&trigger_list); + + session_info = find_or_create_session_info(state, session_name, + session_uid, session_gid); + if (!session_info) { + /* Allocation error or an internal error occurred. */ + goto error; + } + + new_channel_info = channel_info_create(channel_name, &channel_key, + channel_capacity, session_info); + if (!new_channel_info) { + goto error; + } + + rcu_read_lock(); + /* Build a list of all triggers applying to the new channel. */ + cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element, + node) { + struct lttng_trigger_list_element *new_element; + + if (!trigger_applies_to_channel(trigger_ht_element->trigger, + new_channel_info)) { + continue; + } + + new_element = (lttng_trigger_list_element *) zmalloc(sizeof(*new_element)); + if (!new_element) { + rcu_read_unlock(); + goto error; + } + CDS_INIT_LIST_HEAD(&new_element->node); + new_element->trigger = trigger_ht_element->trigger; + cds_list_add(&new_element->node, &trigger_list); + trigger_count++; + } + rcu_read_unlock(); + + DBG("Found %i triggers that apply to newly added channel", + trigger_count); + channel_trigger_list = (lttng_channel_trigger_list *) zmalloc(sizeof(*channel_trigger_list)); + if (!channel_trigger_list) { + goto error; + } + channel_trigger_list->channel_key = new_channel_info->key; + CDS_INIT_LIST_HEAD(&channel_trigger_list->list); + cds_lfht_node_init(&channel_trigger_list->channel_triggers_ht_node); + cds_list_splice(&trigger_list, &channel_trigger_list->list); + + rcu_read_lock(); + /* Add channel to the channel_ht which owns the channel_infos. */ + cds_lfht_add(state->channels_ht, + hash_channel_key(&new_channel_info->key), + &new_channel_info->channels_ht_node); + /* + * Add the list of triggers associated with this channel to the + * channel_triggers_ht. + */ + cds_lfht_add(state->channel_triggers_ht, + hash_channel_key(&new_channel_info->key), + &channel_trigger_list->channel_triggers_ht_node); + rcu_read_unlock(); + session_info_put(session_info); + *cmd_result = LTTNG_OK; + return 0; +error: + channel_info_destroy(new_channel_info); + session_info_put(session_info); + return 1; +} + +static +void free_channel_trigger_list_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct lttng_channel_trigger_list, + rcu_node)); +} + +static +void free_channel_state_sample_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct channel_state_sample, + rcu_node)); +} + +static +int handle_notification_thread_command_remove_channel( + struct notification_thread_state *state, + uint64_t channel_key, enum lttng_domain_type domain, + enum lttng_error_code *cmd_result) +{ + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + struct lttng_channel_trigger_list *trigger_list; + struct lttng_trigger_list_element *trigger_list_element, *tmp; + struct channel_key key = { .key = channel_key, .domain = domain }; + struct channel_info *channel_info; + + DBG("Removing channel key = %" PRIu64 " in %s domain", + channel_key, lttng_domain_type_str(domain)); + + rcu_read_lock(); + + cds_lfht_lookup(state->channel_triggers_ht, + hash_channel_key(&key), + match_channel_trigger_list, + &key, + &iter); + node = cds_lfht_iter_get_node(&iter); + /* + * There is a severe internal error if we are being asked to remove a + * channel that doesn't exist. + */ + if (!node) { + ERR("Channel being removed is unknown to the notification thread"); + goto end; + } + + /* Free the list of triggers associated with this channel. */ + trigger_list = caa_container_of(node, struct lttng_channel_trigger_list, + channel_triggers_ht_node); + cds_list_for_each_entry_safe(trigger_list_element, tmp, + &trigger_list->list, node) { + cds_list_del(&trigger_list_element->node); + free(trigger_list_element); + } + cds_lfht_del(state->channel_triggers_ht, node); + call_rcu(&trigger_list->rcu_node, free_channel_trigger_list_rcu); + + /* Free sampled channel state. */ + cds_lfht_lookup(state->channel_state_ht, + hash_channel_key(&key), + match_channel_state_sample, + &key, + &iter); + node = cds_lfht_iter_get_node(&iter); + /* + * This is expected to be NULL if the channel is destroyed before we + * received a sample. + */ + if (node) { + struct channel_state_sample *sample = caa_container_of(node, + struct channel_state_sample, + channel_state_ht_node); + + cds_lfht_del(state->channel_state_ht, node); + call_rcu(&sample->rcu_node, free_channel_state_sample_rcu); + } + + /* Remove the channel from the channels_ht and free it. */ + cds_lfht_lookup(state->channels_ht, + hash_channel_key(&key), + match_channel_info, + &key, + &iter); + node = cds_lfht_iter_get_node(&iter); + LTTNG_ASSERT(node); + channel_info = caa_container_of(node, struct channel_info, + channels_ht_node); + cds_lfht_del(state->channels_ht, node); + channel_info_destroy(channel_info); +end: + rcu_read_unlock(); + *cmd_result = LTTNG_OK; + return 0; +} + +static +int handle_notification_thread_command_session_rotation( + struct notification_thread_state *state, + enum notification_thread_command_type cmd_type, + const char *session_name, uid_t session_uid, gid_t session_gid, + uint64_t trace_archive_chunk_id, + struct lttng_trace_archive_location *location, + enum lttng_error_code *_cmd_result) +{ + int ret = 0; + enum lttng_error_code cmd_result = LTTNG_OK; + struct lttng_session_trigger_list *trigger_list; + struct lttng_trigger_list_element *trigger_list_element; + struct session_info *session_info; + const struct lttng_credentials session_creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(session_uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(session_gid), + }; + + rcu_read_lock(); + + session_info = find_or_create_session_info(state, session_name, + session_uid, session_gid); + if (!session_info) { + /* Allocation error or an internal error occurred. */ + ret = -1; + cmd_result = LTTNG_ERR_NOMEM; + goto end; + } + + session_info->rotation.ongoing = + cmd_type == NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING; + session_info->rotation.id = trace_archive_chunk_id; + trigger_list = get_session_trigger_list(state, session_name); + if (!trigger_list) { + DBG("No triggers applying to session \"%s\" found", + session_name); + goto end; + } + + cds_list_for_each_entry(trigger_list_element, &trigger_list->list, + node) { + const struct lttng_condition *condition; + struct lttng_trigger *trigger; + struct notification_client_list *client_list; + struct lttng_evaluation *evaluation = NULL; + enum lttng_condition_type condition_type; + enum action_executor_status executor_status; + + trigger = trigger_list_element->trigger; + condition = lttng_trigger_get_const_condition(trigger); + LTTNG_ASSERT(condition); + condition_type = lttng_condition_get_type(condition); + + if (condition_type == LTTNG_CONDITION_TYPE_SESSION_ROTATION_ONGOING && + cmd_type != NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING) { + continue; + } else if (condition_type == LTTNG_CONDITION_TYPE_SESSION_ROTATION_COMPLETED && + cmd_type != NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED) { + continue; + } + + client_list = get_client_list_from_condition(state, condition); + if (cmd_type == NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING) { + evaluation = lttng_evaluation_session_rotation_ongoing_create( + trace_archive_chunk_id); + } else { + evaluation = lttng_evaluation_session_rotation_completed_create( + trace_archive_chunk_id, location); + } + + if (!evaluation) { + /* Internal error */ + ret = -1; + cmd_result = LTTNG_ERR_UNK; + goto put_list; + } + + /* + * Ownership of `evaluation` transferred to the action executor + * no matter the result. + */ + executor_status = action_executor_enqueue_trigger( + state->executor, trigger, evaluation, + &session_creds, client_list); + evaluation = NULL; + switch (executor_status) { + case ACTION_EXECUTOR_STATUS_OK: + break; + case ACTION_EXECUTOR_STATUS_ERROR: + case ACTION_EXECUTOR_STATUS_INVALID: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + */ + ERR("Fatal error occurred while enqueuing action associated with session rotation trigger"); + ret = -1; + goto put_list; + case ACTION_EXECUTOR_STATUS_OVERFLOW: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + * + * Not a fatal error. + */ + WARN("No space left when enqueuing action associated with session rotation trigger"); + ret = 0; + goto put_list; + default: + abort(); + } + +put_list: + notification_client_list_put(client_list); + if (caa_unlikely(ret)) { + break; + } + } +end: + session_info_put(session_info); + *_cmd_result = cmd_result; + rcu_read_unlock(); + return ret; +} + +static +int handle_notification_thread_command_add_tracer_event_source( + struct notification_thread_state *state, + int tracer_event_source_fd, + enum lttng_domain_type domain_type, + enum lttng_error_code *_cmd_result) +{ + int ret = 0; + enum lttng_error_code cmd_result = LTTNG_OK; + struct notification_event_tracer_event_source_element *element = NULL; + + element = (notification_event_tracer_event_source_element *) zmalloc(sizeof(*element)); + if (!element) { + cmd_result = LTTNG_ERR_NOMEM; + ret = -1; + goto end; + } + + element->fd = tracer_event_source_fd; + element->domain = domain_type; + + cds_list_add(&element->node, &state->tracer_event_sources_list); + + DBG3("Adding tracer event source fd to poll set: tracer_event_source_fd = %d, domain = '%s'", + tracer_event_source_fd, + lttng_domain_type_str(domain_type)); + + /* Adding the read side pipe to the event poll. */ + ret = lttng_poll_add(&state->events, tracer_event_source_fd, LPOLLIN | LPOLLERR); + if (ret < 0) { + ERR("Failed to add tracer event source to poll set: tracer_event_source_fd = %d, domain = '%s'", + tracer_event_source_fd, + lttng_domain_type_str(element->domain)); + cds_list_del(&element->node); + free(element); + goto end; + } + + element->is_fd_in_poll_set = true; + +end: + *_cmd_result = cmd_result; + return ret; +} + +static +int drain_event_notifier_notification_pipe( + struct notification_thread_state *state, + int pipe, enum lttng_domain_type domain) +{ + struct lttng_poll_event events = {0}; + int ret; + + ret = lttng_poll_create(&events, 1, LTTNG_CLOEXEC); + if (ret < 0) { + ERR("Error creating lttng_poll_event"); + goto end; + } + + ret = lttng_poll_add(&events, pipe, LPOLLIN); + if (ret < 0) { + ERR("Error adding fd event notifier notification pipe to lttng_poll_event: fd = %d", + pipe); + goto end; + } + + while (true) { + /* + * Continue to consume notifications as long as there are new + * ones coming in. The tracer has been asked to stop producing + * them. + * + * LPOLLIN is explicitly checked since LPOLLHUP is implicitly + * monitored (on Linux, at least) and will be returned when + * the pipe is closed but empty. + */ + ret = lttng_poll_wait_interruptible(&events, 0); + if (ret == 0 || (LTTNG_POLL_GETEV(&events, 0) & LPOLLIN) == 0) { + /* No more notification to be read on this pipe. */ + ret = 0; + goto end; + } else if (ret < 0) { + PERROR("Failed on lttng_poll_wait_interruptible() call"); + ret = -1; + goto end; + } + + ret = handle_one_event_notifier_notification(state, pipe, domain); + if (ret) { + ERR("Error consuming an event notifier notification from pipe: fd = %d", + pipe); + } + } +end: + lttng_poll_clean(&events); + return ret; +} + +static +struct notification_event_tracer_event_source_element * +find_tracer_event_source_element(struct notification_thread_state *state, + int tracer_event_source_fd) +{ + struct notification_event_tracer_event_source_element *source_element; + + cds_list_for_each_entry(source_element, + &state->tracer_event_sources_list, node) { + if (source_element->fd == tracer_event_source_fd) { + goto end; + } + } + + source_element = NULL; +end: + return source_element; +} + +static +int remove_tracer_event_source_from_pollset( + struct notification_thread_state *state, + struct notification_event_tracer_event_source_element *source_element) +{ + int ret = 0; + + LTTNG_ASSERT(source_element->is_fd_in_poll_set); + + DBG3("Removing tracer event source from poll set: tracer_event_source_fd = %d, domain = '%s'", + source_element->fd, + lttng_domain_type_str(source_element->domain)); + + /* Removing the fd from the event poll set. */ + ret = lttng_poll_del(&state->events, source_element->fd); + if (ret < 0) { + ERR("Failed to remove tracer event source from poll set: tracer_event_source_fd = %d, domain = '%s'", + source_element->fd, + lttng_domain_type_str(source_element->domain)); + ret = -1; + goto end; + } + + source_element->is_fd_in_poll_set = false; + + /* + * Force the notification thread to restart the poll() loop to ensure + * that any events from the removed fd are removed. + */ + state->restart_poll = true; + + ret = drain_event_notifier_notification_pipe(state, source_element->fd, + source_element->domain); + if (ret) { + ERR("Error draining event notifier notification: tracer_event_source_fd = %d, domain = %s", + source_element->fd, + lttng_domain_type_str(source_element->domain)); + ret = -1; + goto end; + } + +end: + return ret; +} + +int handle_notification_thread_tracer_event_source_died( + struct notification_thread_state *state, + int tracer_event_source_fd) +{ + int ret = 0; + struct notification_event_tracer_event_source_element *source_element; + + source_element = find_tracer_event_source_element(state, + tracer_event_source_fd); + + LTTNG_ASSERT(source_element); + + ret = remove_tracer_event_source_from_pollset(state, source_element); + if (ret) { + ERR("Failed to remove dead tracer event source from poll set"); + } + + return ret; +} + +static +int handle_notification_thread_command_remove_tracer_event_source( + struct notification_thread_state *state, + int tracer_event_source_fd, + enum lttng_error_code *_cmd_result) +{ + int ret = 0; + enum lttng_error_code cmd_result = LTTNG_OK; + struct notification_event_tracer_event_source_element *source_element = NULL; + + source_element = find_tracer_event_source_element(state, + tracer_event_source_fd); + + LTTNG_ASSERT(source_element); + + /* Remove the tracer source from the list. */ + cds_list_del(&source_element->node); + + if (!source_element->is_fd_in_poll_set) { + /* Skip the poll set removal. */ + goto end; + } + + ret = remove_tracer_event_source_from_pollset(state, source_element); + if (ret) { + ERR("Failed to remove tracer event source from poll set"); + cmd_result = LTTNG_ERR_FATAL; + } + +end: + free(source_element); + *_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 client_uid, + struct lttng_triggers **triggers, + enum lttng_error_code *_cmd_result) +{ + int ret = 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; + + rcu_read_lock(); + + local_triggers = lttng_triggers_create(); + if (!local_triggers) { + /* Not a fatal error. */ + cmd_result = LTTNG_ERR_NOMEM; + goto end; + } + + cds_lfht_for_each_entry(state->triggers_ht, &iter, + trigger_ht_element, node) { + /* + * Only return the triggers to which the client has access. + * The root user has visibility over all triggers. + */ + creds = lttng_trigger_get_credentials(trigger_ht_element->trigger); + if (client_uid != lttng_credentials_get_uid(creds) && client_uid != 0) { + continue; + } + + ret = lttng_triggers_add(local_triggers, + trigger_ht_element->trigger); + if (ret < 0) { + /* Not a fatal error. */ + ret = 0; + cmd_result = LTTNG_ERR_NOMEM; + goto end; + } + } + + /* Transferring ownership to the caller. */ + *triggers = local_triggers; + local_triggers = NULL; + +end: + rcu_read_unlock(); + lttng_triggers_destroy(local_triggers); + *_cmd_result = cmd_result; + return ret; +} + +static inline void get_trigger_info_for_log(const struct lttng_trigger *trigger, + const char **trigger_name, + uid_t *trigger_owner_uid) +{ + enum lttng_trigger_status trigger_status; + + trigger_status = lttng_trigger_get_name(trigger, trigger_name); + switch (trigger_status) { + case LTTNG_TRIGGER_STATUS_OK: + break; + case LTTNG_TRIGGER_STATUS_UNSET: + *trigger_name = "(anonymous)"; + break; + default: + abort(); + } + + trigger_status = lttng_trigger_get_owner_uid(trigger, + trigger_owner_uid); + LTTNG_ASSERT(trigger_status == LTTNG_TRIGGER_STATUS_OK); +} + +static int handle_notification_thread_command_get_trigger( + struct notification_thread_state *state, + const struct lttng_trigger *trigger, + struct lttng_trigger **registered_trigger, + enum lttng_error_code *_cmd_result) +{ + int ret = -1; + struct cds_lfht_iter iter; + struct lttng_trigger_ht_element *trigger_ht_element; + enum lttng_error_code cmd_result = LTTNG_ERR_TRIGGER_NOT_FOUND; + const char *trigger_name; + uid_t trigger_owner_uid; + + rcu_read_lock(); + + cds_lfht_for_each_entry( + state->triggers_ht, &iter, trigger_ht_element, node) { + if (lttng_trigger_is_equal( + trigger, trigger_ht_element->trigger)) { + /* Take one reference on the return trigger. */ + *registered_trigger = trigger_ht_element->trigger; + lttng_trigger_get(*registered_trigger); + ret = 0; + cmd_result = LTTNG_OK; + goto end; + } + } + + /* Not a fatal error if the trigger is not found. */ + get_trigger_info_for_log(trigger, &trigger_name, &trigger_owner_uid); + DBG("Failed to retrieve registered version of trigger: trigger name = '%s', trigger owner uid = %d", + trigger_name, (int) trigger_owner_uid); + + ret = 0; + +end: + rcu_read_unlock(); + *_cmd_result = cmd_result; + return ret; +} + +static +bool condition_is_supported(struct lttng_condition *condition) +{ + bool is_supported; + + switch (lttng_condition_get_type(condition)) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + { + int ret; + enum lttng_domain_type domain; + + ret = lttng_condition_buffer_usage_get_domain_type(condition, + &domain); + LTTNG_ASSERT(ret == 0); + + if (domain != LTTNG_DOMAIN_KERNEL) { + is_supported = true; + 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. + * + * Assume unsupported on error. + */ + is_supported = kernel_supports_ring_buffer_snapshot_sample_positions() == 1; + break; + } + case LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES: + { + const struct lttng_event_rule *event_rule; + enum lttng_domain_type domain; + const enum lttng_condition_status status = + lttng_condition_event_rule_matches_get_rule( + condition, &event_rule); + + LTTNG_ASSERT(status == LTTNG_CONDITION_STATUS_OK); + + domain = lttng_event_rule_get_domain_type(event_rule); + if (domain != LTTNG_DOMAIN_KERNEL) { + is_supported = true; + goto end; + } + + /* + * Older kernel tracers can't emit notification. Therefore, we + * reject triggers that require that mechanism to be available + * to be evaluated. + * + * Assume unsupported on error. + */ + is_supported = kernel_supports_event_notifiers() == 1; + break; + } + default: + is_supported = true; + } +end: + return is_supported; +} + +/* 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; + + status = lttng_condition_session_rotation_get_session_name( + condition, &session_name); + if (status != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to bind trigger to session: unable to get 'session_rotation' condition's session name"); + ret = -1; + goto end; + } + break; + } + default: + ret = -1; + goto end; + } + + trigger_list = get_session_trigger_list(state, session_name); + if (!trigger_list) { + DBG("Unable to bind trigger applying to session \"%s\" as it is not yet known to the notification system", + session_name); + goto end; + + } + + DBG("Newly registered trigger bound to session \"%s\"", + session_name); + ret = lttng_session_trigger_list_add(trigger_list, trigger); +end: + return ret; +} + +/* Must be called with RCU read lock held. */ +static +int bind_trigger_to_matching_channels(struct lttng_trigger *trigger, + struct notification_thread_state *state) +{ + int ret = 0; + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + struct channel_info *channel; + + cds_lfht_for_each_entry(state->channels_ht, &iter, channel, + channels_ht_node) { + struct lttng_trigger_list_element *trigger_list_element; + struct lttng_channel_trigger_list *trigger_list; + struct cds_lfht_iter lookup_iter; + + if (!trigger_applies_to_channel(trigger, channel)) { + continue; + } + + cds_lfht_lookup(state->channel_triggers_ht, + hash_channel_key(&channel->key), + match_channel_trigger_list, + &channel->key, + &lookup_iter); + node = cds_lfht_iter_get_node(&lookup_iter); + LTTNG_ASSERT(node); + trigger_list = caa_container_of(node, + struct lttng_channel_trigger_list, + channel_triggers_ht_node); + + trigger_list_element = (lttng_trigger_list_element *) zmalloc(sizeof(*trigger_list_element)); + if (!trigger_list_element) { + ret = -1; + goto end; + } + CDS_INIT_LIST_HEAD(&trigger_list_element->node); + trigger_list_element->trigger = trigger; + cds_list_add(&trigger_list_element->node, &trigger_list->list); + DBG("Newly registered trigger bound to channel \"%s\"", + channel->name); + } +end: + return ret; +} + +static +bool is_trigger_action_notify(const struct lttng_trigger *trigger) +{ + bool is_notify = false; + unsigned int i, count; + enum lttng_action_status action_status; + const struct lttng_action *action = + lttng_trigger_get_const_action(trigger); + enum lttng_action_type action_type; + + LTTNG_ASSERT(action); + action_type = lttng_action_get_type(action); + if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { + is_notify = true; + goto end; + } else if (action_type != LTTNG_ACTION_TYPE_LIST) { + goto end; + } + + action_status = lttng_action_list_get_count(action, &count); + LTTNG_ASSERT(action_status == LTTNG_ACTION_STATUS_OK); + + for (i = 0; i < count; i++) { + const struct lttng_action *inner_action = + lttng_action_list_get_at_index( + action, i); + + action_type = lttng_action_get_type(inner_action); + if (action_type == LTTNG_ACTION_TYPE_NOTIFY) { + is_notify = true; + goto end; + } + } + +end: + return is_notify; +} + +static bool trigger_name_taken(struct notification_thread_state *state, + const struct lttng_trigger *trigger) +{ + struct cds_lfht_iter iter; + + /* + * No duplicata is allowed in the triggers_by_name_uid_ht. + * The match is done against the trigger name and uid. + */ + cds_lfht_lookup(state->triggers_by_name_uid_ht, + hash_trigger_by_name_uid(trigger), + match_trigger_by_name_uid, + trigger, + &iter); + return !!cds_lfht_iter_get_node(&iter); +} + +static +enum lttng_error_code generate_trigger_name( + struct notification_thread_state *state, + struct lttng_trigger *trigger, const char **name) +{ + enum lttng_error_code ret_code = LTTNG_OK; + bool taken = false; + enum lttng_trigger_status status; + + do { + const int ret = lttng_trigger_generate_name(trigger, + state->trigger_id.name_offset++); + if (ret) { + /* The only reason this can fail right now. */ + ret_code = LTTNG_ERR_NOMEM; + break; + } + + status = lttng_trigger_get_name(trigger, name); + LTTNG_ASSERT(status == LTTNG_TRIGGER_STATUS_OK); + + taken = trigger_name_taken(state, trigger); + } while (taken || state->trigger_id.name_offset == UINT64_MAX); + + return ret_code; +} + +static inline +void notif_thread_state_remove_trigger_ht_elem( + struct notification_thread_state *state, + struct lttng_trigger_ht_element *trigger_ht_element) +{ + LTTNG_ASSERT(state); + LTTNG_ASSERT(trigger_ht_element); + + cds_lfht_del(state->triggers_ht, &trigger_ht_element->node); + cds_lfht_del(state->triggers_by_name_uid_ht, &trigger_ht_element->node_by_name_uid); +} + +static +enum lttng_error_code setup_tracer_notifier( + struct notification_thread_state *state, + struct lttng_trigger *trigger) +{ + enum lttng_error_code ret; + enum event_notifier_error_accounting_status error_accounting_status; + struct cds_lfht_node *node; + uint64_t error_counter_index = 0; + struct lttng_condition *condition = lttng_trigger_get_condition(trigger); + struct notification_trigger_tokens_ht_element *trigger_tokens_ht_element = NULL; + + trigger_tokens_ht_element = (notification_trigger_tokens_ht_element *) zmalloc(sizeof(*trigger_tokens_ht_element)); + if (!trigger_tokens_ht_element) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + /* Add trigger token to the trigger_tokens_ht. */ + cds_lfht_node_init(&trigger_tokens_ht_element->node); + trigger_tokens_ht_element->token = LTTNG_OPTIONAL_GET(trigger->tracer_token); + 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) { + ret = LTTNG_ERR_TRIGGER_EXISTS; + goto error_free_ht_element; + } + + error_accounting_status = event_notifier_error_accounting_register_event_notifier( + trigger, &error_counter_index); + if (error_accounting_status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + if (error_accounting_status == EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_NO_INDEX_AVAILABLE) { + DBG("Trigger list error accounting counter full."); + ret = LTTNG_ERR_EVENT_NOTIFIER_ERROR_ACCOUNTING_FULL; + } else { + ERR("Error registering trigger for error accounting"); + ret = LTTNG_ERR_EVENT_NOTIFIER_REGISTRATION; + } + + goto error_remove_ht_element; + } + + lttng_condition_event_rule_matches_set_error_counter_index( + condition, error_counter_index); + + ret = LTTNG_OK; + goto end; + +error_remove_ht_element: + cds_lfht_del(state->trigger_tokens_ht, &trigger_tokens_ht_element->node); +error_free_ht_element: + free(trigger_tokens_ht_element); +end: + return ret; +} + +/* + * FIXME A client's credentials are not checked when registering a 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, + bool is_trigger_anonymous, + enum lttng_error_code *cmd_result) +{ + int ret = 0; + struct lttng_condition *condition; + struct notification_client_list *client_list = NULL; + struct lttng_trigger_ht_element *trigger_ht_element = NULL; + struct cds_lfht_node *node; + const char* trigger_name; + bool free_trigger = true; + struct lttng_evaluation *evaluation = NULL; + struct lttng_credentials object_creds; + uid_t object_uid; + gid_t object_gid; + enum action_executor_status executor_status; + const uint64_t trigger_tracer_token = + state->trigger_id.next_tracer_token++; + + rcu_read_lock(); + + /* Set the trigger's tracer token. */ + lttng_trigger_set_tracer_token(trigger, trigger_tracer_token); + + if (!is_trigger_anonymous) { + if (lttng_trigger_get_name(trigger, &trigger_name) == + LTTNG_TRIGGER_STATUS_UNSET) { + const enum lttng_error_code ret_code = + generate_trigger_name(state, trigger, + &trigger_name); + + if (ret_code != LTTNG_OK) { + /* Fatal error. */ + ret = -1; + *cmd_result = ret_code; + goto error; + } + } else if (trigger_name_taken(state, trigger)) { + /* Not a fatal error. */ + *cmd_result = LTTNG_ERR_TRIGGER_EXISTS; + ret = 0; + goto error; + } + } else { + trigger_name = "(anonymous)"; + } + + condition = lttng_trigger_get_condition(trigger); + LTTNG_ASSERT(condition); + + /* Some conditions require tracers to implement a minimal ABI version. */ + if (!condition_is_supported(condition)) { + *cmd_result = LTTNG_ERR_NOT_SUPPORTED; + goto error; + } + + trigger_ht_element = (lttng_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_uid); + 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_uid_ht, + hash_trigger_by_name_uid(trigger), + match_trigger_by_name_uid, + trigger, + &trigger_ht_element->node_by_name_uid); + if (node != &trigger_ht_element->node_by_name_uid) { + /* Internal error: add to triggers_ht should have failed. */ + ret = -1; + goto error_free_ht_element; + } + + /* From this point consider the trigger registered. */ + lttng_trigger_set_as_registered(trigger); + + /* + * Some triggers might need a tracer notifier depending on its + * condition and actions. + */ + if (lttng_trigger_needs_tracer_notifier(trigger)) { + enum lttng_error_code error_code; + + error_code = setup_tracer_notifier(state, trigger); + if (error_code != LTTNG_OK) { + notif_thread_state_remove_trigger_ht_elem(state, + trigger_ht_element); + if (error_code == LTTNG_ERR_NOMEM) { + ret = -1; + } else { + *cmd_result = error_code; + ret = 0; + } + + goto error_free_ht_element; + } + } + + /* + * The rest only applies to triggers that have a "notify" action. + * It is not skipped as this is the only action type currently + * supported. + */ + if (is_trigger_action_notify(trigger)) { + /* + * Find or create the client list of this condition. It may + * already be present if another trigger is already registered + * with the same condition. + */ + client_list = get_client_list_from_condition(state, condition); + if (!client_list) { + /* + * No client list for this condition yet. We create new + * one and build it up. + */ + client_list = notification_client_list_create(state, condition); + if (!client_list) { + ERR("Error creating notification client list for trigger %s", trigger->name); + goto error_free_ht_element; + } + } + + CDS_INIT_LIST_HEAD(&trigger_ht_element->client_list_trigger_node); + + pthread_mutex_lock(&client_list->lock); + cds_list_add(&trigger_ht_element->client_list_trigger_node, &client_list->triggers_list); + pthread_mutex_unlock(&client_list->lock); + } + + /* + * Ownership of the trigger and of its wrapper was transfered to + * the triggers_ht. Same for token ht element if necessary. + */ + trigger_ht_element = NULL; + free_trigger = false; + + 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_ht_element; + } + break; + case LTTNG_OBJECT_TYPE_CHANNEL: + /* + * Add the trigger to list of triggers bound to the channels + * currently known. + */ + ret = bind_trigger_to_matching_channels(trigger, state); + if (ret) { + goto error_free_ht_element; + } + break; + case LTTNG_OBJECT_TYPE_NONE: + break; + default: + ERR("Unknown object type on which to bind a newly registered trigger was encountered"); + ret = -1; + goto error_free_ht_element; + } + + /* + * The new trigger's condition must be evaluated against the current + * state. + * + * In the case of `notify` action, nothing preventing clients from + * subscribing to a condition before the corresponding trigger is + * registered, we have to evaluate this new condition right away. + * + * At some point, we were waiting for the next "evaluation" (e.g. on + * reception of a channel sample) to evaluate this new condition, but + * that was broken. + * + * The reason it was broken is that waiting for the next sample + * does not allow us to properly handle transitions for edge-triggered + * conditions. + * + * Consider this example: when we handle a new channel sample, we + * evaluate each conditions twice: once with the previous state, and + * again with the newest state. We then use those two results to + * determine whether a state change happened: a condition was false and + * became true. If a state change happened, we have to notify clients. + * + * Now, if a client subscribes to a given notification and registers + * a trigger *after* that subscription, we have to make sure the + * condition is evaluated at this point while considering only the + * 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. + */ + switch (get_condition_binding_object(condition)) { + case LTTNG_OBJECT_TYPE_SESSION: + ret = evaluate_session_condition_for_client(condition, state, + &evaluation, &object_uid, + &object_gid); + LTTNG_OPTIONAL_SET(&object_creds.uid, object_uid); + LTTNG_OPTIONAL_SET(&object_creds.gid, object_gid); + break; + case LTTNG_OBJECT_TYPE_CHANNEL: + ret = evaluate_channel_condition_for_client(condition, state, + &evaluation, &object_uid, + &object_gid); + LTTNG_OPTIONAL_SET(&object_creds.uid, object_uid); + LTTNG_OPTIONAL_SET(&object_creds.gid, object_gid); + break; + case LTTNG_OBJECT_TYPE_NONE: + ret = 0; + break; + case LTTNG_OBJECT_TYPE_UNKNOWN: + default: + ret = -1; + break; + } + + if (ret) { + /* Fatal error. */ + goto error_free_ht_element; + } + + DBG("Newly registered trigger's condition evaluated to %s", + evaluation ? "true" : "false"); + if (!evaluation) { + /* Evaluation yielded nothing. Normal exit. */ + ret = 0; + goto success; + } + + /* + * Ownership of `evaluation` transferred to the action executor + * no matter the result. + */ + executor_status = action_executor_enqueue_trigger(state->executor, + trigger, evaluation, &object_creds, client_list); + evaluation = NULL; + switch (executor_status) { + case ACTION_EXECUTOR_STATUS_OK: + break; + case ACTION_EXECUTOR_STATUS_ERROR: + case ACTION_EXECUTOR_STATUS_INVALID: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + */ + ERR("Fatal error occurred while enqueuing action associated to newly registered trigger"); + ret = -1; + goto error_free_ht_element; + case ACTION_EXECUTOR_STATUS_OVERFLOW: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + * + * Not a fatal error. + */ + WARN("No space left when enqueuing action associated to newly registered trigger"); + ret = 0; + goto success; + default: + abort(); + } + +success: + *cmd_result = LTTNG_OK; + DBG("Registered trigger: name = `%s`, tracer token = %" PRIu64, + trigger_name, trigger_tracer_token); + goto end; + +error_free_ht_element: + if (trigger_ht_element) { + /* Delayed removal due to RCU constraint on delete. */ + call_rcu(&trigger_ht_element->rcu_node, + free_lttng_trigger_ht_element_rcu); + } +error: + if (free_trigger) { + /* + * Other objects might have a reference to the trigger, mark it + * as unregistered. + */ + lttng_trigger_set_as_unregistered(trigger); + lttng_trigger_destroy(trigger); + } +end: + rcu_read_unlock(); + return ret; +} + +static +void free_lttng_trigger_ht_element_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct lttng_trigger_ht_element, + rcu_node)); +} + +static +void free_notification_trigger_tokens_ht_element_rcu(struct rcu_head *node) +{ + free(caa_container_of(node, struct notification_trigger_tokens_ht_element, + rcu_node)); +} + +static +void teardown_tracer_notifier(struct notification_thread_state *state, + const struct lttng_trigger *trigger) +{ + struct cds_lfht_iter iter; + 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; + } + + event_notifier_error_accounting_unregister_event_notifier( + trigger_tokens_ht_element->trigger); + + /* TODO talk to all app and remove it */ + DBG("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; + } +} + +static +int handle_notification_thread_command_unregister_trigger( + struct notification_thread_state *state, + const struct lttng_trigger *trigger, + enum lttng_error_code *_cmd_reply) +{ + struct cds_lfht_iter iter; + struct cds_lfht_node *triggers_ht_node; + struct lttng_channel_trigger_list *trigger_list; + struct notification_client_list *client_list; + struct lttng_trigger_ht_element *trigger_ht_element = NULL; + const struct lttng_condition *condition = lttng_trigger_get_const_condition( + trigger); + enum lttng_error_code cmd_reply; + + rcu_read_lock(); + + cds_lfht_lookup(state->triggers_ht, + lttng_condition_hash(condition), + match_trigger, + trigger, + &iter); + triggers_ht_node = cds_lfht_iter_get_node(&iter); + if (!triggers_ht_node) { + cmd_reply = LTTNG_ERR_TRIGGER_NOT_FOUND; + goto end; + } else { + cmd_reply = LTTNG_OK; + } + + trigger_ht_element = caa_container_of(triggers_ht_node, + struct lttng_trigger_ht_element, node); + + /* Remove trigger from channel_triggers_ht. */ + cds_lfht_for_each_entry(state->channel_triggers_ht, &iter, trigger_list, + channel_triggers_ht_node) { + struct lttng_trigger_list_element *trigger_element, *tmp; + + cds_list_for_each_entry_safe(trigger_element, tmp, + &trigger_list->list, node) { + if (!lttng_trigger_is_equal(trigger, trigger_element->trigger)) { + continue; + } + + DBG("Removed trigger from channel_triggers_ht"); + cds_list_del(&trigger_element->node); + /* A trigger can only appear once per channel */ + break; + } + } + + if (lttng_trigger_needs_tracer_notifier(trigger)) { + teardown_tracer_notifier(state, trigger); + } + + if (is_trigger_action_notify(trigger)) { + /* + * Remove and release the client list from + * notification_trigger_clients_ht. + */ + client_list = get_client_list_from_condition(state, condition); + LTTNG_ASSERT(client_list); + + pthread_mutex_lock(&client_list->lock); + cds_list_del(&trigger_ht_element->client_list_trigger_node); + pthread_mutex_unlock(&client_list->lock); + + /* Put new reference and the hashtable's reference. */ + notification_client_list_put(client_list); + notification_client_list_put(client_list); + client_list = NULL; + } + + /* Remove trigger from triggers_ht. */ + notif_thread_state_remove_trigger_ht_elem(state, trigger_ht_element); + + /* 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: + rcu_read_unlock(); + if (_cmd_reply) { + *_cmd_reply = cmd_reply; + } + return 0; +} + +/* Returns 0 on success, 1 on exit requested, negative value on error. */ +int handle_notification_thread_command( + struct notification_thread_handle *handle, + struct notification_thread_state *state) +{ + int ret; + uint64_t counter; + struct notification_thread_command *cmd; + + /* Read the event pipe to put it back into a quiescent state. */ + ret = lttng_read(lttng_pipe_get_readfd(handle->cmd_queue.event_pipe), &counter, + sizeof(counter)); + if (ret != sizeof(counter)) { + goto error; + } + + pthread_mutex_lock(&handle->cmd_queue.lock); + cmd = cds_list_first_entry(&handle->cmd_queue.list, + struct notification_thread_command, cmd_list_node); + cds_list_del(&cmd->cmd_list_node); + pthread_mutex_unlock(&handle->cmd_queue.lock); + + DBG("Received `%s` command", + notification_command_type_str(cmd->type)); + switch (cmd->type) { + case NOTIFICATION_COMMAND_TYPE_REGISTER_TRIGGER: + ret = handle_notification_thread_command_register_trigger(state, + cmd->parameters.register_trigger.trigger, + cmd->parameters.register_trigger.is_trigger_anonymous, + &cmd->reply_code); + break; + case NOTIFICATION_COMMAND_TYPE_UNREGISTER_TRIGGER: + ret = handle_notification_thread_command_unregister_trigger( + state, + cmd->parameters.unregister_trigger.trigger, + &cmd->reply_code); + break; + case NOTIFICATION_COMMAND_TYPE_ADD_CHANNEL: + ret = handle_notification_thread_command_add_channel( + state, + cmd->parameters.add_channel.session.name, + cmd->parameters.add_channel.session.uid, + cmd->parameters.add_channel.session.gid, + cmd->parameters.add_channel.channel.name, + cmd->parameters.add_channel.channel.domain, + cmd->parameters.add_channel.channel.key, + cmd->parameters.add_channel.channel.capacity, + &cmd->reply_code); + break; + case NOTIFICATION_COMMAND_TYPE_REMOVE_CHANNEL: + ret = handle_notification_thread_command_remove_channel( + state, cmd->parameters.remove_channel.key, + cmd->parameters.remove_channel.domain, + &cmd->reply_code); + break; + case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_ONGOING: + case NOTIFICATION_COMMAND_TYPE_SESSION_ROTATION_COMPLETED: + ret = handle_notification_thread_command_session_rotation( + state, + cmd->type, + cmd->parameters.session_rotation.session_name, + cmd->parameters.session_rotation.uid, + cmd->parameters.session_rotation.gid, + cmd->parameters.session_rotation.trace_archive_chunk_id, + cmd->parameters.session_rotation.location, + &cmd->reply_code); + break; + case NOTIFICATION_COMMAND_TYPE_ADD_TRACER_EVENT_SOURCE: + ret = handle_notification_thread_command_add_tracer_event_source( + state, + cmd->parameters.tracer_event_source.tracer_event_source_fd, + cmd->parameters.tracer_event_source.domain, + &cmd->reply_code); + break; + case NOTIFICATION_COMMAND_TYPE_REMOVE_TRACER_EVENT_SOURCE: + ret = handle_notification_thread_command_remove_tracer_event_source( + state, + cmd->parameters.tracer_event_source.tracer_event_source_fd, + &cmd->reply_code); + 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, + &triggers, + &cmd->reply_code); + cmd->reply.list_triggers.triggers = triggers; + ret = 0; + break; + } + case NOTIFICATION_COMMAND_TYPE_QUIT: + cmd->reply_code = LTTNG_OK; + ret = 1; + goto end; + case NOTIFICATION_COMMAND_TYPE_GET_TRIGGER: + { + struct lttng_trigger *trigger = NULL; + + ret = handle_notification_thread_command_get_trigger(state, + cmd->parameters.get_trigger.trigger, &trigger, + &cmd->reply_code); + cmd->reply.get_trigger.trigger = trigger; + break; + } + 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 { + ret = client_handle_transmission_status( + client, client_status, state); + } + rcu_read_unlock(); + break; + } + default: + ERR("Unknown internal command received"); + goto error_unlock; + } + + if (ret) { + goto error_unlock; + } +end: + if (cmd->is_async) { + free(cmd); + cmd = NULL; + } else { + lttng_waiter_wake_up(&cmd->reply_waiter); + } + return ret; +error_unlock: + /* Wake-up and return a fatal error to the calling thread. */ + lttng_waiter_wake_up(&cmd->reply_waiter); + cmd->reply_code = LTTNG_ERR_FATAL; +error: + /* Indicate a fatal error to the caller. */ + return -1; +} + +static +int socket_set_non_blocking(int socket) +{ + int ret, flags; + + /* Set the pipe as non-blocking. */ + ret = fcntl(socket, F_GETFL, 0); + if (ret == -1) { + PERROR("fcntl get socket flags"); + goto end; + } + flags = ret; + + ret = fcntl(socket, F_SETFL, flags | O_NONBLOCK); + if (ret == -1) { + PERROR("fcntl set O_NONBLOCK socket flag"); + goto end; + } + DBG("Client socket (fd = %i) set as non-blocking", socket); +end: + return ret; +} + +static +int client_reset_inbound_state(struct notification_client *client) +{ + int ret; + + + lttng_payload_clear(&client->communication.inbound.payload); + + client->communication.inbound.bytes_to_receive = + sizeof(struct lttng_notification_channel_message); + client->communication.inbound.msg_type = + LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNKNOWN; + LTTNG_SOCK_SET_UID_CRED(&client->communication.inbound.creds, -1); + LTTNG_SOCK_SET_GID_CRED(&client->communication.inbound.creds, -1); + ret = lttng_dynamic_buffer_set_size( + &client->communication.inbound.payload.buffer, + client->communication.inbound.bytes_to_receive); + + return ret; +} + +int handle_notification_thread_client_connect( + struct notification_thread_state *state) +{ + int ret; + struct notification_client *client; + + DBG("Handling new notification channel client connection"); + + client = (notification_client *) zmalloc(sizeof(*client)); + if (!client) { + /* Fatal error. */ + 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_payload_init(&client->communication.inbound.payload); + lttng_payload_init(&client->communication.outbound.payload); + client->communication.inbound.expect_creds = true; + + ret = client_reset_inbound_state(client); + if (ret) { + ERR("Failed to reset client communication's inbound state"); + ret = 0; + goto error; + } + + ret = lttcomm_accept_unix_sock(state->notification_channel_socket); + if (ret < 0) { + ERR("Failed to accept new notification channel client connection"); + ret = 0; + goto error; + } + + client->socket = ret; + + ret = socket_set_non_blocking(client->socket); + if (ret) { + ERR("Failed to set new notification channel client connection socket as non-blocking"); + goto error; + } + + ret = lttcomm_setsockopt_creds_unix_sock(client->socket); + if (ret < 0) { + ERR("Failed to set socket options on new notification channel client socket"); + ret = 0; + goto error; + } + + ret = lttng_poll_add(&state->events, client->socket, + LPOLLIN | LPOLLERR | + LPOLLHUP | LPOLLRDHUP); + if (ret < 0) { + ERR("Failed to add notification channel client socket to poll set"); + ret = 0; + goto error; + } + DBG("Added new notification channel client socket (%i) to poll set", + client->socket); + + rcu_read_lock(); + 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; + +error: + notification_client_destroy(client, state); + return ret; +} + +/* + * RCU read-lock must be held by the caller. + * Client lock must _not_ 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. */ + pthread_mutex_lock(&client->lock); + client->communication.active = false; + cds_lfht_del(state->client_socket_ht, &client->client_socket_ht_node); + cds_lfht_del(state->client_id_ht, &client->client_id_ht_node); + pthread_mutex_unlock(&client->lock); + + ret = lttng_poll_del(&state->events, client->socket); + if (ret) { + ERR("Failed to remove client socket %d from poll set", + client->socket); + } + + /* 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; + + rcu_read_lock(); + DBG("Closing client connection (socket fd = %i)", + client_socket); + client = get_client_from_socket(client_socket, state); + if (!client) { + /* Internal state corruption, fatal error. */ + ERR("Unable to find client (socket fd = %i)", + client_socket); + ret = -1; + goto end; + } + + ret = notification_thread_client_disconnect(client, state); +end: + rcu_read_unlock(); + return ret; +} + +int handle_notification_thread_client_disconnect_all( + struct notification_thread_state *state) +{ + struct cds_lfht_iter iter; + struct notification_client *client; + bool error_encoutered = false; + + rcu_read_lock(); + DBG("Closing all client connections"); + cds_lfht_for_each_entry(state->client_socket_ht, &iter, client, + client_socket_ht_node) { + int ret; + + ret = notification_thread_client_disconnect( + client, state); + if (ret) { + error_encoutered = true; + } + } + rcu_read_unlock(); + return error_encoutered ? 1 : 0; +} + +int handle_notification_thread_trigger_unregister_all( + struct notification_thread_state *state) +{ + bool error_occurred = false; + struct cds_lfht_iter iter; + struct lttng_trigger_ht_element *trigger_ht_element; + + rcu_read_lock(); + cds_lfht_for_each_entry(state->triggers_ht, &iter, trigger_ht_element, + node) { + int ret = handle_notification_thread_command_unregister_trigger( + state, trigger_ht_element->trigger, NULL); + if (ret) { + error_occurred = true; + } + } + rcu_read_unlock(); + return error_occurred ? -1 : 0; +} + +static +int client_handle_transmission_status( + struct notification_client *client, + enum client_transmission_status transmission_status, + struct notification_thread_state *state) +{ + int ret = 0; + + switch (transmission_status) { + case CLIENT_TRANSMISSION_STATUS_COMPLETE: + ret = lttng_poll_mod(&state->events, client->socket, + CLIENT_POLL_MASK_IN); + if (ret) { + goto end; + } + + 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; + struct lttng_payload_view pv = lttng_payload_view_from_payload( + &client->communication.outbound.payload, 0, -1); + const int fds_to_send_count = + lttng_payload_view_get_fd_handle_count(&pv); + + ASSERT_LOCKED(client->lock); + + if (!client->communication.active) { + status = CLIENT_TRANSMISSION_STATUS_FAIL; + goto end; + } + + if (pv.buffer.size == 0) { + /* + * If both data and fds are equal to zero, we are in an invalid + * state. + */ + LTTNG_ASSERT(fds_to_send_count != 0); + goto send_fds; + } + + /* Send data. */ + to_send_count = pv.buffer.size; + DBG("Flushing client (socket fd = %i) outgoing queue", + client->socket); + + ret = lttcomm_send_unix_sock_non_block(client->socket, + pv.buffer.data, + to_send_count); + if ((ret >= 0 && ret < to_send_count)) { + DBG("Client (socket fd = %i) outgoing queue could not be completely flushed", + client->socket); + to_send_count -= std::max(ret, (ssize_t) 0); + + memmove(client->communication.outbound.payload.buffer.data, + pv.buffer.data + + pv.buffer.size - to_send_count, + to_send_count); + ret = lttng_dynamic_buffer_set_size( + &client->communication.outbound.payload.buffer, + to_send_count); + if (ret) { + goto error; + } + + status = CLIENT_TRANSMISSION_STATUS_QUEUED; + goto end; + } else if (ret < 0) { + /* Generic error, disable the client's communication. */ + ERR("Failed to flush outgoing queue, disconnecting client (socket fd = %i)", + client->socket); + client->communication.active = false; + status = CLIENT_TRANSMISSION_STATUS_FAIL; + goto end; + } else { + /* + * No error and flushed the queue completely. + * + * The payload buffer size is used later to + * check if there is notifications queued. So albeit that the + * direct caller knows that the transmission is complete, we + * need to set the buffer size to zero. + */ + ret = lttng_dynamic_buffer_set_size( + &client->communication.outbound.payload.buffer, 0); + if (ret) { + goto error; + } + } + +send_fds: + /* No fds to send, transmission is complete. */ + if (fds_to_send_count == 0) { + status = CLIENT_TRANSMISSION_STATUS_COMPLETE; + goto end; + } + + ret = lttcomm_send_payload_view_fds_unix_sock_non_block( + client->socket, &pv); + if (ret < 0) { + /* Generic error, disable the client's communication. */ + ERR("Failed to flush outgoing fds queue, disconnecting client (socket fd = %i)", + client->socket); + client->communication.active = false; + status = CLIENT_TRANSMISSION_STATUS_FAIL; + goto end; + } else if (ret == 0) { + /* Nothing could be sent. */ + status = CLIENT_TRANSMISSION_STATUS_QUEUED; + } else { + /* Fd passing is an all or nothing kind of thing. */ + status = CLIENT_TRANSMISSION_STATUS_COMPLETE; + /* + * The payload _fd_array count is used later to + * check if there is notifications queued. So although the + * direct caller knows that the transmission is complete, we + * need to clear the _fd_array for the queuing check. + */ + lttng_dynamic_pointer_array_clear( + &client->communication.outbound.payload + ._fd_handles); + } + +end: + if (status == CLIENT_TRANSMISSION_STATUS_COMPLETE) { + client->communication.outbound.queued_command_reply = false; + client->communication.outbound.dropped_notification = false; + lttng_payload_clear(&client->communication.outbound.payload); + } + + return status; +error: + return CLIENT_TRANSMISSION_STATUS_ERROR; +} + +static +bool client_has_outbound_data_left( + const struct notification_client *client) +{ + const struct lttng_payload_view pv = lttng_payload_view_from_payload( + &client->communication.outbound.payload, 0, -1); + const bool has_data = pv.buffer.size != 0; + const bool has_fds = lttng_payload_view_get_fd_handle_count(&pv); + + return has_data || has_fds; +} + +/* Client lock must _not_ be held by the 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; + + memcpy(buffer, &msg, sizeof(msg)); + memcpy(buffer + sizeof(msg), &reply, sizeof(reply)); + DBG("Send command reply (%i)", (int) status); + + pthread_mutex_lock(&client->lock); + if (client->communication.outbound.queued_command_reply) { + /* Protocol error. */ + goto error_unlock; + } + + /* Enqueue buffer to outgoing queue and flush it. */ + ret = lttng_dynamic_buffer_append( + &client->communication.outbound.payload.buffer, + buffer, sizeof(buffer)); + if (ret) { + goto error_unlock; + } + + transmission_status = client_flush_outgoing_queue(client); + + if (client_has_outbound_data_left(client)) { + /* Queue could not be emptied. */ + client->communication.outbound.queued_command_reply = true; + } + + pthread_mutex_unlock(&client->lock); + ret = client_handle_transmission_status( + client, transmission_status, state); + if (ret) { + goto error; + } + + return 0; +error_unlock: + pthread_mutex_unlock(&client->lock); +error: + return -1; +} + +static +int client_handle_message_unknown(struct notification_client *client, + struct notification_thread_state *state) +{ + int ret; + /* + * 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; + + LTTNG_ASSERT(sizeof(*msg) == client->communication.inbound.payload.buffer.size); + msg = (const struct lttng_notification_channel_message *) + client->communication.inbound.payload.buffer.data; + + if (msg->size == 0 || + msg->size > DEFAULT_MAX_NOTIFICATION_CLIENT_MESSAGE_PAYLOAD_SIZE) { + ERR("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("Invalid notification channel message: unexpected message type"); + goto end; + } + + client->communication.inbound.bytes_to_receive = msg->size; + client->communication.inbound.fds_to_receive = msg->fds; + client->communication.inbound.msg_type = + (enum lttng_notification_channel_message_type) msg->type; + ret = lttng_dynamic_buffer_set_size( + &client->communication.inbound.payload.buffer, msg->size); + + /* msg is not valid anymore due to lttng_dynamic_buffer_set_size. */ + msg = NULL; +end: + 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)]; + + 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.payload.buffer + .data; + client->major = handshake_client->major; + client->minor = handshake_client->minor; + if (!client->communication.inbound.creds_received) { + ERR("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); + client->is_sessiond = LTTNG_SOCK_GET_PID_CRED(&client->communication.inbound.creds) == getpid(); + DBG("Received handshake from client: uid = %u, gid = %u, protocol version = %i.%i, client is sessiond = %s", + client->uid, client->gid, (int) client->major, + (int) client->minor, + client->is_sessiond ? "true" : "false"); + + if (handshake_client->major != + LTTNG_NOTIFICATION_CHANNEL_VERSION_MAJOR) { + status = LTTNG_NOTIFICATION_CHANNEL_STATUS_UNSUPPORTED_VERSION; + } + + pthread_mutex_lock(&client->lock); + /* Outgoing queue will be flushed when the command reply is sent. */ + ret = lttng_dynamic_buffer_append( + &client->communication.outbound.payload.buffer, send_buffer, + sizeof(send_buffer)); + if (ret) { + ERR("Failed to send protocol version to notification channel client"); + goto end_unlock; + } + + client->validated = true; + client->communication.active = true; + pthread_mutex_unlock(&client->lock); + + /* Set reception state to receive the next message header. */ + ret = client_reset_inbound_state(client); + if (ret) { + ERR("Failed to reset client communication's inbound state"); + goto end; + } + + /* Flushes the outgoing queue. */ + ret = client_send_command_reply(client, state, status); + if (ret) { + ERR("Failed to send reply to notification channel client"); + goto end; + } + + goto end; +end_unlock: + pthread_mutex_unlock(&client->lock); +end: + return ret; +} + +static +int client_handle_message_subscription( + struct notification_client *client, + enum lttng_notification_channel_message_type msg_type, + struct notification_thread_state *state) +{ + int ret; + struct lttng_condition *condition; + enum lttng_notification_channel_status status = + LTTNG_NOTIFICATION_CHANNEL_STATUS_OK; + struct lttng_payload_view condition_view = + lttng_payload_view_from_payload( + &client->communication.inbound.payload, + 0, -1); + size_t expected_condition_size; + + /* + * No need to lock client to sample the inbound state as the only + * other thread accessing clients (action executor) only uses the + * outbound state. + */ + expected_condition_size = client->communication.inbound.payload.buffer.size; + ret = lttng_condition_create_from_payload(&condition_view, &condition); + if (ret != expected_condition_size) { + ERR("Malformed condition received from client"); + goto end; + } + + /* Ownership of condition is always transferred. */ + 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 end; + } + + /* Set reception state to receive the next message header. */ + ret = client_reset_inbound_state(client); + if (ret) { + ERR("Failed to reset client communication's inbound state"); + goto end; + } + + ret = client_send_command_reply(client, state, status); + if (ret) { + ERR("Failed to send reply to notification channel client"); + goto end; + } + +end: + return ret; +} + +static +int client_dispatch_message(struct notification_client *client, + struct notification_thread_state *state) +{ + int ret = 0; + + if (client->communication.inbound.msg_type != + LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE && + client->communication.inbound.msg_type != + LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNKNOWN && + !client->validated) { + WARN("client attempted a command before handshake"); + ret = -1; + goto end; + } + + switch (client->communication.inbound.msg_type) { + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNKNOWN: + { + ret = client_handle_message_unknown(client, state); + break; + } + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_HANDSHAKE: + { + ret = client_handle_message_handshake(client, state); + break; + } + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_SUBSCRIBE: + case LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_UNSUBSCRIBE: + { + ret = client_handle_message_subscription(client, + client->communication.inbound.msg_type, state); + break; + } + default: + abort(); + } +end: + return ret; +} + +/* Incoming data from client. */ +int handle_notification_thread_client_in( + struct notification_thread_state *state, int socket) +{ + int ret = 0; + struct notification_client *client; + ssize_t recv_ret; + size_t offset; + + rcu_read_lock(); + client = get_client_from_socket(socket, state); + if (!client) { + /* Internal error, abort. */ + ret = -1; + goto end; + } + + if (client->communication.inbound.bytes_to_receive == 0 && + client->communication.inbound.fds_to_receive != 0) { + /* Only FDs left to receive. */ + goto receive_fds; + } + + offset = client->communication.inbound.payload.buffer.size - + client->communication.inbound.bytes_to_receive; + if (client->communication.inbound.expect_creds) { + recv_ret = lttcomm_recv_creds_unix_sock(socket, + client->communication.inbound.payload.buffer.data + offset, + client->communication.inbound.bytes_to_receive, + &client->communication.inbound.creds); + if (recv_ret > 0) { + client->communication.inbound.expect_creds = false; + client->communication.inbound.creds_received = true; + } + } else { + recv_ret = lttcomm_recv_unix_sock_non_block(socket, + client->communication.inbound.payload.buffer.data + offset, + client->communication.inbound.bytes_to_receive); + } + if (recv_ret >= 0) { + client->communication.inbound.bytes_to_receive -= recv_ret; + } else { + goto error_disconnect_client; + } + + if (client->communication.inbound.bytes_to_receive != 0) { + /* Message incomplete wait for more data. */ + ret = 0; + goto end; + } + +receive_fds: + LTTNG_ASSERT(client->communication.inbound.bytes_to_receive == 0); + + /* Receive fds. */ + if (client->communication.inbound.fds_to_receive != 0) { + ret = lttcomm_recv_payload_fds_unix_sock_non_block( + client->socket, + client->communication.inbound.fds_to_receive, + &client->communication.inbound.payload); + if (ret > 0) { + /* + * Fds received. non blocking fds passing is all + * or nothing. + */ + ssize_t expected_size; + + expected_size = sizeof(int) * + client->communication.inbound + .fds_to_receive; + LTTNG_ASSERT(ret == expected_size); + client->communication.inbound.fds_to_receive = 0; + } else if (ret == 0) { + /* Received nothing. */ + ret = 0; + goto end; + } else { + goto error_disconnect_client; + } + } + + /* At this point the message is complete.*/ + LTTNG_ASSERT(client->communication.inbound.bytes_to_receive == 0 && + client->communication.inbound.fds_to_receive == 0); + ret = client_dispatch_message(client, state); + if (ret) { + /* + * Only returns an error if this client must be + * disconnected. + */ + goto error_disconnect_client; + } + +end: + rcu_read_unlock(); + return ret; + +error_disconnect_client: + ret = notification_thread_client_disconnect(client, state); + goto end; +} + +/* Client ready to receive outgoing data. */ +int handle_notification_thread_client_out( + struct notification_thread_state *state, int socket) +{ + int ret; + struct notification_client *client; + enum client_transmission_status transmission_status; + + rcu_read_lock(); + client = get_client_from_socket(socket, state); + if (!client) { + /* Internal error, abort. */ + ret = -1; + goto end; + } + + pthread_mutex_lock(&client->lock); + transmission_status = client_flush_outgoing_queue(client); + pthread_mutex_unlock(&client->lock); + + ret = client_handle_transmission_status( + client, transmission_status, state); + if (ret) { + goto end; + } +end: + rcu_read_unlock(); + return ret; +} + +static +bool evaluate_buffer_usage_condition(const struct lttng_condition *condition, + const struct channel_state_sample *sample, + uint64_t buffer_capacity) +{ + bool result = false; + uint64_t threshold; + enum lttng_condition_type condition_type; + const struct lttng_condition_buffer_usage *use_condition = container_of( + condition, struct lttng_condition_buffer_usage, + parent); + + if (use_condition->threshold_bytes.set) { + threshold = use_condition->threshold_bytes.value; + } else { + /* + * Threshold was expressed as a ratio. + * + * TODO the threshold (in bytes) of conditions expressed + * as a ratio of total buffer size could be cached to + * forego this double-multiplication or it could be performed + * as fixed-point math. + * + * Note that caching should accommodates the case where the + * condition applies to multiple channels (i.e. don't assume + * that all channels matching my_chann* have the same size...) + */ + threshold = (uint64_t) (use_condition->threshold_ratio.value * + (double) buffer_capacity); + } + + condition_type = lttng_condition_get_type(condition); + if (condition_type == LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW) { + DBG("Low buffer usage condition being evaluated: threshold = %" PRIu64 ", highest usage = %" PRIu64, + threshold, sample->highest_usage); + + /* + * The low condition should only be triggered once _all_ of the + * streams in a channel have gone below the "low" threshold. + */ + if (sample->highest_usage <= threshold) { + result = true; + } + } else { + DBG("High buffer usage condition being evaluated: threshold = %" PRIu64 ", highest usage = %" PRIu64, + threshold, sample->highest_usage); + + /* + * For high buffer usage scenarios, we want to trigger whenever + * _any_ of the streams has reached the "high" threshold. + */ + if (sample->highest_usage >= threshold) { + result = true; + } + } + + return result; +} + +static +bool evaluate_session_consumed_size_condition( + const struct lttng_condition *condition, + uint64_t session_consumed_size) +{ + uint64_t threshold; + const struct lttng_condition_session_consumed_size *size_condition = + container_of(condition, + struct lttng_condition_session_consumed_size, + parent); + + threshold = size_condition->consumed_threshold_bytes.value; + DBG("Session consumed size condition being evaluated: threshold = %" PRIu64 ", current size = %" PRIu64, + threshold, session_consumed_size); + return session_consumed_size >= threshold; +} + +static +int evaluate_buffer_condition(const struct lttng_condition *condition, + struct lttng_evaluation **evaluation, + const struct notification_thread_state *state, + const struct channel_state_sample *previous_sample, + const struct channel_state_sample *latest_sample, + uint64_t previous_session_consumed_total, + uint64_t latest_session_consumed_total, + struct channel_info *channel_info) +{ + int ret = 0; + enum lttng_condition_type condition_type; + const bool previous_sample_available = !!previous_sample; + bool previous_sample_result = false; + bool latest_sample_result; + + condition_type = lttng_condition_get_type(condition); + + switch (condition_type) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + if (caa_likely(previous_sample_available)) { + previous_sample_result = + evaluate_buffer_usage_condition(condition, + previous_sample, channel_info->capacity); + } + latest_sample_result = evaluate_buffer_usage_condition( + condition, latest_sample, + channel_info->capacity); + break; + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + if (caa_likely(previous_sample_available)) { + previous_sample_result = + evaluate_session_consumed_size_condition( + condition, + previous_session_consumed_total); + } + latest_sample_result = + evaluate_session_consumed_size_condition( + condition, + latest_session_consumed_total); + break; + default: + /* Unknown condition type; internal error. */ + abort(); + } + + if (!latest_sample_result || + (previous_sample_result == latest_sample_result)) { + /* + * Only trigger on a condition evaluation transition. + * + * NOTE: This edge-triggered logic may not be appropriate for + * future condition types. + */ + goto end; + } + + if (!evaluation || !latest_sample_result) { + goto end; + } + + switch (condition_type) { + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_LOW: + case LTTNG_CONDITION_TYPE_BUFFER_USAGE_HIGH: + *evaluation = lttng_evaluation_buffer_usage_create( + condition_type, + latest_sample->highest_usage, + channel_info->capacity); + break; + case LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE: + *evaluation = lttng_evaluation_session_consumed_size_create( + latest_session_consumed_total); + break; + default: + abort(); + } + + if (!*evaluation) { + ret = -1; + goto end; + } +end: + return ret; +} + +static +int client_notification_overflow(struct notification_client *client) +{ + int ret = 0; + const struct lttng_notification_channel_message msg = { + .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION_DROPPED, + }; + + 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.payload.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 object_uid, gid_t object_gid) +{ + const struct lttng_credentials creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(object_uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(object_gid), + }; + + return notification_client_list_send_evaluation(client_list, + trigger, evaluation, + &creds, + client_handle_transmission_status_wrapper, state); +} + +/* + * Permission checks relative to notification channel clients are performed + * here. Notice how object, client, and trigger credentials are involved in + * this check. + * + * The `object` credentials are the credentials associated with the "subject" + * of a condition. For instance, a `rotation completed` condition applies + * to a session. When that condition is met, it will produce an evaluation + * against a session. Hence, in this case, the `object` credentials are the + * credentials of the "subject" session. + * + * The `trigger` credentials are the credentials of the user that registered the + * trigger. + * + * The `client` credentials are the credentials of the user that created a given + * notification channel. + * + * In terms of visibility, it is expected that non-privilieged users can only + * register triggers against "their" objects (their own sessions and + * applications they are allowed to interact with). They can then open a + * notification channel and subscribe to notifications associated with those + * triggers. + * + * As for privilieged users, they can register triggers against the objects of + * other users. They can then subscribe to the notifications associated to their + * triggers. Privilieged users _can't_ subscribe to the notifications of + * triggers owned by other users; they must create their own triggers. + * + * This is more a concern of usability than security. It would be difficult for + * a root user reliably subscribe to a specific set of conditions without + * interference from external users (those could, for instance, unregister + * their triggers). + */ +int notification_client_list_send_evaluation( + struct notification_client_list *client_list, + const struct lttng_trigger *trigger, + const struct lttng_evaluation *evaluation, + const struct lttng_credentials *source_object_creds, + report_client_transmission_result_cb client_report, + void *user_data) +{ + int ret = 0; + struct lttng_payload msg_payload; + struct notification_client_list_element *client_list_element, *tmp; + const struct lttng_notification notification = { + .trigger = (struct lttng_trigger *) trigger, + .evaluation = (struct lttng_evaluation *) evaluation, + }; + struct lttng_notification_channel_message msg_header = { + .type = (int8_t) LTTNG_NOTIFICATION_CHANNEL_MESSAGE_TYPE_NOTIFICATION, + }; + const struct lttng_credentials *trigger_creds = + lttng_trigger_get_credentials(trigger); + + lttng_payload_init(&msg_payload); + + ret = lttng_dynamic_buffer_append(&msg_payload.buffer, &msg_header, + sizeof(msg_header)); + if (ret) { + goto end; + } + + ret = lttng_notification_serialize(¬ification, &msg_payload); + if (ret) { + ERR("Failed to serialize notification"); + ret = -1; + goto end; + } + + /* Update payload size. */ + ((struct lttng_notification_channel_message *) msg_payload.buffer.data) + ->size = (uint32_t)( + msg_payload.buffer.size - sizeof(msg_header)); + + /* Update the payload number of fds. */ + { + const struct lttng_payload_view pv = lttng_payload_view_from_payload( + &msg_payload, 0, -1); + + ((struct lttng_notification_channel_message *) + msg_payload.buffer.data)->fds = (uint32_t) + lttng_payload_view_get_fd_handle_count(&pv); + } + + pthread_mutex_lock(&client_list->lock); + cds_list_for_each_entry_safe(client_list_element, tmp, + &client_list->clients_list, node) { + enum client_transmission_status transmission_status; + struct notification_client *client = + client_list_element->client; + + ret = 0; + pthread_mutex_lock(&client->lock); + if (!client->communication.active) { + /* + * Skip inactive client (protocol error or + * disconnecting). + */ + DBG("Skipping client at it is marked as inactive"); + goto skip_client; + } + + if (lttng_trigger_is_hidden(trigger) && !client->is_sessiond) { + /* + * Notifications resulting from an hidden trigger are + * only sent to the session daemon. + */ + DBG("Skipping client as the trigger is hidden and the client is not the session daemon"); + goto skip_client; + } + + if (source_object_creds) { + if (client->uid != lttng_credentials_get_uid(source_object_creds) && + client->gid != lttng_credentials_get_gid(source_object_creds) && + client->uid != 0) { + /* + * Client is not allowed to monitor this + * object. + */ + DBG("Skipping client at it does not have the object permission to receive notification for this trigger"); + goto skip_client; + } + } + + if (client->uid != lttng_credentials_get_uid(trigger_creds)) { + DBG("Skipping client at it does not have the permission to receive notification for this trigger"); + goto skip_client; + } + + DBG("Sending notification to client (fd = %i, %zu bytes)", + client->socket, msg_payload.buffer.size); + + if (client_has_outbound_data_left(client)) { + /* + * Outgoing data is already buffered for this client; + * drop the notification and enqueue a "dropped + * notification" message if this is the first dropped + * notification since the socket spilled-over to the + * queue. + */ + ret = client_notification_overflow(client); + if (ret) { + /* Fatal error. */ + goto skip_client; + } + } + + ret = lttng_payload_copy(&msg_payload, &client->communication.outbound.payload); + if (ret) { + /* Fatal error. */ + goto skip_client; + } + + transmission_status = client_flush_outgoing_queue(client); + pthread_mutex_unlock(&client->lock); + ret = client_report(client, transmission_status, user_data); + if (ret) { + /* Fatal error. */ + goto end_unlock_list; + } + + continue; + +skip_client: + pthread_mutex_unlock(&client->lock); + if (ret) { + /* Fatal error. */ + goto end_unlock_list; + } + } + ret = 0; + +end_unlock_list: + pthread_mutex_unlock(&client_list->lock); +end: + lttng_payload_reset(&msg_payload); + return ret; +} + +static +struct lttng_event_notifier_notification *recv_one_event_notifier_notification( + int notification_pipe_read_fd, enum lttng_domain_type domain) +{ + int ret; + uint64_t token; + struct lttng_event_notifier_notification *notification = NULL; + char *capture_buffer = NULL; + size_t capture_buffer_size; + void *reception_buffer; + size_t reception_size; + + struct lttng_ust_abi_event_notifier_notification ust_notification; + struct lttng_kernel_abi_event_notifier_notification kernel_notification; + + /* Init lttng_event_notifier_notification */ + switch(domain) { + case LTTNG_DOMAIN_UST: + reception_buffer = (void *) &ust_notification; + reception_size = sizeof(ust_notification); + break; + case LTTNG_DOMAIN_KERNEL: + reception_buffer = (void *) &kernel_notification; + reception_size = sizeof(kernel_notification); + break; + default: + abort(); + } + + /* + * The monitoring pipe only holds messages smaller than PIPE_BUF, + * ensuring that read/write of tracer notifications are atomic. + */ + ret = lttng_read(notification_pipe_read_fd, reception_buffer, + reception_size); + if (ret != reception_size) { + PERROR("Failed to read from event source notification pipe: fd = %d, size to read = %zu, ret = %d", + notification_pipe_read_fd, reception_size, ret); + ret = -1; + goto end; + } + + switch(domain) { + case LTTNG_DOMAIN_UST: + token = ust_notification.token; + capture_buffer_size = ust_notification.capture_buf_size; + break; + case LTTNG_DOMAIN_KERNEL: + token = kernel_notification.token; + capture_buffer_size = kernel_notification.capture_buf_size; + break; + default: + abort(); + } + + if (capture_buffer_size == 0) { + capture_buffer = NULL; + goto skip_capture; + } + + if (capture_buffer_size > MAX_CAPTURE_SIZE) { + ERR("Event notifier has a capture payload size which exceeds the maximum allowed size: capture_payload_size = %zu bytes, max allowed size = %d bytes", + capture_buffer_size, MAX_CAPTURE_SIZE); + goto end; + } + + capture_buffer = (char *) zmalloc(capture_buffer_size); + if (!capture_buffer) { + ERR("Failed to allocate capture buffer"); + goto end; + } + + /* Fetch additional payload (capture). */ + ret = lttng_read(notification_pipe_read_fd, capture_buffer, capture_buffer_size); + if (ret != capture_buffer_size) { + ERR("Failed to read from event source pipe (fd = %i)", + notification_pipe_read_fd); + goto end; + } + +skip_capture: + notification = lttng_event_notifier_notification_create(token, domain, + capture_buffer, capture_buffer_size); + if (notification == NULL) { + goto end; + } + + /* + * Ownership transfered to the lttng_event_notifier_notification object. + */ + capture_buffer = NULL; + +end: + free(capture_buffer); + return notification; +} + +static +int dispatch_one_event_notifier_notification(struct notification_thread_state *state, + struct lttng_event_notifier_notification *notification) +{ + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + struct notification_trigger_tokens_ht_element *element; + struct lttng_evaluation *evaluation = NULL; + enum action_executor_status executor_status; + struct notification_client_list *client_list = NULL; + int ret; + unsigned int capture_count = 0; + + /* Find triggers associated with this token. */ + rcu_read_lock(); + cds_lfht_lookup(state->trigger_tokens_ht, + hash_key_u64(¬ification->tracer_token, lttng_ht_seed), + match_trigger_token, ¬ification->tracer_token, &iter); + node = cds_lfht_iter_get_node(&iter); + if (caa_unlikely(!node)) { + /* + * This is not an error, slow consumption of the tracer + * notifications can lead to situations where a trigger is + * removed but we still get tracer notifications matching a + * trigger that no longer exists. + */ + ret = 0; + goto end_unlock; + } + + element = caa_container_of(node, + struct notification_trigger_tokens_ht_element, + node); + + if (lttng_condition_event_rule_matches_get_capture_descriptor_count( + lttng_trigger_get_const_condition(element->trigger), + &capture_count) != LTTNG_CONDITION_STATUS_OK) { + ERR("Failed to get capture count"); + ret = -1; + goto end; + } + + if (!notification->capture_buffer && capture_count != 0) { + ERR("Expected capture but capture buffer is null"); + ret = -1; + goto end; + } + + evaluation = lttng_evaluation_event_rule_matches_create( + container_of(lttng_trigger_get_const_condition( + element->trigger), + struct lttng_condition_event_rule_matches, + parent), + notification->capture_buffer, + notification->capture_buf_size, false); + + if (evaluation == NULL) { + ERR("Failed to create event rule matches evaluation while creating and enqueuing action executor job"); + ret = -1; + goto end_unlock; + } + + client_list = get_client_list_from_condition(state, + lttng_trigger_get_const_condition(element->trigger)); + executor_status = action_executor_enqueue_trigger(state->executor, + element->trigger, evaluation, NULL, 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; + + /* No clients subscribed to notifications for this trigger. */ + 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->clients_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_unlock(&client_list->lock); + break; + } + case ACTION_EXECUTOR_STATUS_INVALID: + case ACTION_EXECUTOR_STATUS_ERROR: + /* Fatal error, shut down everything. */ + ERR("Fatal error encoutered while enqueuing action to the action executor"); + ret = -1; + goto end_unlock; + default: + /* Unhandled error. */ + abort(); + } + +end_unlock: + notification_client_list_put(client_list); + rcu_read_unlock(); +end: + return ret; +} + +static +int handle_one_event_notifier_notification( + struct notification_thread_state *state, + int pipe, enum lttng_domain_type domain) +{ + int ret = 0; + struct lttng_event_notifier_notification *notification = NULL; + + notification = recv_one_event_notifier_notification(pipe, domain); + if (notification == NULL) { + /* Reception failed, don't consider it fatal. */ + ERR("Error receiving an event notifier notification from tracer: fd = %i, domain = %s", + pipe, lttng_domain_type_str(domain)); + goto end; + } + + ret = dispatch_one_event_notifier_notification(state, notification); + if (ret) { + ERR("Error dispatching an event notifier notification from tracer: fd = %i, domain = %s", + pipe, lttng_domain_type_str(domain)); + goto end; + } + +end: + lttng_event_notifier_notification_destroy(notification); + return ret; +} + +int handle_notification_thread_event_notification(struct notification_thread_state *state, + int pipe, enum lttng_domain_type domain) +{ + return handle_one_event_notifier_notification(state, pipe, domain); +} + +int handle_notification_thread_channel_sample( + struct notification_thread_state *state, int pipe, + enum lttng_domain_type domain) +{ + int ret = 0; + struct lttcomm_consumer_channel_monitor_msg sample_msg; + struct channel_info *channel_info; + struct cds_lfht_node *node; + struct cds_lfht_iter iter; + struct lttng_channel_trigger_list *trigger_list; + struct lttng_trigger_list_element *trigger_list_element; + bool previous_sample_available = false; + struct channel_state_sample previous_sample, latest_sample; + uint64_t previous_session_consumed_total, latest_session_consumed_total; + struct lttng_credentials channel_creds; + + /* + * The monitoring pipe only holds messages smaller than PIPE_BUF, + * ensuring that read/write of sampling messages are atomic. + */ + ret = lttng_read(pipe, &sample_msg, sizeof(sample_msg)); + if (ret != sizeof(sample_msg)) { + ERR("Failed to read from monitoring pipe (fd = %i)", + pipe); + ret = -1; + goto end; + } + + ret = 0; + latest_sample.key.key = sample_msg.key; + latest_sample.key.domain = domain; + latest_sample.highest_usage = sample_msg.highest; + latest_sample.lowest_usage = sample_msg.lowest; + latest_sample.channel_total_consumed = sample_msg.total_consumed; + + rcu_read_lock(); + + /* Retrieve the channel's informations */ + cds_lfht_lookup(state->channels_ht, + hash_channel_key(&latest_sample.key), + match_channel_info, + &latest_sample.key, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (caa_unlikely(!node)) { + /* + * Not an error since the consumer can push a sample to the pipe + * and the rest of the session daemon could notify us of the + * channel's destruction before we get a chance to process that + * sample. + */ + DBG("Received a sample for an unknown channel from consumerd, key = %" PRIu64 " in %s domain", + latest_sample.key.key, + lttng_domain_type_str(domain)); + goto end_unlock; + } + channel_info = caa_container_of(node, struct channel_info, + channels_ht_node); + DBG("Handling channel sample for channel %s (key = %" PRIu64 ") in session %s (highest usage = %" PRIu64 ", lowest usage = %" PRIu64", total consumed = %" PRIu64")", + channel_info->name, + latest_sample.key.key, + channel_info->session_info->name, + latest_sample.highest_usage, + latest_sample.lowest_usage, + latest_sample.channel_total_consumed); + + previous_session_consumed_total = + channel_info->session_info->consumed_data_size; + + /* Retrieve the channel's last sample, if it exists, and update it. */ + cds_lfht_lookup(state->channel_state_ht, + hash_channel_key(&latest_sample.key), + match_channel_state_sample, + &latest_sample.key, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (caa_likely(node)) { + struct channel_state_sample *stored_sample; + + /* Update the sample stored. */ + stored_sample = caa_container_of(node, + struct channel_state_sample, + channel_state_ht_node); + + memcpy(&previous_sample, stored_sample, + sizeof(previous_sample)); + stored_sample->highest_usage = latest_sample.highest_usage; + stored_sample->lowest_usage = latest_sample.lowest_usage; + stored_sample->channel_total_consumed = latest_sample.channel_total_consumed; + previous_sample_available = true; + + latest_session_consumed_total = + previous_session_consumed_total + + (latest_sample.channel_total_consumed - previous_sample.channel_total_consumed); + } else { + /* + * This is the channel's first sample, allocate space for and + * store the new sample. + */ + struct channel_state_sample *stored_sample; + + stored_sample = (channel_state_sample *) zmalloc(sizeof(*stored_sample)); + if (!stored_sample) { + ret = -1; + goto end_unlock; + } + + memcpy(stored_sample, &latest_sample, sizeof(*stored_sample)); + cds_lfht_node_init(&stored_sample->channel_state_ht_node); + cds_lfht_add(state->channel_state_ht, + hash_channel_key(&stored_sample->key), + &stored_sample->channel_state_ht_node); + + latest_session_consumed_total = + previous_session_consumed_total + + latest_sample.channel_total_consumed; + } + + channel_info->session_info->consumed_data_size = + latest_session_consumed_total; + + /* Find triggers associated with this channel. */ + cds_lfht_lookup(state->channel_triggers_ht, + hash_channel_key(&latest_sample.key), + match_channel_trigger_list, + &latest_sample.key, + &iter); + node = cds_lfht_iter_get_node(&iter); + if (caa_likely(!node)) { + goto end_unlock; + } + + channel_creds = (typeof(channel_creds)) { + .uid = LTTNG_OPTIONAL_INIT_VALUE(channel_info->session_info->uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(channel_info->session_info->gid), + }; + + trigger_list = caa_container_of(node, struct lttng_channel_trigger_list, + channel_triggers_ht_node); + cds_list_for_each_entry(trigger_list_element, &trigger_list->list, + node) { + const struct lttng_condition *condition; + struct lttng_trigger *trigger; + struct notification_client_list *client_list = NULL; + struct lttng_evaluation *evaluation = NULL; + enum action_executor_status executor_status; + + ret = 0; + trigger = trigger_list_element->trigger; + condition = lttng_trigger_get_const_condition(trigger); + LTTNG_ASSERT(condition); + + /* + * Check if any client is subscribed to the result of this + * evaluation. + */ + client_list = get_client_list_from_condition(state, condition); + + ret = evaluate_buffer_condition(condition, &evaluation, state, + previous_sample_available ? &previous_sample : NULL, + &latest_sample, + previous_session_consumed_total, + latest_session_consumed_total, + channel_info); + if (caa_unlikely(ret)) { + goto put_list; + } + + if (caa_likely(!evaluation)) { + goto put_list; + } + + /* + * Ownership of `evaluation` transferred to the action executor + * no matter the result. + */ + executor_status = action_executor_enqueue_trigger( + state->executor, trigger, evaluation, + &channel_creds, client_list); + evaluation = NULL; + switch (executor_status) { + case ACTION_EXECUTOR_STATUS_OK: + break; + case ACTION_EXECUTOR_STATUS_ERROR: + case ACTION_EXECUTOR_STATUS_INVALID: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + */ + ERR("Fatal error occurred while enqueuing action associated with buffer-condition trigger"); + ret = -1; + goto put_list; + case ACTION_EXECUTOR_STATUS_OVERFLOW: + /* + * TODO Add trigger identification (name/id) when + * it is added to the API. + * + * Not a fatal error. + */ + WARN("No space left when enqueuing action associated with buffer-condition trigger"); + ret = 0; + goto put_list; + default: + abort(); + } + +put_list: + notification_client_list_put(client_list); + if (caa_unlikely(ret)) { + break; + } + } +end_unlock: + rcu_read_unlock(); +end: + return ret; +} diff --git a/src/bin/lttng-sessiond/notification-thread.c b/src/bin/lttng-sessiond/notification-thread.c deleted file mode 100644 index c15e356ad..000000000 --- a/src/bin/lttng-sessiond/notification-thread.c +++ /dev/null @@ -1,817 +0,0 @@ -/* - * Copyright (C) 2017 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "notification-thread.h" -#include "notification-thread-events.h" -#include "notification-thread-commands.h" -#include "lttng-sessiond.h" -#include "health-sessiond.h" -#include "thread.h" -#include "testpoint.h" - -#include "kernel.h" -#include - -#include -#include -#include - - -int notifier_consumption_paused; -/* - * Destroy the thread data previously created by the init function. - */ -void notification_thread_handle_destroy( - struct notification_thread_handle *handle) -{ - int ret; - - if (!handle) { - goto end; - } - - LTTNG_ASSERT(cds_list_empty(&handle->cmd_queue.list)); - pthread_mutex_destroy(&handle->cmd_queue.lock); - sem_destroy(&handle->ready); - - if (handle->cmd_queue.event_pipe) { - lttng_pipe_destroy(handle->cmd_queue.event_pipe); - } - if (handle->channel_monitoring_pipes.ust32_consumer >= 0) { - ret = close(handle->channel_monitoring_pipes.ust32_consumer); - if (ret) { - PERROR("close 32-bit consumer channel monitoring pipe"); - } - } - if (handle->channel_monitoring_pipes.ust64_consumer >= 0) { - ret = close(handle->channel_monitoring_pipes.ust64_consumer); - if (ret) { - PERROR("close 64-bit consumer channel monitoring pipe"); - } - } - if (handle->channel_monitoring_pipes.kernel_consumer >= 0) { - ret = close(handle->channel_monitoring_pipes.kernel_consumer); - if (ret) { - PERROR("close kernel consumer channel monitoring pipe"); - } - } - -end: - free(handle); -} - -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) -{ - int ret; - struct notification_thread_handle *handle; - struct lttng_pipe *event_pipe = NULL; - - handle = zmalloc(sizeof(*handle)); - if (!handle) { - goto end; - } - - sem_init(&handle->ready, 0, 0); - - event_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!event_pipe) { - ERR("event_pipe creation"); - goto error; - } - - handle->cmd_queue.event_pipe = event_pipe; - event_pipe = NULL; - - CDS_INIT_LIST_HEAD(&handle->cmd_queue.list); - ret = pthread_mutex_init(&handle->cmd_queue.lock, NULL); - if (ret) { - goto error; - } - - if (ust32_channel_monitor_pipe) { - handle->channel_monitoring_pipes.ust32_consumer = - lttng_pipe_release_readfd( - ust32_channel_monitor_pipe); - if (handle->channel_monitoring_pipes.ust32_consumer < 0) { - goto error; - } - } else { - handle->channel_monitoring_pipes.ust32_consumer = -1; - } - if (ust64_channel_monitor_pipe) { - handle->channel_monitoring_pipes.ust64_consumer = - lttng_pipe_release_readfd( - ust64_channel_monitor_pipe); - if (handle->channel_monitoring_pipes.ust64_consumer < 0) { - goto error; - } - } else { - handle->channel_monitoring_pipes.ust64_consumer = -1; - } - if (kernel_channel_monitor_pipe) { - handle->channel_monitoring_pipes.kernel_consumer = - lttng_pipe_release_readfd( - kernel_channel_monitor_pipe); - if (handle->channel_monitoring_pipes.kernel_consumer < 0) { - goto error; - } - } else { - handle->channel_monitoring_pipes.kernel_consumer = -1; - } - -end: - return handle; -error: - lttng_pipe_destroy(event_pipe); - notification_thread_handle_destroy(handle); - return NULL; -} - -static -char *get_notification_channel_sock_path(void) -{ - int ret; - bool is_root = !getuid(); - char *sock_path; - - sock_path = zmalloc(LTTNG_PATH_MAX); - if (!sock_path) { - goto error; - } - - if (is_root) { - ret = snprintf(sock_path, LTTNG_PATH_MAX, - DEFAULT_GLOBAL_NOTIFICATION_CHANNEL_UNIX_SOCK); - if (ret < 0) { - goto error; - } - } else { - const char *home_path = utils_get_home_dir(); - - if (!home_path) { - ERR("Can't get HOME directory for socket creation"); - goto error; - } - - ret = snprintf(sock_path, LTTNG_PATH_MAX, - DEFAULT_HOME_NOTIFICATION_CHANNEL_UNIX_SOCK, - home_path); - if (ret < 0) { - goto error; - } - } - - return sock_path; -error: - free(sock_path); - return NULL; -} - -static -void notification_channel_socket_destroy(int fd) -{ - int ret; - char *sock_path = get_notification_channel_sock_path(); - - DBG("Destroying notification channel socket"); - - if (sock_path) { - ret = unlink(sock_path); - free(sock_path); - if (ret < 0) { - PERROR("unlink notification channel socket"); - } - } - - ret = close(fd); - if (ret) { - PERROR("close notification channel socket"); - } -} - -static -int notification_channel_socket_create(void) -{ - int fd = -1, ret; - char *sock_path = get_notification_channel_sock_path(); - - DBG("Creating notification channel UNIX socket at %s", - sock_path); - - ret = lttcomm_create_unix_sock(sock_path); - if (ret < 0) { - ERR("Failed to create notification socket"); - goto error; - } - fd = ret; - - ret = chmod(sock_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); - if (ret < 0) { - ERR("Set file permissions failed: %s", sock_path); - PERROR("chmod notification channel socket"); - goto error; - } - - if (getuid() == 0) { - gid_t gid; - - ret = utils_get_group_id(the_config.tracing_group_name.value, - true, &gid); - if (ret) { - /* Default to root group. */ - gid = 0; - } - - ret = chown(sock_path, 0, gid); - if (ret) { - ERR("Failed to set the notification channel socket's group"); - ret = -1; - goto error; - } - } - - DBG("Notification channel UNIX socket created (fd = %i)", - fd); - free(sock_path); - return fd; -error: - if (fd >= 0 && close(fd) < 0) { - PERROR("close notification channel socket"); - } - free(sock_path); - return ret; -} - -static -int init_poll_set(struct lttng_poll_event *poll_set, - struct notification_thread_handle *handle, - int notification_channel_socket) -{ - int ret; - - /* - * Create pollset with size 5: - * - 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. - */ - ret = lttng_poll_create(poll_set, 5, LTTNG_CLOEXEC); - if (ret < 0) { - goto end; - } - - ret = lttng_poll_add(poll_set, notification_channel_socket, - LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP); - if (ret < 0) { - ERR("Failed to add notification channel socket to pollset"); - goto error; - } - ret = lttng_poll_add(poll_set, lttng_pipe_get_readfd(handle->cmd_queue.event_pipe), - LPOLLIN | LPOLLERR); - if (ret < 0) { - ERR("Failed to add notification command queue event fd to pollset"); - goto error; - } - ret = lttng_poll_add(poll_set, - handle->channel_monitoring_pipes.ust32_consumer, - LPOLLIN | LPOLLERR); - if (ret < 0) { - ERR("Failed to add ust-32 channel monitoring pipe fd to pollset"); - goto error; - } - ret = lttng_poll_add(poll_set, - handle->channel_monitoring_pipes.ust64_consumer, - LPOLLIN | LPOLLERR); - if (ret < 0) { - ERR("Failed to add ust-64 channel monitoring pipe fd to pollset"); - goto error; - } - if (handle->channel_monitoring_pipes.kernel_consumer < 0) { - goto end; - } - ret = lttng_poll_add(poll_set, - handle->channel_monitoring_pipes.kernel_consumer, - LPOLLIN | LPOLLERR); - if (ret < 0) { - ERR("Failed to add kernel channel monitoring pipe fd to pollset"); - goto error; - } -end: - return ret; -error: - lttng_poll_clean(poll_set); - return ret; -} - -static -void fini_thread_state(struct notification_thread_state *state) -{ - int ret; - - if (state->client_socket_ht) { - ret = handle_notification_thread_client_disconnect_all(state); - LTTNG_ASSERT(!ret); - ret = cds_lfht_destroy(state->client_socket_ht, NULL); - LTTNG_ASSERT(!ret); - } - if (state->client_id_ht) { - ret = cds_lfht_destroy(state->client_id_ht, NULL); - LTTNG_ASSERT(!ret); - } - if (state->triggers_ht) { - ret = handle_notification_thread_trigger_unregister_all(state); - LTTNG_ASSERT(!ret); - ret = cds_lfht_destroy(state->triggers_ht, NULL); - LTTNG_ASSERT(!ret); - } - if (state->channel_triggers_ht) { - ret = cds_lfht_destroy(state->channel_triggers_ht, NULL); - LTTNG_ASSERT(!ret); - } - if (state->channel_state_ht) { - ret = cds_lfht_destroy(state->channel_state_ht, NULL); - LTTNG_ASSERT(!ret); - } - if (state->notification_trigger_clients_ht) { - ret = cds_lfht_destroy(state->notification_trigger_clients_ht, - NULL); - LTTNG_ASSERT(!ret); - } - if (state->channels_ht) { - ret = cds_lfht_destroy(state->channels_ht, NULL); - LTTNG_ASSERT(!ret); - } - if (state->sessions_ht) { - ret = cds_lfht_destroy(state->sessions_ht, NULL); - LTTNG_ASSERT(!ret); - } - if (state->triggers_by_name_uid_ht) { - ret = cds_lfht_destroy(state->triggers_by_name_uid_ht, NULL); - LTTNG_ASSERT(!ret); - } - if (state->trigger_tokens_ht) { - ret = cds_lfht_destroy(state->trigger_tokens_ht, NULL); - LTTNG_ASSERT(!ret); - } - /* - * Must be destroyed after all channels have been destroyed. - * See comment in struct lttng_session_trigger_list. - */ - if (state->session_triggers_ht) { - ret = cds_lfht_destroy(state->session_triggers_ht, NULL); - LTTNG_ASSERT(!ret); - } - if (state->notification_channel_socket >= 0) { - notification_channel_socket_destroy( - state->notification_channel_socket); - } - - LTTNG_ASSERT(cds_list_empty(&state->tracer_event_sources_list)); - - if (state->executor) { - action_executor_destroy(state->executor); - } - lttng_poll_clean(&state->events); -} - -static -void mark_thread_as_ready(struct notification_thread_handle *handle) -{ - DBG("Marking notification thread as ready"); - sem_post(&handle->ready); -} - -static -void wait_until_thread_is_ready(struct notification_thread_handle *handle) -{ - DBG("Waiting for notification thread to be ready"); - sem_wait(&handle->ready); - DBG("Notification thread is ready"); -} - -static -int init_thread_state(struct notification_thread_handle *handle, - struct notification_thread_state *state) -{ - int ret; - - memset(state, 0, sizeof(*state)); - state->notification_channel_socket = -1; - state->trigger_id.next_tracer_token = 1; - lttng_poll_init(&state->events); - - ret = notification_channel_socket_create(); - if (ret < 0) { - goto end; - } - state->notification_channel_socket = ret; - - ret = init_poll_set(&state->events, handle, - state->notification_channel_socket); - if (ret) { - goto end; - } - - DBG("Listening on notification channel socket"); - ret = lttcomm_listen_unix_sock(state->notification_channel_socket); - if (ret < 0) { - ERR("Listen failed on notification channel socket"); - goto error; - } - - state->client_socket_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, - CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!state->client_socket_ht) { - 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) { - goto error; - } - - state->session_triggers_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, - CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!state->session_triggers_ht) { - goto error; - } - - state->channel_state_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, - CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!state->channel_state_ht) { - goto error; - } - - state->notification_trigger_clients_ht = cds_lfht_new(DEFAULT_HT_SIZE, - 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!state->notification_trigger_clients_ht) { - goto error; - } - - state->channels_ht = cds_lfht_new(DEFAULT_HT_SIZE, - 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!state->channels_ht) { - goto error; - } - state->sessions_ht = cds_lfht_new(DEFAULT_HT_SIZE, - 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!state->sessions_ht) { - goto error; - } - state->triggers_ht = cds_lfht_new(DEFAULT_HT_SIZE, - 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!state->triggers_ht) { - goto error; - } - state->triggers_by_name_uid_ht = cds_lfht_new(DEFAULT_HT_SIZE, - 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!state->triggers_by_name_uid_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; - } - - CDS_INIT_LIST_HEAD(&state->tracer_event_sources_list); - - state->executor = action_executor_create(handle); - if (!state->executor) { - goto error; - } - - state->restart_poll = false; - - mark_thread_as_ready(handle); -end: - return 0; -error: - fini_thread_state(state); - return -1; -} - -static -int handle_channel_monitoring_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 (fd == handle->channel_monitoring_pipes.ust32_consumer || - fd == handle->channel_monitoring_pipes.ust64_consumer) { - domain = LTTNG_DOMAIN_UST; - } else if (fd == handle->channel_monitoring_pipes.kernel_consumer) { - domain = LTTNG_DOMAIN_KERNEL; - } else { - abort(); - } - - if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ret = lttng_poll_del(&state->events, fd); - if (ret) { - ERR("Failed to remove consumer monitoring pipe from poll set"); - } - goto end; - } - - ret = handle_notification_thread_channel_sample( - state, fd, domain); - if (ret) { - ERR("Consumer sample handling error occurred"); - ret = -1; - goto end; - } -end: - return ret; -} - -static int handle_event_notification_pipe(int event_source_fd, - enum lttng_domain_type domain, - uint32_t revents, - struct notification_thread_state *state) -{ - int ret = 0; - - if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ret = handle_notification_thread_tracer_event_source_died( - state, event_source_fd); - if (ret) { - ERR("Failed to remove event notification pipe from poll set: fd = %d", - event_source_fd); - } - goto end; - } - - if (testpoint(sessiond_handle_notifier_event_pipe)) { - ret = 0; - goto end; - } - - if (caa_unlikely(notifier_consumption_paused)) { - DBG("Event notifier notification consumption paused, sleeping..."); - sleep(1); - goto end; - } - - ret = handle_notification_thread_event_notification( - state, event_source_fd, domain); - if (ret) { - ERR("Event notification handling error occurred for fd: %d", - event_source_fd); - ret = -1; - goto end; - } - -end: - return ret; -} - -/* - * Return the event source domain type via parameter. - */ -static bool fd_is_event_notification_source(const struct notification_thread_state *state, - int fd, - enum lttng_domain_type *domain) -{ - struct notification_event_tracer_event_source_element *source_element; - - LTTNG_ASSERT(domain); - - cds_list_for_each_entry(source_element, - &state->tracer_event_sources_list, node) { - if (source_element->fd != fd) { - continue; - } - - *domain = source_element->domain; - return true; - } - - return false; -} - -/* - * This thread services notification channel clients and commands received - * from various lttng-sessiond components over a command queue. - */ -static -void *thread_notification(void *data) -{ - int ret; - struct notification_thread_handle *handle = data; - struct notification_thread_state state; - enum lttng_domain_type domain; - - DBG("Started notification thread"); - - health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_NOTIFICATION); - rcu_register_thread(); - rcu_thread_online(); - - if (!handle) { - ERR("Invalid thread context provided"); - goto end; - } - - health_code_update(); - - ret = init_thread_state(handle, &state); - if (ret) { - goto end; - } - - if (testpoint(sessiond_thread_notification)) { - goto end; - } - - while (true) { - int fd_count, i; - - health_poll_entry(); - DBG("Entering poll wait"); - ret = lttng_poll_wait(&state.events, -1); - DBG("Poll wait returned (%i)", ret); - health_poll_exit(); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - continue; - } - ERR("Error encountered during lttng_poll_wait (%i)", ret); - goto error; - } - - /* - * Reset restart_poll flag so that calls below might turn it - * on. - */ - state.restart_poll = false; - - fd_count = ret; - for (i = 0; i < fd_count; i++) { - int fd = LTTNG_POLL_GETFD(&state.events, i); - uint32_t revents = LTTNG_POLL_GETEV(&state.events, i); - - DBG("Handling fd (%i) activity (%u)", fd, revents); - - if (fd == state.notification_channel_socket) { - if (revents & LPOLLIN) { - ret = handle_notification_thread_client_connect( - &state); - if (ret < 0) { - goto error; - } - } else if (revents & - (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ERR("Notification socket poll error"); - goto error; - } else { - ERR("Unexpected poll events %u for notification socket %i", revents, fd); - goto error; - } - } else if (fd == lttng_pipe_get_readfd(handle->cmd_queue.event_pipe)) { - ret = handle_notification_thread_command(handle, - &state); - if (ret < 0) { - DBG("Error encountered while servicing command queue"); - goto error; - } else if (ret > 0) { - goto exit; - } - } else if (fd == handle->channel_monitoring_pipes.ust32_consumer || - fd == handle->channel_monitoring_pipes.ust64_consumer || - fd == handle->channel_monitoring_pipes.kernel_consumer) { - ret = handle_channel_monitoring_pipe(fd, - revents, handle, &state); - if (ret) { - goto error; - } - } else if (fd_is_event_notification_source(&state, fd, &domain)) { - ret = handle_event_notification_pipe(fd, domain, revents, &state); - if (ret) { - goto error; - } - } else { - /* Activity on a client's socket. */ - if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - /* - * It doesn't matter if a command was - * pending on the client socket at this - * point since it now has no way to - * receive the notifications to which - * it was subscribing or unsubscribing. - */ - ret = handle_notification_thread_client_disconnect( - fd, &state); - if (ret) { - goto error; - } - } else { - if (revents & LPOLLIN) { - ret = handle_notification_thread_client_in( - &state, fd); - if (ret) { - goto error; - } - } - - if (revents & LPOLLOUT) { - ret = handle_notification_thread_client_out( - &state, fd); - if (ret) { - goto error; - } - } - } - } - - /* - * Calls above might have changed the state of the - * FDs in `state.events`. Call _poll_wait() again to - * ensure we have a consistent state. - */ - if (state.restart_poll) { - break; - } - } - } -exit: -error: - fini_thread_state(&state); -end: - rcu_thread_offline(); - rcu_unregister_thread(); - health_unregister(the_health_sessiond); - return NULL; -} - -static -bool shutdown_notification_thread(void *thread_data) -{ - struct notification_thread_handle *handle = thread_data; - - notification_thread_command_quit(handle); - return true; -} - -struct lttng_thread *launch_notification_thread( - struct notification_thread_handle *handle) -{ - struct lttng_thread *thread; - - thread = lttng_thread_create("Notification", - thread_notification, - shutdown_notification_thread, - NULL, - handle); - if (!thread) { - goto error; - } - - /* - * Wait for the thread to be marked as "ready" before returning - * as other subsystems depend on the notification subsystem - * (e.g. rotation thread). - */ - wait_until_thread_is_ready(handle); - return thread; -error: - return NULL; -} diff --git a/src/bin/lttng-sessiond/notification-thread.cpp b/src/bin/lttng-sessiond/notification-thread.cpp new file mode 100644 index 000000000..e8a2adec9 --- /dev/null +++ b/src/bin/lttng-sessiond/notification-thread.cpp @@ -0,0 +1,817 @@ +/* + * Copyright (C) 2017 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "notification-thread.h" +#include "notification-thread-events.h" +#include "notification-thread-commands.h" +#include "lttng-sessiond.h" +#include "health-sessiond.h" +#include "thread.h" +#include "testpoint.h" + +#include "kernel.h" +#include + +#include +#include +#include + + +int notifier_consumption_paused; +/* + * Destroy the thread data previously created by the init function. + */ +void notification_thread_handle_destroy( + struct notification_thread_handle *handle) +{ + int ret; + + if (!handle) { + goto end; + } + + LTTNG_ASSERT(cds_list_empty(&handle->cmd_queue.list)); + pthread_mutex_destroy(&handle->cmd_queue.lock); + sem_destroy(&handle->ready); + + if (handle->cmd_queue.event_pipe) { + lttng_pipe_destroy(handle->cmd_queue.event_pipe); + } + if (handle->channel_monitoring_pipes.ust32_consumer >= 0) { + ret = close(handle->channel_monitoring_pipes.ust32_consumer); + if (ret) { + PERROR("close 32-bit consumer channel monitoring pipe"); + } + } + if (handle->channel_monitoring_pipes.ust64_consumer >= 0) { + ret = close(handle->channel_monitoring_pipes.ust64_consumer); + if (ret) { + PERROR("close 64-bit consumer channel monitoring pipe"); + } + } + if (handle->channel_monitoring_pipes.kernel_consumer >= 0) { + ret = close(handle->channel_monitoring_pipes.kernel_consumer); + if (ret) { + PERROR("close kernel consumer channel monitoring pipe"); + } + } + +end: + free(handle); +} + +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) +{ + int ret; + struct notification_thread_handle *handle; + struct lttng_pipe *event_pipe = NULL; + + handle = (notification_thread_handle *) zmalloc(sizeof(*handle)); + if (!handle) { + goto end; + } + + sem_init(&handle->ready, 0, 0); + + event_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!event_pipe) { + ERR("event_pipe creation"); + goto error; + } + + handle->cmd_queue.event_pipe = event_pipe; + event_pipe = NULL; + + CDS_INIT_LIST_HEAD(&handle->cmd_queue.list); + ret = pthread_mutex_init(&handle->cmd_queue.lock, NULL); + if (ret) { + goto error; + } + + if (ust32_channel_monitor_pipe) { + handle->channel_monitoring_pipes.ust32_consumer = + lttng_pipe_release_readfd( + ust32_channel_monitor_pipe); + if (handle->channel_monitoring_pipes.ust32_consumer < 0) { + goto error; + } + } else { + handle->channel_monitoring_pipes.ust32_consumer = -1; + } + if (ust64_channel_monitor_pipe) { + handle->channel_monitoring_pipes.ust64_consumer = + lttng_pipe_release_readfd( + ust64_channel_monitor_pipe); + if (handle->channel_monitoring_pipes.ust64_consumer < 0) { + goto error; + } + } else { + handle->channel_monitoring_pipes.ust64_consumer = -1; + } + if (kernel_channel_monitor_pipe) { + handle->channel_monitoring_pipes.kernel_consumer = + lttng_pipe_release_readfd( + kernel_channel_monitor_pipe); + if (handle->channel_monitoring_pipes.kernel_consumer < 0) { + goto error; + } + } else { + handle->channel_monitoring_pipes.kernel_consumer = -1; + } + +end: + return handle; +error: + lttng_pipe_destroy(event_pipe); + notification_thread_handle_destroy(handle); + return NULL; +} + +static +char *get_notification_channel_sock_path(void) +{ + int ret; + bool is_root = !getuid(); + char *sock_path; + + sock_path = (char *) zmalloc(LTTNG_PATH_MAX); + if (!sock_path) { + goto error; + } + + if (is_root) { + ret = snprintf(sock_path, LTTNG_PATH_MAX, + DEFAULT_GLOBAL_NOTIFICATION_CHANNEL_UNIX_SOCK); + if (ret < 0) { + goto error; + } + } else { + const char *home_path = utils_get_home_dir(); + + if (!home_path) { + ERR("Can't get HOME directory for socket creation"); + goto error; + } + + ret = snprintf(sock_path, LTTNG_PATH_MAX, + DEFAULT_HOME_NOTIFICATION_CHANNEL_UNIX_SOCK, + home_path); + if (ret < 0) { + goto error; + } + } + + return sock_path; +error: + free(sock_path); + return NULL; +} + +static +void notification_channel_socket_destroy(int fd) +{ + int ret; + char *sock_path = get_notification_channel_sock_path(); + + DBG("Destroying notification channel socket"); + + if (sock_path) { + ret = unlink(sock_path); + free(sock_path); + if (ret < 0) { + PERROR("unlink notification channel socket"); + } + } + + ret = close(fd); + if (ret) { + PERROR("close notification channel socket"); + } +} + +static +int notification_channel_socket_create(void) +{ + int fd = -1, ret; + char *sock_path = get_notification_channel_sock_path(); + + DBG("Creating notification channel UNIX socket at %s", + sock_path); + + ret = lttcomm_create_unix_sock(sock_path); + if (ret < 0) { + ERR("Failed to create notification socket"); + goto error; + } + fd = ret; + + ret = chmod(sock_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + if (ret < 0) { + ERR("Set file permissions failed: %s", sock_path); + PERROR("chmod notification channel socket"); + goto error; + } + + if (getuid() == 0) { + gid_t gid; + + ret = utils_get_group_id(the_config.tracing_group_name.value, + true, &gid); + if (ret) { + /* Default to root group. */ + gid = 0; + } + + ret = chown(sock_path, 0, gid); + if (ret) { + ERR("Failed to set the notification channel socket's group"); + ret = -1; + goto error; + } + } + + DBG("Notification channel UNIX socket created (fd = %i)", + fd); + free(sock_path); + return fd; +error: + if (fd >= 0 && close(fd) < 0) { + PERROR("close notification channel socket"); + } + free(sock_path); + return ret; +} + +static +int init_poll_set(struct lttng_poll_event *poll_set, + struct notification_thread_handle *handle, + int notification_channel_socket) +{ + int ret; + + /* + * Create pollset with size 5: + * - 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. + */ + ret = lttng_poll_create(poll_set, 5, LTTNG_CLOEXEC); + if (ret < 0) { + goto end; + } + + ret = lttng_poll_add(poll_set, notification_channel_socket, + LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP); + if (ret < 0) { + ERR("Failed to add notification channel socket to pollset"); + goto error; + } + ret = lttng_poll_add(poll_set, lttng_pipe_get_readfd(handle->cmd_queue.event_pipe), + LPOLLIN | LPOLLERR); + if (ret < 0) { + ERR("Failed to add notification command queue event fd to pollset"); + goto error; + } + ret = lttng_poll_add(poll_set, + handle->channel_monitoring_pipes.ust32_consumer, + LPOLLIN | LPOLLERR); + if (ret < 0) { + ERR("Failed to add ust-32 channel monitoring pipe fd to pollset"); + goto error; + } + ret = lttng_poll_add(poll_set, + handle->channel_monitoring_pipes.ust64_consumer, + LPOLLIN | LPOLLERR); + if (ret < 0) { + ERR("Failed to add ust-64 channel monitoring pipe fd to pollset"); + goto error; + } + if (handle->channel_monitoring_pipes.kernel_consumer < 0) { + goto end; + } + ret = lttng_poll_add(poll_set, + handle->channel_monitoring_pipes.kernel_consumer, + LPOLLIN | LPOLLERR); + if (ret < 0) { + ERR("Failed to add kernel channel monitoring pipe fd to pollset"); + goto error; + } +end: + return ret; +error: + lttng_poll_clean(poll_set); + return ret; +} + +static +void fini_thread_state(struct notification_thread_state *state) +{ + int ret; + + if (state->client_socket_ht) { + ret = handle_notification_thread_client_disconnect_all(state); + LTTNG_ASSERT(!ret); + ret = cds_lfht_destroy(state->client_socket_ht, NULL); + LTTNG_ASSERT(!ret); + } + if (state->client_id_ht) { + ret = cds_lfht_destroy(state->client_id_ht, NULL); + LTTNG_ASSERT(!ret); + } + if (state->triggers_ht) { + ret = handle_notification_thread_trigger_unregister_all(state); + LTTNG_ASSERT(!ret); + ret = cds_lfht_destroy(state->triggers_ht, NULL); + LTTNG_ASSERT(!ret); + } + if (state->channel_triggers_ht) { + ret = cds_lfht_destroy(state->channel_triggers_ht, NULL); + LTTNG_ASSERT(!ret); + } + if (state->channel_state_ht) { + ret = cds_lfht_destroy(state->channel_state_ht, NULL); + LTTNG_ASSERT(!ret); + } + if (state->notification_trigger_clients_ht) { + ret = cds_lfht_destroy(state->notification_trigger_clients_ht, + NULL); + LTTNG_ASSERT(!ret); + } + if (state->channels_ht) { + ret = cds_lfht_destroy(state->channels_ht, NULL); + LTTNG_ASSERT(!ret); + } + if (state->sessions_ht) { + ret = cds_lfht_destroy(state->sessions_ht, NULL); + LTTNG_ASSERT(!ret); + } + if (state->triggers_by_name_uid_ht) { + ret = cds_lfht_destroy(state->triggers_by_name_uid_ht, NULL); + LTTNG_ASSERT(!ret); + } + if (state->trigger_tokens_ht) { + ret = cds_lfht_destroy(state->trigger_tokens_ht, NULL); + LTTNG_ASSERT(!ret); + } + /* + * Must be destroyed after all channels have been destroyed. + * See comment in struct lttng_session_trigger_list. + */ + if (state->session_triggers_ht) { + ret = cds_lfht_destroy(state->session_triggers_ht, NULL); + LTTNG_ASSERT(!ret); + } + if (state->notification_channel_socket >= 0) { + notification_channel_socket_destroy( + state->notification_channel_socket); + } + + LTTNG_ASSERT(cds_list_empty(&state->tracer_event_sources_list)); + + if (state->executor) { + action_executor_destroy(state->executor); + } + lttng_poll_clean(&state->events); +} + +static +void mark_thread_as_ready(struct notification_thread_handle *handle) +{ + DBG("Marking notification thread as ready"); + sem_post(&handle->ready); +} + +static +void wait_until_thread_is_ready(struct notification_thread_handle *handle) +{ + DBG("Waiting for notification thread to be ready"); + sem_wait(&handle->ready); + DBG("Notification thread is ready"); +} + +static +int init_thread_state(struct notification_thread_handle *handle, + struct notification_thread_state *state) +{ + int ret; + + memset(state, 0, sizeof(*state)); + state->notification_channel_socket = -1; + state->trigger_id.next_tracer_token = 1; + lttng_poll_init(&state->events); + + ret = notification_channel_socket_create(); + if (ret < 0) { + goto end; + } + state->notification_channel_socket = ret; + + ret = init_poll_set(&state->events, handle, + state->notification_channel_socket); + if (ret) { + goto end; + } + + DBG("Listening on notification channel socket"); + ret = lttcomm_listen_unix_sock(state->notification_channel_socket); + if (ret < 0) { + ERR("Listen failed on notification channel socket"); + goto error; + } + + state->client_socket_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->client_socket_ht) { + 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) { + goto error; + } + + state->session_triggers_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->session_triggers_ht) { + goto error; + } + + state->channel_state_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->channel_state_ht) { + goto error; + } + + state->notification_trigger_clients_ht = cds_lfht_new(DEFAULT_HT_SIZE, + 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->notification_trigger_clients_ht) { + goto error; + } + + state->channels_ht = cds_lfht_new(DEFAULT_HT_SIZE, + 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->channels_ht) { + goto error; + } + state->sessions_ht = cds_lfht_new(DEFAULT_HT_SIZE, + 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->sessions_ht) { + goto error; + } + state->triggers_ht = cds_lfht_new(DEFAULT_HT_SIZE, + 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->triggers_ht) { + goto error; + } + state->triggers_by_name_uid_ht = cds_lfht_new(DEFAULT_HT_SIZE, + 1, 0, CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!state->triggers_by_name_uid_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; + } + + CDS_INIT_LIST_HEAD(&state->tracer_event_sources_list); + + state->executor = action_executor_create(handle); + if (!state->executor) { + goto error; + } + + state->restart_poll = false; + + mark_thread_as_ready(handle); +end: + return 0; +error: + fini_thread_state(state); + return -1; +} + +static +int handle_channel_monitoring_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 (fd == handle->channel_monitoring_pipes.ust32_consumer || + fd == handle->channel_monitoring_pipes.ust64_consumer) { + domain = LTTNG_DOMAIN_UST; + } else if (fd == handle->channel_monitoring_pipes.kernel_consumer) { + domain = LTTNG_DOMAIN_KERNEL; + } else { + abort(); + } + + if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ret = lttng_poll_del(&state->events, fd); + if (ret) { + ERR("Failed to remove consumer monitoring pipe from poll set"); + } + goto end; + } + + ret = handle_notification_thread_channel_sample( + state, fd, domain); + if (ret) { + ERR("Consumer sample handling error occurred"); + ret = -1; + goto end; + } +end: + return ret; +} + +static int handle_event_notification_pipe(int event_source_fd, + enum lttng_domain_type domain, + uint32_t revents, + struct notification_thread_state *state) +{ + int ret = 0; + + if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ret = handle_notification_thread_tracer_event_source_died( + state, event_source_fd); + if (ret) { + ERR("Failed to remove event notification pipe from poll set: fd = %d", + event_source_fd); + } + goto end; + } + + if (testpoint(sessiond_handle_notifier_event_pipe)) { + ret = 0; + goto end; + } + + if (caa_unlikely(notifier_consumption_paused)) { + DBG("Event notifier notification consumption paused, sleeping..."); + sleep(1); + goto end; + } + + ret = handle_notification_thread_event_notification( + state, event_source_fd, domain); + if (ret) { + ERR("Event notification handling error occurred for fd: %d", + event_source_fd); + ret = -1; + goto end; + } + +end: + return ret; +} + +/* + * Return the event source domain type via parameter. + */ +static bool fd_is_event_notification_source(const struct notification_thread_state *state, + int fd, + enum lttng_domain_type *domain) +{ + struct notification_event_tracer_event_source_element *source_element; + + LTTNG_ASSERT(domain); + + cds_list_for_each_entry(source_element, + &state->tracer_event_sources_list, node) { + if (source_element->fd != fd) { + continue; + } + + *domain = source_element->domain; + return true; + } + + return false; +} + +/* + * This thread services notification channel clients and commands received + * from various lttng-sessiond components over a command queue. + */ +static +void *thread_notification(void *data) +{ + int ret; + struct notification_thread_handle *handle = (notification_thread_handle *) data; + struct notification_thread_state state; + enum lttng_domain_type domain; + + DBG("Started notification thread"); + + health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_NOTIFICATION); + rcu_register_thread(); + rcu_thread_online(); + + if (!handle) { + ERR("Invalid thread context provided"); + goto end; + } + + health_code_update(); + + ret = init_thread_state(handle, &state); + if (ret) { + goto end; + } + + if (testpoint(sessiond_thread_notification)) { + goto end; + } + + while (true) { + int fd_count, i; + + health_poll_entry(); + DBG("Entering poll wait"); + ret = lttng_poll_wait(&state.events, -1); + DBG("Poll wait returned (%i)", ret); + health_poll_exit(); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + continue; + } + ERR("Error encountered during lttng_poll_wait (%i)", ret); + goto error; + } + + /* + * Reset restart_poll flag so that calls below might turn it + * on. + */ + state.restart_poll = false; + + fd_count = ret; + for (i = 0; i < fd_count; i++) { + int fd = LTTNG_POLL_GETFD(&state.events, i); + uint32_t revents = LTTNG_POLL_GETEV(&state.events, i); + + DBG("Handling fd (%i) activity (%u)", fd, revents); + + if (fd == state.notification_channel_socket) { + if (revents & LPOLLIN) { + ret = handle_notification_thread_client_connect( + &state); + if (ret < 0) { + goto error; + } + } else if (revents & + (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ERR("Notification socket poll error"); + goto error; + } else { + ERR("Unexpected poll events %u for notification socket %i", revents, fd); + goto error; + } + } else if (fd == lttng_pipe_get_readfd(handle->cmd_queue.event_pipe)) { + ret = handle_notification_thread_command(handle, + &state); + if (ret < 0) { + DBG("Error encountered while servicing command queue"); + goto error; + } else if (ret > 0) { + goto exit; + } + } else if (fd == handle->channel_monitoring_pipes.ust32_consumer || + fd == handle->channel_monitoring_pipes.ust64_consumer || + fd == handle->channel_monitoring_pipes.kernel_consumer) { + ret = handle_channel_monitoring_pipe(fd, + revents, handle, &state); + if (ret) { + goto error; + } + } else if (fd_is_event_notification_source(&state, fd, &domain)) { + ret = handle_event_notification_pipe(fd, domain, revents, &state); + if (ret) { + goto error; + } + } else { + /* Activity on a client's socket. */ + if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + /* + * It doesn't matter if a command was + * pending on the client socket at this + * point since it now has no way to + * receive the notifications to which + * it was subscribing or unsubscribing. + */ + ret = handle_notification_thread_client_disconnect( + fd, &state); + if (ret) { + goto error; + } + } else { + if (revents & LPOLLIN) { + ret = handle_notification_thread_client_in( + &state, fd); + if (ret) { + goto error; + } + } + + if (revents & LPOLLOUT) { + ret = handle_notification_thread_client_out( + &state, fd); + if (ret) { + goto error; + } + } + } + } + + /* + * Calls above might have changed the state of the + * FDs in `state.events`. Call _poll_wait() again to + * ensure we have a consistent state. + */ + if (state.restart_poll) { + break; + } + } + } +exit: +error: + fini_thread_state(&state); +end: + rcu_thread_offline(); + rcu_unregister_thread(); + health_unregister(the_health_sessiond); + return NULL; +} + +static +bool shutdown_notification_thread(void *thread_data) +{ + struct notification_thread_handle *handle = (notification_thread_handle *) thread_data; + + notification_thread_command_quit(handle); + return true; +} + +struct lttng_thread *launch_notification_thread( + struct notification_thread_handle *handle) +{ + struct lttng_thread *thread; + + thread = lttng_thread_create("Notification", + thread_notification, + shutdown_notification_thread, + NULL, + handle); + if (!thread) { + goto error; + } + + /* + * Wait for the thread to be marked as "ready" before returning + * as other subsystems depend on the notification subsystem + * (e.g. rotation thread). + */ + wait_until_thread_is_ready(handle); + return thread; +error: + return NULL; +} diff --git a/src/bin/lttng-sessiond/notify-apps.c b/src/bin/lttng-sessiond/notify-apps.c deleted file mode 100644 index 7c86b57c8..000000000 --- a/src/bin/lttng-sessiond/notify-apps.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2013 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE - -#include -#include - -#include "fd-limit.h" -#include "lttng-sessiond.h" -#include "notify-apps.h" -#include "health-sessiond.h" -#include "testpoint.h" -#include "utils.h" -#include "thread.h" - -struct thread_notifiers { - struct lttng_pipe *quit_pipe; - int apps_cmd_notify_pipe_read_fd; -}; - -/* - * This thread manage application notify communication. - */ -static void *thread_application_notification(void *data) -{ - int i, ret, pollfd, err = -1; - ssize_t size_ret; - uint32_t revents, nb_fd; - struct lttng_poll_event events; - struct thread_notifiers *notifiers = data; - const int quit_pipe_read_fd = lttng_pipe_get_readfd(notifiers->quit_pipe); - - DBG("[ust-thread] Manage application notify command"); - - rcu_register_thread(); - rcu_thread_online(); - - health_register(the_health_sessiond, - HEALTH_SESSIOND_TYPE_APP_MANAGE_NOTIFY); - - if (testpoint(sessiond_thread_app_manage_notify)) { - goto error_testpoint; - } - - health_code_update(); - - ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); - if (ret < 0) { - goto error_poll_create; - } - - /* Add notify pipe to the pollset. */ - ret = lttng_poll_add(&events, notifiers->apps_cmd_notify_pipe_read_fd, - LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP); - if (ret < 0) { - goto error; - } - - ret = lttng_poll_add(&events, quit_pipe_read_fd, - LPOLLIN | LPOLLERR); - if (ret < 0) { - goto error; - } - - health_code_update(); - - while (1) { - DBG3("[ust-thread] Manage notify polling"); - - /* Inifinite blocking call, waiting for transmission */ -restart: - health_poll_entry(); - ret = lttng_poll_wait(&events, -1); - DBG3("[ust-thread] Manage notify return from poll on %d fds", - LTTNG_POLL_GETNB(&events)); - health_poll_exit(); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - goto restart; - } - goto error; - } - - nb_fd = ret; - - for (i = 0; i < nb_fd; i++) { - health_code_update(); - - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - /* Thread quit pipe has been closed. Killing thread. */ - if (pollfd == quit_pipe_read_fd) { - err = 0; - goto exit; - } else if (pollfd == notifiers->apps_cmd_notify_pipe_read_fd) { - /* Inspect the apps cmd pipe */ - int sock; - - if (revents & LPOLLIN) { - /* Get socket from dispatch thread. */ - size_ret = lttng_read(notifiers->apps_cmd_notify_pipe_read_fd, - &sock, sizeof(sock)); - if (size_ret < sizeof(sock)) { - PERROR("read apps notify pipe"); - goto error; - } - health_code_update(); - - ret = lttng_poll_add(&events, sock, - LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP); - if (ret < 0) { - /* - * It's possible we've reached the max poll fd allowed. - * Let's close the socket but continue normal execution. - */ - ret = close(sock); - if (ret) { - PERROR("close notify socket %d", sock); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - continue; - } - DBG3("UST thread notify added sock %d to pollset", sock); - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ERR("Apps notify command pipe error"); - goto error; - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - } else { - /* - * At this point, we know that a registered application - * triggered the event. - */ - if (revents & (LPOLLIN | LPOLLPRI)) { - ret = ust_app_recv_notify(pollfd); - if (ret < 0) { - /* Removing from the poll set */ - ret = lttng_poll_del(&events, pollfd); - if (ret < 0) { - goto error; - } - - /* The socket is closed after a grace period here. */ - ust_app_notify_sock_unregister(pollfd); - } - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - /* Removing from the poll set */ - ret = lttng_poll_del(&events, pollfd); - if (ret < 0) { - goto error; - } - - /* The socket is closed after a grace period here. */ - ust_app_notify_sock_unregister(pollfd); - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - health_code_update(); - } - } - } - -exit: -error: - lttng_poll_clean(&events); -error_poll_create: -error_testpoint: - - DBG("Application notify communication apps thread cleanup complete"); - if (err) { - health_error(); - ERR("Health error occurred in %s", __func__); - } - health_unregister(the_health_sessiond); - rcu_thread_offline(); - rcu_unregister_thread(); - return NULL; -} - -static bool shutdown_application_notification_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); - - return notify_thread_pipe(write_fd) == 1; -} - -static void cleanup_application_notification_thread(void *data) -{ - struct thread_notifiers *notifiers = data; - - lttng_pipe_destroy(notifiers->quit_pipe); - free(notifiers); -} - -bool launch_application_notification_thread(int apps_cmd_notify_pipe_read_fd) -{ - struct lttng_thread *thread; - struct thread_notifiers *notifiers; - struct lttng_pipe *quit_pipe; - - notifiers = zmalloc(sizeof(*notifiers)); - if (!notifiers) { - goto error_alloc; - } - notifiers->apps_cmd_notify_pipe_read_fd = apps_cmd_notify_pipe_read_fd; - - quit_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!quit_pipe) { - goto error; - } - notifiers->quit_pipe = quit_pipe; - - thread = lttng_thread_create("Application notification", - thread_application_notification, - shutdown_application_notification_thread, - cleanup_application_notification_thread, - notifiers); - if (!thread) { - goto error; - } - lttng_thread_put(thread); - return true; -error: - cleanup_application_notification_thread(notifiers); -error_alloc: - return false; -} diff --git a/src/bin/lttng-sessiond/notify-apps.cpp b/src/bin/lttng-sessiond/notify-apps.cpp new file mode 100644 index 000000000..0e799ff26 --- /dev/null +++ b/src/bin/lttng-sessiond/notify-apps.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2013 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE + +#include +#include + +#include "fd-limit.h" +#include "lttng-sessiond.h" +#include "notify-apps.h" +#include "health-sessiond.h" +#include "testpoint.h" +#include "utils.h" +#include "thread.h" + +struct thread_notifiers { + struct lttng_pipe *quit_pipe; + int apps_cmd_notify_pipe_read_fd; +}; + +/* + * This thread manage application notify communication. + */ +static void *thread_application_notification(void *data) +{ + int i, ret, pollfd, err = -1; + ssize_t size_ret; + uint32_t revents, nb_fd; + struct lttng_poll_event events; + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int quit_pipe_read_fd = lttng_pipe_get_readfd(notifiers->quit_pipe); + + DBG("[ust-thread] Manage application notify command"); + + rcu_register_thread(); + rcu_thread_online(); + + health_register(the_health_sessiond, + HEALTH_SESSIOND_TYPE_APP_MANAGE_NOTIFY); + + if (testpoint(sessiond_thread_app_manage_notify)) { + goto error_testpoint; + } + + health_code_update(); + + ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); + if (ret < 0) { + goto error_poll_create; + } + + /* Add notify pipe to the pollset. */ + ret = lttng_poll_add(&events, notifiers->apps_cmd_notify_pipe_read_fd, + LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP); + if (ret < 0) { + goto error; + } + + ret = lttng_poll_add(&events, quit_pipe_read_fd, + LPOLLIN | LPOLLERR); + if (ret < 0) { + goto error; + } + + health_code_update(); + + while (1) { + DBG3("[ust-thread] Manage notify polling"); + + /* Inifinite blocking call, waiting for transmission */ +restart: + health_poll_entry(); + ret = lttng_poll_wait(&events, -1); + DBG3("[ust-thread] Manage notify return from poll on %d fds", + LTTNG_POLL_GETNB(&events)); + health_poll_exit(); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + goto restart; + } + goto error; + } + + nb_fd = ret; + + for (i = 0; i < nb_fd; i++) { + health_code_update(); + + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + /* Thread quit pipe has been closed. Killing thread. */ + if (pollfd == quit_pipe_read_fd) { + err = 0; + goto exit; + } else if (pollfd == notifiers->apps_cmd_notify_pipe_read_fd) { + /* Inspect the apps cmd pipe */ + int sock; + + if (revents & LPOLLIN) { + /* Get socket from dispatch thread. */ + size_ret = lttng_read(notifiers->apps_cmd_notify_pipe_read_fd, + &sock, sizeof(sock)); + if (size_ret < sizeof(sock)) { + PERROR("read apps notify pipe"); + goto error; + } + health_code_update(); + + ret = lttng_poll_add(&events, sock, + LPOLLIN | LPOLLERR | LPOLLHUP | LPOLLRDHUP); + if (ret < 0) { + /* + * It's possible we've reached the max poll fd allowed. + * Let's close the socket but continue normal execution. + */ + ret = close(sock); + if (ret) { + PERROR("close notify socket %d", sock); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + continue; + } + DBG3("UST thread notify added sock %d to pollset", sock); + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ERR("Apps notify command pipe error"); + goto error; + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + } else { + /* + * At this point, we know that a registered application + * triggered the event. + */ + if (revents & (LPOLLIN | LPOLLPRI)) { + ret = ust_app_recv_notify(pollfd); + if (ret < 0) { + /* Removing from the poll set */ + ret = lttng_poll_del(&events, pollfd); + if (ret < 0) { + goto error; + } + + /* The socket is closed after a grace period here. */ + ust_app_notify_sock_unregister(pollfd); + } + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + /* Removing from the poll set */ + ret = lttng_poll_del(&events, pollfd); + if (ret < 0) { + goto error; + } + + /* The socket is closed after a grace period here. */ + ust_app_notify_sock_unregister(pollfd); + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + health_code_update(); + } + } + } + +exit: +error: + lttng_poll_clean(&events); +error_poll_create: +error_testpoint: + + DBG("Application notify communication apps thread cleanup complete"); + if (err) { + health_error(); + ERR("Health error occurred in %s", __func__); + } + health_unregister(the_health_sessiond); + rcu_thread_offline(); + rcu_unregister_thread(); + return NULL; +} + +static bool shutdown_application_notification_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + const int write_fd = lttng_pipe_get_writefd(notifiers->quit_pipe); + + return notify_thread_pipe(write_fd) == 1; +} + +static void cleanup_application_notification_thread(void *data) +{ + struct thread_notifiers *notifiers = (thread_notifiers *) data; + + lttng_pipe_destroy(notifiers->quit_pipe); + free(notifiers); +} + +bool launch_application_notification_thread(int apps_cmd_notify_pipe_read_fd) +{ + struct lttng_thread *thread; + struct thread_notifiers *notifiers; + struct lttng_pipe *quit_pipe; + + notifiers = (thread_notifiers *) zmalloc(sizeof(*notifiers)); + if (!notifiers) { + goto error_alloc; + } + notifiers->apps_cmd_notify_pipe_read_fd = apps_cmd_notify_pipe_read_fd; + + quit_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!quit_pipe) { + goto error; + } + notifiers->quit_pipe = quit_pipe; + + thread = lttng_thread_create("Application notification", + thread_application_notification, + shutdown_application_notification_thread, + cleanup_application_notification_thread, + notifiers); + if (!thread) { + goto error; + } + lttng_thread_put(thread); + return true; +error: + cleanup_application_notification_thread(notifiers); +error_alloc: + return false; +} diff --git a/src/bin/lttng-sessiond/process-utils.c b/src/bin/lttng-sessiond/process-utils.c deleted file mode 100644 index 2d629b547..000000000 --- a/src/bin/lttng-sessiond/process-utils.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include "lttng-sessiond.h" - -/* Notify parents that we are ready for cmd and health check */ -void sessiond_signal_parents(void) -{ - /* - * Notify parent pid that we are ready to accept command - * for client side. This ppid is the one from the - * external process that spawned us. - */ - if (the_config.sig_parent) { - kill(the_ppid, SIGUSR1); - } - - /* - * Notify the parent of the fork() process that we are - * ready. - */ - if (the_config.daemonize || the_config.background) { - kill(the_child_ppid, SIGUSR1); - } -} diff --git a/src/bin/lttng-sessiond/process-utils.cpp b/src/bin/lttng-sessiond/process-utils.cpp new file mode 100644 index 000000000..2d629b547 --- /dev/null +++ b/src/bin/lttng-sessiond/process-utils.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include "lttng-sessiond.h" + +/* Notify parents that we are ready for cmd and health check */ +void sessiond_signal_parents(void) +{ + /* + * Notify parent pid that we are ready to accept command + * for client side. This ppid is the one from the + * external process that spawned us. + */ + if (the_config.sig_parent) { + kill(the_ppid, SIGUSR1); + } + + /* + * Notify the parent of the fork() process that we are + * ready. + */ + if (the_config.daemonize || the_config.background) { + kill(the_child_ppid, SIGUSR1); + } +} diff --git a/src/bin/lttng-sessiond/register.c b/src/bin/lttng-sessiond/register.c deleted file mode 100644 index bd6cd52dd..000000000 --- a/src/bin/lttng-sessiond/register.c +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "register.h" -#include "lttng-sessiond.h" -#include "testpoint.h" -#include "health-sessiond.h" -#include "fd-limit.h" -#include "utils.h" -#include "thread.h" - -struct thread_state { - struct lttng_pipe *quit_pipe; - struct ust_cmd_queue *ust_cmd_queue; - sem_t ready; - bool running; - int application_socket; -}; - -/* - * Creates the application socket. - */ -static int create_application_socket(void) -{ - int ret = 0; - int apps_sock; - const mode_t old_umask = umask(0); - - /* Create the application unix socket */ - apps_sock = lttcomm_create_unix_sock( - the_config.apps_unix_sock_path.value); - if (apps_sock < 0) { - ERR("Create unix sock failed: %s", - the_config.apps_unix_sock_path.value); - ret = -1; - goto end; - } - - /* Set the cloexec flag */ - ret = utils_set_fd_cloexec(apps_sock); - if (ret < 0) { - ERR("Unable to set CLOEXEC flag to the app Unix socket (fd: %d). " - "Continuing but note that the consumer daemon will have a " - "reference to this socket on exec()", apps_sock); - } - - /* File permission MUST be 666 */ - ret = chmod(the_config.apps_unix_sock_path.value, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | - S_IWOTH); - if (ret < 0) { - PERROR("Set file permissions failed on %s", - the_config.apps_unix_sock_path.value); - goto error_close_socket; - } - - DBG3("Session daemon application socket created (fd = %d) ", apps_sock); - ret = apps_sock; -end: - umask(old_umask); - return ret; -error_close_socket: - if (close(apps_sock)) { - PERROR("Failed to close application socket in error path"); - } - apps_sock = -1; - ret = -1; - goto end; -} - -/* - * Notify UST applications using the shm mmap futex. - */ -static int notify_ust_apps(int active, bool is_root) -{ - char *wait_shm_mmap; - - DBG("Notifying applications of session daemon state: %d", active); - - /* See shm.c for this call implying mmap, shm and futex calls */ - wait_shm_mmap = shm_ust_get_mmap( - the_config.wait_shm_path.value, is_root); - if (wait_shm_mmap == NULL) { - goto error; - } - - /* Wake waiting process */ - futex_wait_update((int32_t *) wait_shm_mmap, active); - - /* Apps notified successfully */ - return 0; - -error: - return -1; -} - -static void cleanup_application_registration_thread(void *data) -{ - struct thread_state *thread_state = data; - - if (!data) { - return; - } - - lttng_pipe_destroy(thread_state->quit_pipe); - free(thread_state); -} - -static void set_thread_status(struct thread_state *thread_state, bool running) -{ - DBG("Marking application registration thread's state as %s", running ? "running" : "error"); - thread_state->running = running; - sem_post(&thread_state->ready); -} - -static bool wait_thread_status(struct thread_state *thread_state) -{ - DBG("Waiting for application registration thread to be ready"); - sem_wait(&thread_state->ready); - if (thread_state->running) { - DBG("Application registration thread is ready"); - } else { - ERR("Initialization of application registration thread failed"); - } - - return thread_state->running; -} - -static void thread_init_cleanup(void *data) -{ - struct thread_state *thread_state = data; - - set_thread_status(thread_state, false); -} - -/* - * This thread manage application registration. - */ -static void *thread_application_registration(void *data) -{ - int sock = -1, i, ret, pollfd, err = -1; - uint32_t revents, nb_fd; - struct lttng_poll_event events; - /* - * Gets allocated in this thread, enqueued to a global queue, dequeued - * and freed in the manage apps thread. - */ - struct ust_command *ust_cmd = NULL; - const bool is_root = (getuid() == 0); - struct thread_state *thread_state = data; - const int application_socket = thread_state->application_socket; - const int quit_pipe_read_fd = lttng_pipe_get_readfd( - thread_state->quit_pipe); - - DBG("[thread] Manage application registration started"); - - pthread_cleanup_push(thread_init_cleanup, thread_state); - health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_APP_REG); - - ret = lttcomm_listen_unix_sock(application_socket); - if (ret < 0) { - goto error_listen; - } - - /* - * Pass 2 as size here for the thread quit pipe and apps_sock. Nothing - * more will be added to this poll set. - */ - ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); - if (ret < 0) { - goto error_create_poll; - } - - /* Add the application registration socket */ - ret = lttng_poll_add(&events, application_socket, LPOLLIN | LPOLLRDHUP); - if (ret < 0) { - goto error_poll_add; - } - - /* Add the application registration socket */ - ret = lttng_poll_add(&events, quit_pipe_read_fd, LPOLLIN | LPOLLRDHUP); - if (ret < 0) { - goto error_poll_add; - } - - set_thread_status(thread_state, true); - pthread_cleanup_pop(0); - - if (testpoint(sessiond_thread_registration_apps)) { - goto error_poll_add; - } - - while (1) { - DBG("Accepting application registration"); - - /* Inifinite blocking call, waiting for transmission */ - restart: - health_poll_entry(); - ret = lttng_poll_wait(&events, -1); - health_poll_exit(); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - goto restart; - } - goto error; - } - - nb_fd = ret; - - for (i = 0; i < nb_fd; i++) { - health_code_update(); - - /* Fetch once the poll data */ - revents = LTTNG_POLL_GETEV(&events, i); - pollfd = LTTNG_POLL_GETFD(&events, i); - - /* Thread quit pipe has been closed. Killing thread. */ - if (pollfd == quit_pipe_read_fd) { - err = 0; - goto exit; - } else { - /* Event on the registration socket */ - if (revents & LPOLLIN) { - sock = lttcomm_accept_unix_sock(application_socket); - if (sock < 0) { - goto error; - } - - /* - * Set socket timeout for both receiving and ending. - * app_socket_timeout is in seconds, whereas - * lttcomm_setsockopt_rcv_timeout and - * lttcomm_setsockopt_snd_timeout expect msec as - * parameter. - */ - if (the_config.app_socket_timeout >= 0) { - (void) lttcomm_setsockopt_rcv_timeout(sock, - the_config.app_socket_timeout * 1000); - (void) lttcomm_setsockopt_snd_timeout(sock, - the_config.app_socket_timeout * 1000); - } - - /* - * Set the CLOEXEC flag. Return code is useless because - * either way, the show must go on. - */ - (void) utils_set_fd_cloexec(sock); - - /* Create UST registration command for enqueuing */ - ust_cmd = zmalloc(sizeof(struct ust_command)); - if (ust_cmd == NULL) { - PERROR("ust command zmalloc"); - ret = close(sock); - if (ret) { - PERROR("close"); - } - sock = -1; - goto error; - } - - /* - * Using message-based transmissions to ensure we don't - * have to deal with partially received messages. - */ - ret = lttng_fd_get(LTTNG_FD_APPS, 1); - if (ret < 0) { - ERR("Exhausted file descriptors allowed for applications."); - free(ust_cmd); - ret = close(sock); - if (ret) { - PERROR("close"); - } - sock = -1; - continue; - } - - health_code_update(); - ret = ust_app_recv_registration(sock, &ust_cmd->reg_msg); - if (ret < 0) { - free(ust_cmd); - /* Close socket of the application. */ - ret = close(sock); - if (ret) { - PERROR("close"); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - sock = -1; - continue; - } - health_code_update(); - - ust_cmd->sock = sock; - sock = -1; - - DBG("UST registration received with pid:%d ppid:%d uid:%d" - " gid:%d sock:%d name:%s (version %d.%d)", - ust_cmd->reg_msg.pid, ust_cmd->reg_msg.ppid, - ust_cmd->reg_msg.uid, ust_cmd->reg_msg.gid, - ust_cmd->sock, ust_cmd->reg_msg.name, - ust_cmd->reg_msg.major, ust_cmd->reg_msg.minor); - - /* - * Lock free enqueue the registration request. The red pill - * has been taken! This apps will be part of the *system*. - */ - cds_wfcq_enqueue(&thread_state->ust_cmd_queue->head, - &thread_state->ust_cmd_queue->tail, - &ust_cmd->node); - - /* - * Wake the registration queue futex. Implicit memory - * barrier with the exchange in cds_wfcq_enqueue. - */ - futex_nto1_wake(&thread_state->ust_cmd_queue->futex); - } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { - ERR("Register apps socket poll error"); - goto error; - } else { - ERR("Unexpected poll events %u for sock %d", revents, pollfd); - goto error; - } - } - } - } - -exit: -error: - /* Notify that the registration thread is gone */ - notify_ust_apps(0, is_root); - - ret = close(application_socket); - if (ret) { - PERROR("Failed to close application registration socket"); - } - if (sock >= 0) { - ret = close(sock); - if (ret) { - PERROR("Failed to close application socket"); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - } - unlink(the_config.apps_unix_sock_path.value); - -error_poll_add: - lttng_poll_clean(&events); -error_listen: -error_create_poll: - DBG("UST Registration thread cleanup complete"); - if (err) { - health_error(); - ERR("Health error occurred in %s", __func__); - } - health_unregister(the_health_sessiond); - return NULL; -} - -static bool shutdown_application_registration_thread(void *data) -{ - struct thread_state *thread_state = data; - const int write_fd = lttng_pipe_get_writefd(thread_state->quit_pipe); - - return notify_thread_pipe(write_fd) == 1; -} - -struct lttng_thread *launch_application_registration_thread( - struct ust_cmd_queue *cmd_queue) -{ - int ret; - struct lttng_pipe *quit_pipe; - struct thread_state *thread_state = NULL; - struct lttng_thread *thread = NULL; - const bool is_root = (getuid() == 0); - int application_socket = -1; - - thread_state = zmalloc(sizeof(*thread_state)); - if (!thread_state) { - goto error_alloc; - } - quit_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!quit_pipe) { - goto error; - } - thread_state->quit_pipe = quit_pipe; - thread_state->ust_cmd_queue = cmd_queue; - application_socket = create_application_socket(); - if (application_socket < 0) { - goto error; - } - thread_state->application_socket = application_socket; - sem_init(&thread_state->ready, 0, 0); - - thread = lttng_thread_create("UST application registration", - thread_application_registration, - shutdown_application_registration_thread, - cleanup_application_registration_thread, - thread_state); - if (!thread) { - goto error; - } - /* - * The application registration thread now owns the application socket - * and the global thread state. The thread state is used to wait for - * the thread's status, but its ownership now belongs to the thread. - */ - application_socket = -1; - if (!wait_thread_status(thread_state)) { - thread_state = NULL; - goto error; - } - - /* Notify all applications to register. */ - ret = notify_ust_apps(1, is_root); - if (ret < 0) { - ERR("Failed to notify applications or create the wait shared memory.\n" - "Execution continues but there might be problems for already\n" - "running applications that wishes to register."); - } - - return thread; -error: - lttng_thread_put(thread); - cleanup_application_registration_thread(thread_state); - if (application_socket >= 0) { - if (close(application_socket)) { - PERROR("Failed to close application registration socket"); - } - } -error_alloc: - return NULL; -} diff --git a/src/bin/lttng-sessiond/register.cpp b/src/bin/lttng-sessiond/register.cpp new file mode 100644 index 000000000..5bcc637b0 --- /dev/null +++ b/src/bin/lttng-sessiond/register.cpp @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "register.h" +#include "lttng-sessiond.h" +#include "testpoint.h" +#include "health-sessiond.h" +#include "fd-limit.h" +#include "utils.h" +#include "thread.h" + +struct thread_state { + struct lttng_pipe *quit_pipe; + struct ust_cmd_queue *ust_cmd_queue; + sem_t ready; + bool running; + int application_socket; +}; + +/* + * Creates the application socket. + */ +static int create_application_socket(void) +{ + int ret = 0; + int apps_sock; + const mode_t old_umask = umask(0); + + /* Create the application unix socket */ + apps_sock = lttcomm_create_unix_sock( + the_config.apps_unix_sock_path.value); + if (apps_sock < 0) { + ERR("Create unix sock failed: %s", + the_config.apps_unix_sock_path.value); + ret = -1; + goto end; + } + + /* Set the cloexec flag */ + ret = utils_set_fd_cloexec(apps_sock); + if (ret < 0) { + ERR("Unable to set CLOEXEC flag to the app Unix socket (fd: %d). " + "Continuing but note that the consumer daemon will have a " + "reference to this socket on exec()", apps_sock); + } + + /* File permission MUST be 666 */ + ret = chmod(the_config.apps_unix_sock_path.value, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | + S_IWOTH); + if (ret < 0) { + PERROR("Set file permissions failed on %s", + the_config.apps_unix_sock_path.value); + goto error_close_socket; + } + + DBG3("Session daemon application socket created (fd = %d) ", apps_sock); + ret = apps_sock; +end: + umask(old_umask); + return ret; +error_close_socket: + if (close(apps_sock)) { + PERROR("Failed to close application socket in error path"); + } + apps_sock = -1; + ret = -1; + goto end; +} + +/* + * Notify UST applications using the shm mmap futex. + */ +static int notify_ust_apps(int active, bool is_root) +{ + char *wait_shm_mmap; + + DBG("Notifying applications of session daemon state: %d", active); + + /* See shm.c for this call implying mmap, shm and futex calls */ + wait_shm_mmap = shm_ust_get_mmap( + the_config.wait_shm_path.value, is_root); + if (wait_shm_mmap == NULL) { + goto error; + } + + /* Wake waiting process */ + futex_wait_update((int32_t *) wait_shm_mmap, active); + + /* Apps notified successfully */ + return 0; + +error: + return -1; +} + +static void cleanup_application_registration_thread(void *data) +{ + struct thread_state *thread_state = (struct thread_state *) data; + + if (!data) { + return; + } + + lttng_pipe_destroy(thread_state->quit_pipe); + free(thread_state); +} + +static void set_thread_status(struct thread_state *thread_state, bool running) +{ + DBG("Marking application registration thread's state as %s", running ? "running" : "error"); + thread_state->running = running; + sem_post(&thread_state->ready); +} + +static bool wait_thread_status(struct thread_state *thread_state) +{ + DBG("Waiting for application registration thread to be ready"); + sem_wait(&thread_state->ready); + if (thread_state->running) { + DBG("Application registration thread is ready"); + } else { + ERR("Initialization of application registration thread failed"); + } + + return thread_state->running; +} + +static void thread_init_cleanup(void *data) +{ + struct thread_state *thread_state = (struct thread_state *) data; + + set_thread_status(thread_state, false); +} + +/* + * This thread manage application registration. + */ +static void *thread_application_registration(void *data) +{ + int sock = -1, i, ret, pollfd, err = -1; + uint32_t revents, nb_fd; + struct lttng_poll_event events; + /* + * Gets allocated in this thread, enqueued to a global queue, dequeued + * and freed in the manage apps thread. + */ + struct ust_command *ust_cmd = NULL; + const bool is_root = (getuid() == 0); + struct thread_state *thread_state = (struct thread_state *) data; + const int application_socket = thread_state->application_socket; + const int quit_pipe_read_fd = lttng_pipe_get_readfd( + thread_state->quit_pipe); + + DBG("[thread] Manage application registration started"); + + pthread_cleanup_push(thread_init_cleanup, thread_state); + health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_APP_REG); + + ret = lttcomm_listen_unix_sock(application_socket); + if (ret < 0) { + goto error_listen; + } + + /* + * Pass 2 as size here for the thread quit pipe and apps_sock. Nothing + * more will be added to this poll set. + */ + ret = lttng_poll_create(&events, 2, LTTNG_CLOEXEC); + if (ret < 0) { + goto error_create_poll; + } + + /* Add the application registration socket */ + ret = lttng_poll_add(&events, application_socket, LPOLLIN | LPOLLRDHUP); + if (ret < 0) { + goto error_poll_add; + } + + /* Add the application registration socket */ + ret = lttng_poll_add(&events, quit_pipe_read_fd, LPOLLIN | LPOLLRDHUP); + if (ret < 0) { + goto error_poll_add; + } + + set_thread_status(thread_state, true); + pthread_cleanup_pop(0); + + if (testpoint(sessiond_thread_registration_apps)) { + goto error_poll_add; + } + + while (1) { + DBG("Accepting application registration"); + + /* Inifinite blocking call, waiting for transmission */ + restart: + health_poll_entry(); + ret = lttng_poll_wait(&events, -1); + health_poll_exit(); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + goto restart; + } + goto error; + } + + nb_fd = ret; + + for (i = 0; i < nb_fd; i++) { + health_code_update(); + + /* Fetch once the poll data */ + revents = LTTNG_POLL_GETEV(&events, i); + pollfd = LTTNG_POLL_GETFD(&events, i); + + /* Thread quit pipe has been closed. Killing thread. */ + if (pollfd == quit_pipe_read_fd) { + err = 0; + goto exit; + } else { + /* Event on the registration socket */ + if (revents & LPOLLIN) { + sock = lttcomm_accept_unix_sock(application_socket); + if (sock < 0) { + goto error; + } + + /* + * Set socket timeout for both receiving and ending. + * app_socket_timeout is in seconds, whereas + * lttcomm_setsockopt_rcv_timeout and + * lttcomm_setsockopt_snd_timeout expect msec as + * parameter. + */ + if (the_config.app_socket_timeout >= 0) { + (void) lttcomm_setsockopt_rcv_timeout(sock, + the_config.app_socket_timeout * 1000); + (void) lttcomm_setsockopt_snd_timeout(sock, + the_config.app_socket_timeout * 1000); + } + + /* + * Set the CLOEXEC flag. Return code is useless because + * either way, the show must go on. + */ + (void) utils_set_fd_cloexec(sock); + + /* Create UST registration command for enqueuing */ + ust_cmd = (ust_command *) zmalloc(sizeof(struct ust_command)); + if (ust_cmd == NULL) { + PERROR("ust command zmalloc"); + ret = close(sock); + if (ret) { + PERROR("close"); + } + sock = -1; + goto error; + } + + /* + * Using message-based transmissions to ensure we don't + * have to deal with partially received messages. + */ + ret = lttng_fd_get(LTTNG_FD_APPS, 1); + if (ret < 0) { + ERR("Exhausted file descriptors allowed for applications."); + free(ust_cmd); + ret = close(sock); + if (ret) { + PERROR("close"); + } + sock = -1; + continue; + } + + health_code_update(); + ret = ust_app_recv_registration(sock, &ust_cmd->reg_msg); + if (ret < 0) { + free(ust_cmd); + /* Close socket of the application. */ + ret = close(sock); + if (ret) { + PERROR("close"); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + sock = -1; + continue; + } + health_code_update(); + + ust_cmd->sock = sock; + sock = -1; + + DBG("UST registration received with pid:%d ppid:%d uid:%d" + " gid:%d sock:%d name:%s (version %d.%d)", + ust_cmd->reg_msg.pid, ust_cmd->reg_msg.ppid, + ust_cmd->reg_msg.uid, ust_cmd->reg_msg.gid, + ust_cmd->sock, ust_cmd->reg_msg.name, + ust_cmd->reg_msg.major, ust_cmd->reg_msg.minor); + + /* + * Lock free enqueue the registration request. The red pill + * has been taken! This apps will be part of the *system*. + */ + cds_wfcq_head_ptr_t head; + head.h = &thread_state->ust_cmd_queue->head; + cds_wfcq_enqueue(head, + &thread_state->ust_cmd_queue->tail, + &ust_cmd->node); + + /* + * Wake the registration queue futex. Implicit memory + * barrier with the exchange in cds_wfcq_enqueue. + */ + futex_nto1_wake(&thread_state->ust_cmd_queue->futex); + } else if (revents & (LPOLLERR | LPOLLHUP | LPOLLRDHUP)) { + ERR("Register apps socket poll error"); + goto error; + } else { + ERR("Unexpected poll events %u for sock %d", revents, pollfd); + goto error; + } + } + } + } + +exit: +error: + /* Notify that the registration thread is gone */ + notify_ust_apps(0, is_root); + + ret = close(application_socket); + if (ret) { + PERROR("Failed to close application registration socket"); + } + if (sock >= 0) { + ret = close(sock); + if (ret) { + PERROR("Failed to close application socket"); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + } + unlink(the_config.apps_unix_sock_path.value); + +error_poll_add: + lttng_poll_clean(&events); +error_listen: +error_create_poll: + DBG("UST Registration thread cleanup complete"); + if (err) { + health_error(); + ERR("Health error occurred in %s", __func__); + } + health_unregister(the_health_sessiond); + return NULL; +} + +static bool shutdown_application_registration_thread(void *data) +{ + struct thread_state *thread_state = (struct thread_state *) data; + const int write_fd = lttng_pipe_get_writefd(thread_state->quit_pipe); + + return notify_thread_pipe(write_fd) == 1; +} + +struct lttng_thread *launch_application_registration_thread( + struct ust_cmd_queue *cmd_queue) +{ + int ret; + struct lttng_pipe *quit_pipe; + struct thread_state *thread_state = NULL; + struct lttng_thread *thread = NULL; + const bool is_root = (getuid() == 0); + int application_socket = -1; + + thread_state = (struct thread_state *) zmalloc(sizeof(*thread_state)); + if (!thread_state) { + goto error_alloc; + } + quit_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!quit_pipe) { + goto error; + } + thread_state->quit_pipe = quit_pipe; + thread_state->ust_cmd_queue = cmd_queue; + application_socket = create_application_socket(); + if (application_socket < 0) { + goto error; + } + thread_state->application_socket = application_socket; + sem_init(&thread_state->ready, 0, 0); + + thread = lttng_thread_create("UST application registration", + thread_application_registration, + shutdown_application_registration_thread, + cleanup_application_registration_thread, + thread_state); + if (!thread) { + goto error; + } + /* + * The application registration thread now owns the application socket + * and the global thread state. The thread state is used to wait for + * the thread's status, but its ownership now belongs to the thread. + */ + application_socket = -1; + if (!wait_thread_status(thread_state)) { + thread_state = NULL; + goto error; + } + + /* Notify all applications to register. */ + ret = notify_ust_apps(1, is_root); + if (ret < 0) { + ERR("Failed to notify applications or create the wait shared memory.\n" + "Execution continues but there might be problems for already\n" + "running applications that wishes to register."); + } + + return thread; +error: + lttng_thread_put(thread); + cleanup_application_registration_thread(thread_state); + if (application_socket >= 0) { + if (close(application_socket)) { + PERROR("Failed to close application registration socket"); + } + } +error_alloc: + return NULL; +} diff --git a/src/bin/lttng-sessiond/rotate.c b/src/bin/lttng-sessiond/rotate.c deleted file mode 100644 index cdf95f353..000000000 --- a/src/bin/lttng-sessiond/rotate.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2017 Julien Desfossez - * Copyright (C) 2018 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "session.h" -#include "rotate.h" -#include "rotation-thread.h" -#include "lttng-sessiond.h" -#include "health-sessiond.h" -#include "cmd.h" -#include "utils.h" -#include "notification-thread-commands.h" - -#include -#include -#include - -int subscribe_session_consumed_size_rotation(struct ltt_session *session, uint64_t size, - struct notification_thread_handle *notification_thread_handle) -{ - int ret; - enum lttng_condition_status condition_status; - enum lttng_notification_channel_status nc_status; - struct lttng_action *action; - const struct lttng_credentials session_creds = { - .uid = LTTNG_OPTIONAL_INIT_VALUE(session->uid), - .gid = LTTNG_OPTIONAL_INIT_VALUE(session->gid), - }; - - session->rotate_condition = lttng_condition_session_consumed_size_create(); - if (!session->rotate_condition) { - ERR("Failed to create session consumed size condition object"); - ret = -1; - goto end; - } - - condition_status = lttng_condition_session_consumed_size_set_threshold( - session->rotate_condition, size); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - ERR("Could not set session consumed size condition threshold (size = %" PRIu64 ")", - size); - ret = -1; - goto end; - } - - condition_status = - lttng_condition_session_consumed_size_set_session_name( - session->rotate_condition, session->name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - ERR("Could not set session consumed size condition session name (name = %s)", - session->name); - ret = -1; - goto end; - } - - action = lttng_action_notify_create(); - if (!action) { - ERR("Could not create notify action"); - ret = -1; - goto end; - } - - session->rotate_trigger = lttng_trigger_create(session->rotate_condition, - action); - if (!session->rotate_trigger) { - ERR("Could not create size-based rotation trigger"); - ret = -1; - goto end; - } - - /* Ensure this trigger is not visible to external users. */ - lttng_trigger_set_hidden(session->rotate_trigger); - lttng_trigger_set_credentials( - session->rotate_trigger, &session_creds); - - nc_status = lttng_notification_channel_subscribe( - rotate_notification_channel, session->rotate_condition); - if (nc_status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { - ERR("Could not subscribe to session consumed size notification"); - ret = -1; - goto end; - } - - ret = notification_thread_command_register_trigger( - notification_thread_handle, session->rotate_trigger, - true); - if (ret < 0 && ret != -LTTNG_ERR_TRIGGER_EXISTS) { - ERR("Register trigger, %s", lttng_strerror(ret)); - ret = -1; - goto end; - } - - ret = 0; - -end: - return ret; -} - -int unsubscribe_session_consumed_size_rotation(struct ltt_session *session, - struct notification_thread_handle *notification_thread_handle) -{ - int ret = 0; - enum lttng_notification_channel_status status; - - status = lttng_notification_channel_unsubscribe( - rotate_notification_channel, - session->rotate_condition); - if (status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { - ERR("Session unsubscribe error: %d", (int) status); - ret = -1; - goto end; - } - - ret = notification_thread_command_unregister_trigger( - notification_thread_handle, session->rotate_trigger); - if (ret != LTTNG_OK) { - ERR("Session unregister trigger error: %d", ret); - goto end; - } - - ret = 0; -end: - return ret; -} diff --git a/src/bin/lttng-sessiond/rotate.cpp b/src/bin/lttng-sessiond/rotate.cpp new file mode 100644 index 000000000..cdf95f353 --- /dev/null +++ b/src/bin/lttng-sessiond/rotate.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2017 Julien Desfossez + * Copyright (C) 2018 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "session.h" +#include "rotate.h" +#include "rotation-thread.h" +#include "lttng-sessiond.h" +#include "health-sessiond.h" +#include "cmd.h" +#include "utils.h" +#include "notification-thread-commands.h" + +#include +#include +#include + +int subscribe_session_consumed_size_rotation(struct ltt_session *session, uint64_t size, + struct notification_thread_handle *notification_thread_handle) +{ + int ret; + enum lttng_condition_status condition_status; + enum lttng_notification_channel_status nc_status; + struct lttng_action *action; + const struct lttng_credentials session_creds = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(session->uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(session->gid), + }; + + session->rotate_condition = lttng_condition_session_consumed_size_create(); + if (!session->rotate_condition) { + ERR("Failed to create session consumed size condition object"); + ret = -1; + goto end; + } + + condition_status = lttng_condition_session_consumed_size_set_threshold( + session->rotate_condition, size); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Could not set session consumed size condition threshold (size = %" PRIu64 ")", + size); + ret = -1; + goto end; + } + + condition_status = + lttng_condition_session_consumed_size_set_session_name( + session->rotate_condition, session->name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Could not set session consumed size condition session name (name = %s)", + session->name); + ret = -1; + goto end; + } + + action = lttng_action_notify_create(); + if (!action) { + ERR("Could not create notify action"); + ret = -1; + goto end; + } + + session->rotate_trigger = lttng_trigger_create(session->rotate_condition, + action); + if (!session->rotate_trigger) { + ERR("Could not create size-based rotation trigger"); + ret = -1; + goto end; + } + + /* Ensure this trigger is not visible to external users. */ + lttng_trigger_set_hidden(session->rotate_trigger); + lttng_trigger_set_credentials( + session->rotate_trigger, &session_creds); + + nc_status = lttng_notification_channel_subscribe( + rotate_notification_channel, session->rotate_condition); + if (nc_status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { + ERR("Could not subscribe to session consumed size notification"); + ret = -1; + goto end; + } + + ret = notification_thread_command_register_trigger( + notification_thread_handle, session->rotate_trigger, + true); + if (ret < 0 && ret != -LTTNG_ERR_TRIGGER_EXISTS) { + ERR("Register trigger, %s", lttng_strerror(ret)); + ret = -1; + goto end; + } + + ret = 0; + +end: + return ret; +} + +int unsubscribe_session_consumed_size_rotation(struct ltt_session *session, + struct notification_thread_handle *notification_thread_handle) +{ + int ret = 0; + enum lttng_notification_channel_status status; + + status = lttng_notification_channel_unsubscribe( + rotate_notification_channel, + session->rotate_condition); + if (status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { + ERR("Session unsubscribe error: %d", (int) status); + ret = -1; + goto end; + } + + ret = notification_thread_command_unregister_trigger( + notification_thread_handle, session->rotate_trigger); + if (ret != LTTNG_OK) { + ERR("Session unregister trigger error: %d", ret); + goto end; + } + + ret = 0; +end: + return ret; +} diff --git a/src/bin/lttng-sessiond/rotation-thread.c b/src/bin/lttng-sessiond/rotation-thread.c deleted file mode 100644 index 3373443f0..000000000 --- a/src/bin/lttng-sessiond/rotation-thread.c +++ /dev/null @@ -1,896 +0,0 @@ -/* - * Copyright (C) 2017 Julien Desfossez - * Copyright (C) 2018 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "rotation-thread.h" -#include "lttng-sessiond.h" -#include "health-sessiond.h" -#include "rotate.h" -#include "cmd.h" -#include "session.h" -#include "timer.h" -#include "notification-thread-commands.h" -#include "utils.h" -#include "thread.h" - -#include -#include - -struct lttng_notification_channel *rotate_notification_channel = NULL; - -struct rotation_thread { - struct lttng_poll_event events; -}; - -struct rotation_thread_job { - enum rotation_thread_job_type type; - struct ltt_session *session; - /* List member in struct rotation_thread_timer_queue. */ - struct cds_list_head head; -}; - -/* - * The timer thread enqueues jobs and wakes up the rotation thread. - * When the rotation thread wakes up, it empties the queue. - */ -struct rotation_thread_timer_queue { - struct lttng_pipe *event_pipe; - struct cds_list_head list; - pthread_mutex_t lock; -}; - -struct rotation_thread_handle { - struct rotation_thread_timer_queue *rotation_timer_queue; - /* Access to the notification thread cmd_queue */ - struct notification_thread_handle *notification_thread_handle; - /* Thread-specific quit pipe. */ - struct lttng_pipe *quit_pipe; -}; - -static -const char *get_job_type_str(enum rotation_thread_job_type job_type) -{ - switch (job_type) { - case ROTATION_THREAD_JOB_TYPE_CHECK_PENDING_ROTATION: - return "CHECK_PENDING_ROTATION"; - case ROTATION_THREAD_JOB_TYPE_SCHEDULED_ROTATION: - return "SCHEDULED_ROTATION"; - default: - abort(); - } -} - -struct rotation_thread_timer_queue *rotation_thread_timer_queue_create(void) -{ - struct rotation_thread_timer_queue *queue = NULL; - - queue = zmalloc(sizeof(*queue)); - if (!queue) { - PERROR("Failed to allocate timer rotate queue"); - goto end; - } - - queue->event_pipe = lttng_pipe_open(FD_CLOEXEC | O_NONBLOCK); - CDS_INIT_LIST_HEAD(&queue->list); - pthread_mutex_init(&queue->lock, NULL); -end: - return queue; -} - -void rotation_thread_timer_queue_destroy( - struct rotation_thread_timer_queue *queue) -{ - if (!queue) { - return; - } - - lttng_pipe_destroy(queue->event_pipe); - - pthread_mutex_lock(&queue->lock); - LTTNG_ASSERT(cds_list_empty(&queue->list)); - pthread_mutex_unlock(&queue->lock); - pthread_mutex_destroy(&queue->lock); - free(queue); -} - -/* - * Destroy the thread data previously created by the init function. - */ -void rotation_thread_handle_destroy( - struct rotation_thread_handle *handle) -{ - lttng_pipe_destroy(handle->quit_pipe); - free(handle); -} - -struct rotation_thread_handle *rotation_thread_handle_create( - struct rotation_thread_timer_queue *rotation_timer_queue, - struct notification_thread_handle *notification_thread_handle) -{ - struct rotation_thread_handle *handle; - - handle = zmalloc(sizeof(*handle)); - if (!handle) { - goto end; - } - - handle->rotation_timer_queue = rotation_timer_queue; - handle->notification_thread_handle = notification_thread_handle; - handle->quit_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!handle->quit_pipe) { - goto error; - } - -end: - return handle; -error: - rotation_thread_handle_destroy(handle); - return NULL; -} - -/* - * Called with the rotation_thread_timer_queue lock held. - * Return true if the same timer job already exists in the queue, false if not. - */ -static -bool timer_job_exists(const struct rotation_thread_timer_queue *queue, - enum rotation_thread_job_type job_type, - struct ltt_session *session) -{ - bool exists = false; - struct rotation_thread_job *job; - - cds_list_for_each_entry(job, &queue->list, head) { - if (job->session == session && job->type == job_type) { - exists = true; - goto end; - } - } -end: - return exists; -} - -void rotation_thread_enqueue_job(struct rotation_thread_timer_queue *queue, - enum rotation_thread_job_type job_type, - struct ltt_session *session) -{ - int ret; - const char dummy = '!'; - struct rotation_thread_job *job = NULL; - const char *job_type_str = get_job_type_str(job_type); - - pthread_mutex_lock(&queue->lock); - if (timer_job_exists(queue, job_type, session)) { - /* - * This timer job is already pending, we don't need to add - * it. - */ - goto end; - } - - job = zmalloc(sizeof(struct rotation_thread_job)); - if (!job) { - PERROR("Failed to allocate rotation thread job of type \"%s\" for session \"%s\"", - job_type_str, session->name); - goto end; - } - /* No reason for this to fail as the caller must hold a reference. */ - (void) session_get(session); - - job->session = session; - job->type = job_type; - cds_list_add_tail(&job->head, &queue->list); - - ret = lttng_write(lttng_pipe_get_writefd(queue->event_pipe), &dummy, - sizeof(dummy)); - if (ret < 0) { - /* - * We do not want to block in the timer handler, the job has - * been enqueued in the list, the wakeup pipe is probably full, - * the job will be processed when the rotation_thread catches - * up. - */ - if (errno == EAGAIN || errno == EWOULDBLOCK) { - /* - * Not an error, but would be surprising and indicate - * that the rotation thread can't keep up with the - * current load. - */ - DBG("Wake-up pipe of rotation thread job queue is full"); - goto end; - } - PERROR("Failed to wake-up the rotation thread after pushing a job of type \"%s\" for session \"%s\"", - job_type_str, session->name); - goto end; - } - -end: - pthread_mutex_unlock(&queue->lock); -} - -static -int init_poll_set(struct lttng_poll_event *poll_set, - struct rotation_thread_handle *handle) -{ - int ret; - - /* - * Create pollset with size 3: - * - rotation thread quit pipe, - * - rotation thread timer queue pipe, - * - notification channel sock, - */ - ret = lttng_poll_create(poll_set, 5, LTTNG_CLOEXEC); - if (ret < 0) { - goto error; - } - - ret = lttng_poll_add(poll_set, - lttng_pipe_get_readfd(handle->quit_pipe), - LPOLLIN | LPOLLERR); - if (ret < 0) { - ERR("Failed to add quit pipe read fd to poll set"); - goto error; - } - - ret = lttng_poll_add(poll_set, - lttng_pipe_get_readfd(handle->rotation_timer_queue->event_pipe), - LPOLLIN | LPOLLERR); - if (ret < 0) { - ERR("Failed to add rotate_pending fd to poll set"); - goto error; - } - - return ret; -error: - lttng_poll_clean(poll_set); - return ret; -} - -static -void fini_thread_state(struct rotation_thread *state) -{ - lttng_poll_clean(&state->events); - if (rotate_notification_channel) { - lttng_notification_channel_destroy(rotate_notification_channel); - } -} - -static -int init_thread_state(struct rotation_thread_handle *handle, - struct rotation_thread *state) -{ - int ret; - - memset(state, 0, sizeof(*state)); - lttng_poll_init(&state->events); - - ret = init_poll_set(&state->events, handle); - if (ret) { - ERR("Failed to initialize rotation thread poll set"); - goto end; - } - - rotate_notification_channel = lttng_notification_channel_create( - lttng_session_daemon_notification_endpoint); - if (!rotate_notification_channel) { - ERR("Could not create notification channel"); - ret = -1; - goto end; - } - ret = lttng_poll_add(&state->events, rotate_notification_channel->socket, - LPOLLIN | LPOLLERR); - if (ret < 0) { - ERR("Failed to add notification fd to pollset"); - goto end; - } - -end: - return ret; -} - -static -void check_session_rotation_pending_on_consumers(struct ltt_session *session, - bool *_rotation_completed) -{ - int ret = 0; - struct consumer_socket *socket; - struct cds_lfht_iter iter; - enum consumer_trace_chunk_exists_status exists_status; - uint64_t relayd_id; - bool chunk_exists_on_peer = false; - enum lttng_trace_chunk_status chunk_status; - - LTTNG_ASSERT(session->chunk_being_archived); - - /* - * Check for a local pending rotation on all consumers (32-bit - * user space, 64-bit user space, and kernel). - */ - rcu_read_lock(); - if (!session->ust_session) { - goto skip_ust; - } - cds_lfht_for_each_entry(session->ust_session->consumer->socks->ht, - &iter, socket, node.node) { - relayd_id = session->ust_session->consumer->type == CONSUMER_DST_LOCAL ? - -1ULL : - session->ust_session->consumer->net_seq_index; - - pthread_mutex_lock(socket->lock); - ret = consumer_trace_chunk_exists(socket, - relayd_id, - session->id, session->chunk_being_archived, - &exists_status); - if (ret) { - pthread_mutex_unlock(socket->lock); - ERR("Error occurred while checking rotation status on consumer daemon"); - goto end; - } - - if (exists_status != CONSUMER_TRACE_CHUNK_EXISTS_STATUS_UNKNOWN_CHUNK) { - pthread_mutex_unlock(socket->lock); - chunk_exists_on_peer = true; - goto end; - } - pthread_mutex_unlock(socket->lock); - } - -skip_ust: - if (!session->kernel_session) { - goto skip_kernel; - } - cds_lfht_for_each_entry(session->kernel_session->consumer->socks->ht, - &iter, socket, node.node) { - pthread_mutex_lock(socket->lock); - relayd_id = session->kernel_session->consumer->type == CONSUMER_DST_LOCAL ? - -1ULL : - session->kernel_session->consumer->net_seq_index; - - ret = consumer_trace_chunk_exists(socket, - relayd_id, - session->id, session->chunk_being_archived, - &exists_status); - if (ret) { - pthread_mutex_unlock(socket->lock); - ERR("Error occurred while checking rotation status on consumer daemon"); - goto end; - } - - if (exists_status != CONSUMER_TRACE_CHUNK_EXISTS_STATUS_UNKNOWN_CHUNK) { - pthread_mutex_unlock(socket->lock); - chunk_exists_on_peer = true; - goto end; - } - pthread_mutex_unlock(socket->lock); - } -skip_kernel: -end: - rcu_read_unlock(); - - if (!chunk_exists_on_peer) { - uint64_t chunk_being_archived_id; - - chunk_status = lttng_trace_chunk_get_id( - session->chunk_being_archived, - &chunk_being_archived_id); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - DBG("Rotation of trace archive %" PRIu64 " of session \"%s\" is complete on all consumers", - chunk_being_archived_id, - session->name); - } - *_rotation_completed = !chunk_exists_on_peer; - if (ret) { - ret = session_reset_rotation_state(session, - LTTNG_ROTATION_STATE_ERROR); - if (ret) { - ERR("Failed to reset rotation state of session \"%s\"", - session->name); - } - } -} - -/* - * Check if the last rotation was completed, called with session lock held. - * Should only return non-zero in the event of a fatal error. Doing so will - * shutdown the thread. - */ -static -int check_session_rotation_pending(struct ltt_session *session, - struct notification_thread_handle *notification_thread_handle) -{ - int ret; - struct lttng_trace_archive_location *location; - enum lttng_trace_chunk_status chunk_status; - bool rotation_completed = false; - const char *archived_chunk_name; - uint64_t chunk_being_archived_id; - - if (!session->chunk_being_archived) { - ret = 0; - goto end; - } - - chunk_status = lttng_trace_chunk_get_id(session->chunk_being_archived, - &chunk_being_archived_id); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - - DBG("Checking for pending rotation on session \"%s\", trace archive %" PRIu64, - session->name, chunk_being_archived_id); - - /* - * The rotation-pending check timer of a session is launched in - * one-shot mode. If the rotation is incomplete, the rotation - * thread will re-enable the pending-check timer. - * - * The timer thread can't stop the timer itself since it is involved - * in the check for the timer's quiescence. - */ - ret = timer_session_rotation_pending_check_stop(session); - if (ret) { - goto check_ongoing_rotation; - } - - check_session_rotation_pending_on_consumers(session, - &rotation_completed); - if (!rotation_completed || - session->rotation_state == LTTNG_ROTATION_STATE_ERROR) { - goto check_ongoing_rotation; - } - - /* - * Now we can clear the "ONGOING" state in the session. New - * rotations can start now. - */ - chunk_status = lttng_trace_chunk_get_name(session->chunk_being_archived, - &archived_chunk_name, NULL); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - free(session->last_archived_chunk_name); - session->last_archived_chunk_name = strdup(archived_chunk_name); - if (!session->last_archived_chunk_name) { - PERROR("Failed to duplicate archived chunk name"); - } - session_reset_rotation_state(session, LTTNG_ROTATION_STATE_COMPLETED); - - if (!session->quiet_rotation) { - location = session_get_trace_archive_location(session); - ret = notification_thread_command_session_rotation_completed( - notification_thread_handle, - session->name, - session->uid, - session->gid, - session->last_archived_chunk_id.value, - location); - lttng_trace_archive_location_put(location); - if (ret != LTTNG_OK) { - ERR("Failed to notify notification thread of completed rotation for session %s", - session->name); - } - } - - ret = 0; -check_ongoing_rotation: - if (session->rotation_state == LTTNG_ROTATION_STATE_ONGOING) { - chunk_status = lttng_trace_chunk_get_id( - session->chunk_being_archived, - &chunk_being_archived_id); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - - DBG("Rotation of trace archive %" PRIu64 " is still pending for session %s", - chunk_being_archived_id, session->name); - ret = timer_session_rotation_pending_check_start(session, - DEFAULT_ROTATE_PENDING_TIMER); - if (ret) { - ERR("Failed to re-enable rotation pending timer"); - ret = -1; - goto end; - } - } - -end: - return ret; -} - -/* Call with the session and session_list locks held. */ -static -int launch_session_rotation(struct ltt_session *session) -{ - int ret; - struct lttng_rotate_session_return rotation_return; - - DBG("Launching scheduled time-based rotation on session \"%s\"", - session->name); - - ret = cmd_rotate_session(session, &rotation_return, false, - LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); - if (ret == LTTNG_OK) { - DBG("Scheduled time-based rotation successfully launched on session \"%s\"", - session->name); - } else { - /* Don't consider errors as fatal. */ - DBG("Scheduled time-based rotation aborted for session %s: %s", - session->name, lttng_strerror(ret)); - } - return 0; -} - -static -int run_job(struct rotation_thread_job *job, struct ltt_session *session, - struct notification_thread_handle *notification_thread_handle) -{ - int ret; - - switch (job->type) { - case ROTATION_THREAD_JOB_TYPE_SCHEDULED_ROTATION: - ret = launch_session_rotation(session); - break; - case ROTATION_THREAD_JOB_TYPE_CHECK_PENDING_ROTATION: - ret = check_session_rotation_pending(session, - notification_thread_handle); - break; - default: - abort(); - } - return ret; -} - -static -int handle_job_queue(struct rotation_thread_handle *handle, - struct rotation_thread *state, - struct rotation_thread_timer_queue *queue) -{ - int ret = 0; - - for (;;) { - struct ltt_session *session; - struct rotation_thread_job *job; - - /* Take the queue lock only to pop an element from the list. */ - pthread_mutex_lock(&queue->lock); - if (cds_list_empty(&queue->list)) { - pthread_mutex_unlock(&queue->lock); - break; - } - job = cds_list_first_entry(&queue->list, - typeof(*job), head); - cds_list_del(&job->head); - pthread_mutex_unlock(&queue->lock); - - session_lock_list(); - session = job->session; - if (!session) { - DBG("Session \"%s\" not found", - session->name); - /* - * This is a non-fatal error, and we cannot report it to - * the user (timer), so just print the error and - * continue the processing. - * - * While the timer thread will purge pending signals for - * a session on the session's destruction, it is - * possible for a job targeting that session to have - * already been queued before it was destroyed. - */ - free(job); - session_put(session); - session_unlock_list(); - continue; - } - - session_lock(session); - ret = run_job(job, session, handle->notification_thread_handle); - session_unlock(session); - /* Release reference held by the job. */ - session_put(session); - session_unlock_list(); - free(job); - if (ret) { - goto end; - } - } - - ret = 0; - -end: - return ret; -} - -static -int handle_condition(const struct lttng_condition *condition, - const struct lttng_evaluation *evaluation, - struct notification_thread_handle *notification_thread_handle) -{ - int ret = 0; - const char *condition_session_name = NULL; - enum lttng_condition_type condition_type; - enum lttng_condition_status condition_status; - enum lttng_evaluation_status evaluation_status; - uint64_t consumed; - struct ltt_session *session; - - condition_type = lttng_condition_get_type(condition); - - if (condition_type != LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE) { - ret = -1; - ERR("Condition type and session usage type are not the same"); - goto end; - } - - /* Fetch info to test */ - condition_status = lttng_condition_session_consumed_size_get_session_name( - condition, &condition_session_name); - if (condition_status != LTTNG_CONDITION_STATUS_OK) { - ERR("Session name could not be fetched"); - ret = -1; - goto end; - } - evaluation_status = lttng_evaluation_session_consumed_size_get_consumed_size(evaluation, - &consumed); - if (evaluation_status != LTTNG_EVALUATION_STATUS_OK) { - ERR("Failed to get evaluation"); - ret = -1; - goto end; - } - - session_lock_list(); - session = session_find_by_name(condition_session_name); - if (!session) { - ret = -1; - session_unlock_list(); - ERR("Session \"%s\" not found", - condition_session_name); - goto end; - } - session_lock(session); - - ret = unsubscribe_session_consumed_size_rotation(session, - notification_thread_handle); - if (ret) { - goto end_unlock; - } - - ret = cmd_rotate_session(session, NULL, false, - LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); - if (ret == -LTTNG_ERR_ROTATION_PENDING) { - DBG("Rotate already pending, subscribe to the next threshold value"); - } else if (ret != LTTNG_OK) { - ERR("Failed to rotate on size notification with error: %s", - lttng_strerror(ret)); - ret = -1; - goto end_unlock; - } - ret = subscribe_session_consumed_size_rotation(session, - consumed + session->rotate_size, - notification_thread_handle); - if (ret) { - ERR("Failed to subscribe to session consumed size condition"); - goto end_unlock; - } - ret = 0; - -end_unlock: - session_unlock(session); - session_put(session); - session_unlock_list(); -end: - return ret; -} - -static -int handle_notification_channel(int fd, - struct rotation_thread_handle *handle, - struct rotation_thread *state) -{ - int ret; - bool notification_pending; - struct lttng_notification *notification = NULL; - enum lttng_notification_channel_status status; - const struct lttng_evaluation *notification_evaluation; - const struct lttng_condition *notification_condition; - - status = lttng_notification_channel_has_pending_notification( - rotate_notification_channel, ¬ification_pending); - if (status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { - ERR("Error occurred while checking for pending notification"); - ret = -1; - goto end; - } - - if (!notification_pending) { - ret = 0; - goto end; - } - - /* Receive the next notification. */ - status = lttng_notification_channel_get_next_notification( - rotate_notification_channel, - ¬ification); - - switch (status) { - case LTTNG_NOTIFICATION_CHANNEL_STATUS_OK: - break; - case LTTNG_NOTIFICATION_CHANNEL_STATUS_NOTIFICATIONS_DROPPED: - /* Not an error, we will wait for the next one */ - ret = 0; - goto end;; - case LTTNG_NOTIFICATION_CHANNEL_STATUS_CLOSED: - ERR("Notification channel was closed"); - ret = -1; - goto end; - default: - /* Unhandled conditions / errors. */ - ERR("Unknown notification channel status"); - ret = -1; - goto end; - } - - notification_condition = lttng_notification_get_condition(notification); - notification_evaluation = lttng_notification_get_evaluation(notification); - - ret = handle_condition(notification_condition, notification_evaluation, - handle->notification_thread_handle); - -end: - lttng_notification_destroy(notification); - return ret; -} - -static -void *thread_rotation(void *data) -{ - int ret; - struct rotation_thread_handle *handle = data; - struct rotation_thread thread; - int queue_pipe_fd; - - DBG("Started rotation thread"); - rcu_register_thread(); - rcu_thread_online(); - health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_ROTATION); - health_code_update(); - - if (!handle) { - ERR("Invalid thread context provided"); - goto end; - } - - queue_pipe_fd = lttng_pipe_get_readfd( - handle->rotation_timer_queue->event_pipe); - - - ret = init_thread_state(handle, &thread); - if (ret) { - goto error; - } - - while (true) { - int fd_count, i; - - health_poll_entry(); - DBG("Entering poll wait"); - ret = lttng_poll_wait(&thread.events, -1); - DBG("Poll wait returned (%i)", ret); - health_poll_exit(); - if (ret < 0) { - /* - * Restart interrupted system call. - */ - if (errno == EINTR) { - continue; - } - ERR("Error encountered during lttng_poll_wait (%i)", ret); - goto error; - } - - fd_count = ret; - for (i = 0; i < fd_count; i++) { - int fd = LTTNG_POLL_GETFD(&thread.events, i); - uint32_t revents = LTTNG_POLL_GETEV(&thread.events, i); - - DBG("Handling fd (%i) activity (%u)", - fd, revents); - - if (revents & LPOLLERR) { - ERR("Polling returned an error on fd %i", fd); - goto error; - } - - if (fd == rotate_notification_channel->socket) { - ret = handle_notification_channel(fd, handle, - &thread); - if (ret) { - ERR("Error occurred while handling activity on notification channel socket"); - goto error; - } - } else { - /* Job queue or quit pipe activity. */ - - /* - * The job queue is serviced if there is - * activity on the quit pipe to ensure it is - * flushed and all references held in the queue - * are released. - */ - ret = handle_job_queue(handle, &thread, - handle->rotation_timer_queue); - if (ret) { - ERR("Failed to handle rotation timer pipe event"); - goto error; - } - - if (fd == queue_pipe_fd) { - char buf; - - ret = lttng_read(fd, &buf, 1); - if (ret != 1) { - ERR("Failed to read from wakeup pipe (fd = %i)", fd); - goto error; - } - } else { - DBG("Quit pipe activity"); - goto exit; - } - } - } - } -exit: -error: - DBG("Thread exit"); - fini_thread_state(&thread); -end: - health_unregister(the_health_sessiond); - rcu_thread_offline(); - rcu_unregister_thread(); - return NULL; -} - -static -bool shutdown_rotation_thread(void *thread_data) -{ - struct rotation_thread_handle *handle = thread_data; - const int write_fd = lttng_pipe_get_writefd(handle->quit_pipe); - - return notify_thread_pipe(write_fd) == 1; -} - -bool launch_rotation_thread(struct rotation_thread_handle *handle) -{ - struct lttng_thread *thread; - - thread = lttng_thread_create("Rotation", - thread_rotation, - shutdown_rotation_thread, - NULL, - handle); - if (!thread) { - goto error; - } - lttng_thread_put(thread); - return true; -error: - return false; -} diff --git a/src/bin/lttng-sessiond/rotation-thread.cpp b/src/bin/lttng-sessiond/rotation-thread.cpp new file mode 100644 index 000000000..8754d48a1 --- /dev/null +++ b/src/bin/lttng-sessiond/rotation-thread.cpp @@ -0,0 +1,896 @@ +/* + * Copyright (C) 2017 Julien Desfossez + * Copyright (C) 2018 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "rotation-thread.h" +#include "lttng-sessiond.h" +#include "health-sessiond.h" +#include "rotate.h" +#include "cmd.h" +#include "session.h" +#include "timer.h" +#include "notification-thread-commands.h" +#include "utils.h" +#include "thread.h" + +#include +#include + +struct lttng_notification_channel *rotate_notification_channel = NULL; + +struct rotation_thread { + struct lttng_poll_event events; +}; + +struct rotation_thread_job { + enum rotation_thread_job_type type; + struct ltt_session *session; + /* List member in struct rotation_thread_timer_queue. */ + struct cds_list_head head; +}; + +/* + * The timer thread enqueues jobs and wakes up the rotation thread. + * When the rotation thread wakes up, it empties the queue. + */ +struct rotation_thread_timer_queue { + struct lttng_pipe *event_pipe; + struct cds_list_head list; + pthread_mutex_t lock; +}; + +struct rotation_thread_handle { + struct rotation_thread_timer_queue *rotation_timer_queue; + /* Access to the notification thread cmd_queue */ + struct notification_thread_handle *notification_thread_handle; + /* Thread-specific quit pipe. */ + struct lttng_pipe *quit_pipe; +}; + +static +const char *get_job_type_str(enum rotation_thread_job_type job_type) +{ + switch (job_type) { + case ROTATION_THREAD_JOB_TYPE_CHECK_PENDING_ROTATION: + return "CHECK_PENDING_ROTATION"; + case ROTATION_THREAD_JOB_TYPE_SCHEDULED_ROTATION: + return "SCHEDULED_ROTATION"; + default: + abort(); + } +} + +struct rotation_thread_timer_queue *rotation_thread_timer_queue_create(void) +{ + struct rotation_thread_timer_queue *queue = NULL; + + queue = (rotation_thread_timer_queue *) zmalloc(sizeof(*queue)); + if (!queue) { + PERROR("Failed to allocate timer rotate queue"); + goto end; + } + + queue->event_pipe = lttng_pipe_open(FD_CLOEXEC | O_NONBLOCK); + CDS_INIT_LIST_HEAD(&queue->list); + pthread_mutex_init(&queue->lock, NULL); +end: + return queue; +} + +void rotation_thread_timer_queue_destroy( + struct rotation_thread_timer_queue *queue) +{ + if (!queue) { + return; + } + + lttng_pipe_destroy(queue->event_pipe); + + pthread_mutex_lock(&queue->lock); + LTTNG_ASSERT(cds_list_empty(&queue->list)); + pthread_mutex_unlock(&queue->lock); + pthread_mutex_destroy(&queue->lock); + free(queue); +} + +/* + * Destroy the thread data previously created by the init function. + */ +void rotation_thread_handle_destroy( + struct rotation_thread_handle *handle) +{ + lttng_pipe_destroy(handle->quit_pipe); + free(handle); +} + +struct rotation_thread_handle *rotation_thread_handle_create( + struct rotation_thread_timer_queue *rotation_timer_queue, + struct notification_thread_handle *notification_thread_handle) +{ + struct rotation_thread_handle *handle; + + handle = (rotation_thread_handle *) zmalloc(sizeof(*handle)); + if (!handle) { + goto end; + } + + handle->rotation_timer_queue = rotation_timer_queue; + handle->notification_thread_handle = notification_thread_handle; + handle->quit_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!handle->quit_pipe) { + goto error; + } + +end: + return handle; +error: + rotation_thread_handle_destroy(handle); + return NULL; +} + +/* + * Called with the rotation_thread_timer_queue lock held. + * Return true if the same timer job already exists in the queue, false if not. + */ +static +bool timer_job_exists(const struct rotation_thread_timer_queue *queue, + enum rotation_thread_job_type job_type, + struct ltt_session *session) +{ + bool exists = false; + struct rotation_thread_job *job; + + cds_list_for_each_entry(job, &queue->list, head) { + if (job->session == session && job->type == job_type) { + exists = true; + goto end; + } + } +end: + return exists; +} + +void rotation_thread_enqueue_job(struct rotation_thread_timer_queue *queue, + enum rotation_thread_job_type job_type, + struct ltt_session *session) +{ + int ret; + const char dummy = '!'; + struct rotation_thread_job *job = NULL; + const char *job_type_str = get_job_type_str(job_type); + + pthread_mutex_lock(&queue->lock); + if (timer_job_exists(queue, job_type, session)) { + /* + * This timer job is already pending, we don't need to add + * it. + */ + goto end; + } + + job = (rotation_thread_job *) zmalloc(sizeof(struct rotation_thread_job)); + if (!job) { + PERROR("Failed to allocate rotation thread job of type \"%s\" for session \"%s\"", + job_type_str, session->name); + goto end; + } + /* No reason for this to fail as the caller must hold a reference. */ + (void) session_get(session); + + job->session = session; + job->type = job_type; + cds_list_add_tail(&job->head, &queue->list); + + ret = lttng_write(lttng_pipe_get_writefd(queue->event_pipe), &dummy, + sizeof(dummy)); + if (ret < 0) { + /* + * We do not want to block in the timer handler, the job has + * been enqueued in the list, the wakeup pipe is probably full, + * the job will be processed when the rotation_thread catches + * up. + */ + if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* + * Not an error, but would be surprising and indicate + * that the rotation thread can't keep up with the + * current load. + */ + DBG("Wake-up pipe of rotation thread job queue is full"); + goto end; + } + PERROR("Failed to wake-up the rotation thread after pushing a job of type \"%s\" for session \"%s\"", + job_type_str, session->name); + goto end; + } + +end: + pthread_mutex_unlock(&queue->lock); +} + +static +int init_poll_set(struct lttng_poll_event *poll_set, + struct rotation_thread_handle *handle) +{ + int ret; + + /* + * Create pollset with size 3: + * - rotation thread quit pipe, + * - rotation thread timer queue pipe, + * - notification channel sock, + */ + ret = lttng_poll_create(poll_set, 5, LTTNG_CLOEXEC); + if (ret < 0) { + goto error; + } + + ret = lttng_poll_add(poll_set, + lttng_pipe_get_readfd(handle->quit_pipe), + LPOLLIN | LPOLLERR); + if (ret < 0) { + ERR("Failed to add quit pipe read fd to poll set"); + goto error; + } + + ret = lttng_poll_add(poll_set, + lttng_pipe_get_readfd(handle->rotation_timer_queue->event_pipe), + LPOLLIN | LPOLLERR); + if (ret < 0) { + ERR("Failed to add rotate_pending fd to poll set"); + goto error; + } + + return ret; +error: + lttng_poll_clean(poll_set); + return ret; +} + +static +void fini_thread_state(struct rotation_thread *state) +{ + lttng_poll_clean(&state->events); + if (rotate_notification_channel) { + lttng_notification_channel_destroy(rotate_notification_channel); + } +} + +static +int init_thread_state(struct rotation_thread_handle *handle, + struct rotation_thread *state) +{ + int ret; + + memset(state, 0, sizeof(*state)); + lttng_poll_init(&state->events); + + ret = init_poll_set(&state->events, handle); + if (ret) { + ERR("Failed to initialize rotation thread poll set"); + goto end; + } + + rotate_notification_channel = lttng_notification_channel_create( + lttng_session_daemon_notification_endpoint); + if (!rotate_notification_channel) { + ERR("Could not create notification channel"); + ret = -1; + goto end; + } + ret = lttng_poll_add(&state->events, rotate_notification_channel->socket, + LPOLLIN | LPOLLERR); + if (ret < 0) { + ERR("Failed to add notification fd to pollset"); + goto end; + } + +end: + return ret; +} + +static +void check_session_rotation_pending_on_consumers(struct ltt_session *session, + bool *_rotation_completed) +{ + int ret = 0; + struct consumer_socket *socket; + struct cds_lfht_iter iter; + enum consumer_trace_chunk_exists_status exists_status; + uint64_t relayd_id; + bool chunk_exists_on_peer = false; + enum lttng_trace_chunk_status chunk_status; + + LTTNG_ASSERT(session->chunk_being_archived); + + /* + * Check for a local pending rotation on all consumers (32-bit + * user space, 64-bit user space, and kernel). + */ + rcu_read_lock(); + if (!session->ust_session) { + goto skip_ust; + } + cds_lfht_for_each_entry(session->ust_session->consumer->socks->ht, + &iter, socket, node.node) { + relayd_id = session->ust_session->consumer->type == CONSUMER_DST_LOCAL ? + -1ULL : + session->ust_session->consumer->net_seq_index; + + pthread_mutex_lock(socket->lock); + ret = consumer_trace_chunk_exists(socket, + relayd_id, + session->id, session->chunk_being_archived, + &exists_status); + if (ret) { + pthread_mutex_unlock(socket->lock); + ERR("Error occurred while checking rotation status on consumer daemon"); + goto end; + } + + if (exists_status != CONSUMER_TRACE_CHUNK_EXISTS_STATUS_UNKNOWN_CHUNK) { + pthread_mutex_unlock(socket->lock); + chunk_exists_on_peer = true; + goto end; + } + pthread_mutex_unlock(socket->lock); + } + +skip_ust: + if (!session->kernel_session) { + goto skip_kernel; + } + cds_lfht_for_each_entry(session->kernel_session->consumer->socks->ht, + &iter, socket, node.node) { + pthread_mutex_lock(socket->lock); + relayd_id = session->kernel_session->consumer->type == CONSUMER_DST_LOCAL ? + -1ULL : + session->kernel_session->consumer->net_seq_index; + + ret = consumer_trace_chunk_exists(socket, + relayd_id, + session->id, session->chunk_being_archived, + &exists_status); + if (ret) { + pthread_mutex_unlock(socket->lock); + ERR("Error occurred while checking rotation status on consumer daemon"); + goto end; + } + + if (exists_status != CONSUMER_TRACE_CHUNK_EXISTS_STATUS_UNKNOWN_CHUNK) { + pthread_mutex_unlock(socket->lock); + chunk_exists_on_peer = true; + goto end; + } + pthread_mutex_unlock(socket->lock); + } +skip_kernel: +end: + rcu_read_unlock(); + + if (!chunk_exists_on_peer) { + uint64_t chunk_being_archived_id; + + chunk_status = lttng_trace_chunk_get_id( + session->chunk_being_archived, + &chunk_being_archived_id); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + DBG("Rotation of trace archive %" PRIu64 " of session \"%s\" is complete on all consumers", + chunk_being_archived_id, + session->name); + } + *_rotation_completed = !chunk_exists_on_peer; + if (ret) { + ret = session_reset_rotation_state(session, + LTTNG_ROTATION_STATE_ERROR); + if (ret) { + ERR("Failed to reset rotation state of session \"%s\"", + session->name); + } + } +} + +/* + * Check if the last rotation was completed, called with session lock held. + * Should only return non-zero in the event of a fatal error. Doing so will + * shutdown the thread. + */ +static +int check_session_rotation_pending(struct ltt_session *session, + struct notification_thread_handle *notification_thread_handle) +{ + int ret; + struct lttng_trace_archive_location *location; + enum lttng_trace_chunk_status chunk_status; + bool rotation_completed = false; + const char *archived_chunk_name; + uint64_t chunk_being_archived_id; + + if (!session->chunk_being_archived) { + ret = 0; + goto end; + } + + chunk_status = lttng_trace_chunk_get_id(session->chunk_being_archived, + &chunk_being_archived_id); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + + DBG("Checking for pending rotation on session \"%s\", trace archive %" PRIu64, + session->name, chunk_being_archived_id); + + /* + * The rotation-pending check timer of a session is launched in + * one-shot mode. If the rotation is incomplete, the rotation + * thread will re-enable the pending-check timer. + * + * The timer thread can't stop the timer itself since it is involved + * in the check for the timer's quiescence. + */ + ret = timer_session_rotation_pending_check_stop(session); + if (ret) { + goto check_ongoing_rotation; + } + + check_session_rotation_pending_on_consumers(session, + &rotation_completed); + if (!rotation_completed || + session->rotation_state == LTTNG_ROTATION_STATE_ERROR) { + goto check_ongoing_rotation; + } + + /* + * Now we can clear the "ONGOING" state in the session. New + * rotations can start now. + */ + chunk_status = lttng_trace_chunk_get_name(session->chunk_being_archived, + &archived_chunk_name, NULL); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + free(session->last_archived_chunk_name); + session->last_archived_chunk_name = strdup(archived_chunk_name); + if (!session->last_archived_chunk_name) { + PERROR("Failed to duplicate archived chunk name"); + } + session_reset_rotation_state(session, LTTNG_ROTATION_STATE_COMPLETED); + + if (!session->quiet_rotation) { + location = session_get_trace_archive_location(session); + ret = notification_thread_command_session_rotation_completed( + notification_thread_handle, + session->name, + session->uid, + session->gid, + session->last_archived_chunk_id.value, + location); + lttng_trace_archive_location_put(location); + if (ret != LTTNG_OK) { + ERR("Failed to notify notification thread of completed rotation for session %s", + session->name); + } + } + + ret = 0; +check_ongoing_rotation: + if (session->rotation_state == LTTNG_ROTATION_STATE_ONGOING) { + chunk_status = lttng_trace_chunk_get_id( + session->chunk_being_archived, + &chunk_being_archived_id); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + + DBG("Rotation of trace archive %" PRIu64 " is still pending for session %s", + chunk_being_archived_id, session->name); + ret = timer_session_rotation_pending_check_start(session, + DEFAULT_ROTATE_PENDING_TIMER); + if (ret) { + ERR("Failed to re-enable rotation pending timer"); + ret = -1; + goto end; + } + } + +end: + return ret; +} + +/* Call with the session and session_list locks held. */ +static +int launch_session_rotation(struct ltt_session *session) +{ + int ret; + struct lttng_rotate_session_return rotation_return; + + DBG("Launching scheduled time-based rotation on session \"%s\"", + session->name); + + ret = cmd_rotate_session(session, &rotation_return, false, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); + if (ret == LTTNG_OK) { + DBG("Scheduled time-based rotation successfully launched on session \"%s\"", + session->name); + } else { + /* Don't consider errors as fatal. */ + DBG("Scheduled time-based rotation aborted for session %s: %s", + session->name, lttng_strerror(ret)); + } + return 0; +} + +static +int run_job(struct rotation_thread_job *job, struct ltt_session *session, + struct notification_thread_handle *notification_thread_handle) +{ + int ret; + + switch (job->type) { + case ROTATION_THREAD_JOB_TYPE_SCHEDULED_ROTATION: + ret = launch_session_rotation(session); + break; + case ROTATION_THREAD_JOB_TYPE_CHECK_PENDING_ROTATION: + ret = check_session_rotation_pending(session, + notification_thread_handle); + break; + default: + abort(); + } + return ret; +} + +static +int handle_job_queue(struct rotation_thread_handle *handle, + struct rotation_thread *state, + struct rotation_thread_timer_queue *queue) +{ + int ret = 0; + + for (;;) { + struct ltt_session *session; + struct rotation_thread_job *job; + + /* Take the queue lock only to pop an element from the list. */ + pthread_mutex_lock(&queue->lock); + if (cds_list_empty(&queue->list)) { + pthread_mutex_unlock(&queue->lock); + break; + } + job = cds_list_first_entry(&queue->list, + typeof(*job), head); + cds_list_del(&job->head); + pthread_mutex_unlock(&queue->lock); + + session_lock_list(); + session = job->session; + if (!session) { + DBG("Session \"%s\" not found", + session->name); + /* + * This is a non-fatal error, and we cannot report it to + * the user (timer), so just print the error and + * continue the processing. + * + * While the timer thread will purge pending signals for + * a session on the session's destruction, it is + * possible for a job targeting that session to have + * already been queued before it was destroyed. + */ + free(job); + session_put(session); + session_unlock_list(); + continue; + } + + session_lock(session); + ret = run_job(job, session, handle->notification_thread_handle); + session_unlock(session); + /* Release reference held by the job. */ + session_put(session); + session_unlock_list(); + free(job); + if (ret) { + goto end; + } + } + + ret = 0; + +end: + return ret; +} + +static +int handle_condition(const struct lttng_condition *condition, + const struct lttng_evaluation *evaluation, + struct notification_thread_handle *notification_thread_handle) +{ + int ret = 0; + const char *condition_session_name = NULL; + enum lttng_condition_type condition_type; + enum lttng_condition_status condition_status; + enum lttng_evaluation_status evaluation_status; + uint64_t consumed; + struct ltt_session *session; + + condition_type = lttng_condition_get_type(condition); + + if (condition_type != LTTNG_CONDITION_TYPE_SESSION_CONSUMED_SIZE) { + ret = -1; + ERR("Condition type and session usage type are not the same"); + goto end; + } + + /* Fetch info to test */ + condition_status = lttng_condition_session_consumed_size_get_session_name( + condition, &condition_session_name); + if (condition_status != LTTNG_CONDITION_STATUS_OK) { + ERR("Session name could not be fetched"); + ret = -1; + goto end; + } + evaluation_status = lttng_evaluation_session_consumed_size_get_consumed_size(evaluation, + &consumed); + if (evaluation_status != LTTNG_EVALUATION_STATUS_OK) { + ERR("Failed to get evaluation"); + ret = -1; + goto end; + } + + session_lock_list(); + session = session_find_by_name(condition_session_name); + if (!session) { + ret = -1; + session_unlock_list(); + ERR("Session \"%s\" not found", + condition_session_name); + goto end; + } + session_lock(session); + + ret = unsubscribe_session_consumed_size_rotation(session, + notification_thread_handle); + if (ret) { + goto end_unlock; + } + + ret = cmd_rotate_session(session, NULL, false, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED); + if (ret == -LTTNG_ERR_ROTATION_PENDING) { + DBG("Rotate already pending, subscribe to the next threshold value"); + } else if (ret != LTTNG_OK) { + ERR("Failed to rotate on size notification with error: %s", + lttng_strerror(ret)); + ret = -1; + goto end_unlock; + } + ret = subscribe_session_consumed_size_rotation(session, + consumed + session->rotate_size, + notification_thread_handle); + if (ret) { + ERR("Failed to subscribe to session consumed size condition"); + goto end_unlock; + } + ret = 0; + +end_unlock: + session_unlock(session); + session_put(session); + session_unlock_list(); +end: + return ret; +} + +static +int handle_notification_channel(int fd, + struct rotation_thread_handle *handle, + struct rotation_thread *state) +{ + int ret; + bool notification_pending; + struct lttng_notification *notification = NULL; + enum lttng_notification_channel_status status; + const struct lttng_evaluation *notification_evaluation; + const struct lttng_condition *notification_condition; + + status = lttng_notification_channel_has_pending_notification( + rotate_notification_channel, ¬ification_pending); + if (status != LTTNG_NOTIFICATION_CHANNEL_STATUS_OK) { + ERR("Error occurred while checking for pending notification"); + ret = -1; + goto end; + } + + if (!notification_pending) { + ret = 0; + goto end; + } + + /* Receive the next notification. */ + status = lttng_notification_channel_get_next_notification( + rotate_notification_channel, + ¬ification); + + switch (status) { + case LTTNG_NOTIFICATION_CHANNEL_STATUS_OK: + break; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_NOTIFICATIONS_DROPPED: + /* Not an error, we will wait for the next one */ + ret = 0; + goto end;; + case LTTNG_NOTIFICATION_CHANNEL_STATUS_CLOSED: + ERR("Notification channel was closed"); + ret = -1; + goto end; + default: + /* Unhandled conditions / errors. */ + ERR("Unknown notification channel status"); + ret = -1; + goto end; + } + + notification_condition = lttng_notification_get_condition(notification); + notification_evaluation = lttng_notification_get_evaluation(notification); + + ret = handle_condition(notification_condition, notification_evaluation, + handle->notification_thread_handle); + +end: + lttng_notification_destroy(notification); + return ret; +} + +static +void *thread_rotation(void *data) +{ + int ret; + struct rotation_thread_handle *handle = (rotation_thread_handle *) data; + struct rotation_thread thread; + int queue_pipe_fd; + + DBG("Started rotation thread"); + rcu_register_thread(); + rcu_thread_online(); + health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_ROTATION); + health_code_update(); + + if (!handle) { + ERR("Invalid thread context provided"); + goto end; + } + + queue_pipe_fd = lttng_pipe_get_readfd( + handle->rotation_timer_queue->event_pipe); + + + ret = init_thread_state(handle, &thread); + if (ret) { + goto error; + } + + while (true) { + int fd_count, i; + + health_poll_entry(); + DBG("Entering poll wait"); + ret = lttng_poll_wait(&thread.events, -1); + DBG("Poll wait returned (%i)", ret); + health_poll_exit(); + if (ret < 0) { + /* + * Restart interrupted system call. + */ + if (errno == EINTR) { + continue; + } + ERR("Error encountered during lttng_poll_wait (%i)", ret); + goto error; + } + + fd_count = ret; + for (i = 0; i < fd_count; i++) { + int fd = LTTNG_POLL_GETFD(&thread.events, i); + uint32_t revents = LTTNG_POLL_GETEV(&thread.events, i); + + DBG("Handling fd (%i) activity (%u)", + fd, revents); + + if (revents & LPOLLERR) { + ERR("Polling returned an error on fd %i", fd); + goto error; + } + + if (fd == rotate_notification_channel->socket) { + ret = handle_notification_channel(fd, handle, + &thread); + if (ret) { + ERR("Error occurred while handling activity on notification channel socket"); + goto error; + } + } else { + /* Job queue or quit pipe activity. */ + + /* + * The job queue is serviced if there is + * activity on the quit pipe to ensure it is + * flushed and all references held in the queue + * are released. + */ + ret = handle_job_queue(handle, &thread, + handle->rotation_timer_queue); + if (ret) { + ERR("Failed to handle rotation timer pipe event"); + goto error; + } + + if (fd == queue_pipe_fd) { + char buf; + + ret = lttng_read(fd, &buf, 1); + if (ret != 1) { + ERR("Failed to read from wakeup pipe (fd = %i)", fd); + goto error; + } + } else { + DBG("Quit pipe activity"); + goto exit; + } + } + } + } +exit: +error: + DBG("Thread exit"); + fini_thread_state(&thread); +end: + health_unregister(the_health_sessiond); + rcu_thread_offline(); + rcu_unregister_thread(); + return NULL; +} + +static +bool shutdown_rotation_thread(void *thread_data) +{ + struct rotation_thread_handle *handle = (rotation_thread_handle *) thread_data; + const int write_fd = lttng_pipe_get_writefd(handle->quit_pipe); + + return notify_thread_pipe(write_fd) == 1; +} + +bool launch_rotation_thread(struct rotation_thread_handle *handle) +{ + struct lttng_thread *thread; + + thread = lttng_thread_create("Rotation", + thread_rotation, + shutdown_rotation_thread, + NULL, + handle); + if (!thread) { + goto error; + } + lttng_thread_put(thread); + return true; +error: + return false; +} diff --git a/src/bin/lttng-sessiond/save.c b/src/bin/lttng-sessiond/save.c deleted file mode 100644 index f0a6e43d5..000000000 --- a/src/bin/lttng-sessiond/save.c +++ /dev/null @@ -1,2897 +0,0 @@ -/* - * Copyright (C) 2014 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "kernel.h" -#include "save.h" -#include "session.h" -#include "lttng-syscall.h" -#include "trace-ust.h" -#include "agent.h" - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_kernel_channel_attributes(struct config_writer *writer, - struct lttng_channel_attr *attr) -{ - int ret; - - ret = config_writer_write_element_string(writer, - config_element_overwrite_mode, - attr->overwrite ? config_overwrite_mode_overwrite : - config_overwrite_mode_discard); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_subbuf_size, attr->subbuf_size); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_num_subbuf, - attr->num_subbuf); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_switch_timer_interval, - attr->switch_timer_interval); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_read_timer_interval, - attr->read_timer_interval); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_output_type, - attr->output == LTTNG_EVENT_SPLICE ? - config_output_type_splice : config_output_type_mmap); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_tracefile_size, attr->tracefile_size); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_tracefile_count, - attr->tracefile_count); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_live_timer_interval, - attr->live_timer_interval); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (attr->extended.ptr) { - struct lttng_channel_extended *ext = NULL; - - ext = (struct lttng_channel_extended *) attr->extended.ptr; - ret = config_writer_write_element_unsigned_int(writer, - config_element_monitor_timer_interval, - ext->monitor_timer_interval); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_signed_int(writer, - config_element_blocking_timeout, - ext->blocking_timeout); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_ust_channel_attributes(struct config_writer *writer, - struct lttng_ust_abi_channel_attr *attr) -{ - int ret; - struct ltt_ust_channel *channel = NULL; - - ret = config_writer_write_element_string(writer, - config_element_overwrite_mode, - attr->overwrite ? config_overwrite_mode_overwrite : - config_overwrite_mode_discard); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_subbuf_size, attr->subbuf_size); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_num_subbuf, - attr->num_subbuf); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_switch_timer_interval, - attr->switch_timer_interval); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_read_timer_interval, - attr->read_timer_interval); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_output_type, - attr->output == LTTNG_UST_ABI_MMAP ? - config_output_type_mmap : config_output_type_splice); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_signed_int(writer, - config_element_blocking_timeout, - attr->u.s.blocking_timeout); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* - * Fetch the monitor timer which is located in the parent of - * lttng_ust_channel_attr - */ - channel = caa_container_of(attr, struct ltt_ust_channel, attr); - ret = config_writer_write_element_unsigned_int(writer, - config_element_monitor_timer_interval, - channel->monitor_timer_interval); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -static -const char *get_kernel_instrumentation_string( - enum lttng_kernel_abi_instrumentation instrumentation) -{ - const char *instrumentation_string; - - switch (instrumentation) { - case LTTNG_KERNEL_ABI_ALL: - instrumentation_string = config_event_type_all; - break; - case LTTNG_KERNEL_ABI_TRACEPOINT: - instrumentation_string = config_event_type_tracepoint; - break; - case LTTNG_KERNEL_ABI_KPROBE: - instrumentation_string = config_event_type_probe; - break; - case LTTNG_KERNEL_ABI_UPROBE: - instrumentation_string = config_event_type_userspace_probe; - break; - case LTTNG_KERNEL_ABI_FUNCTION: - instrumentation_string = config_event_type_function_entry; - break; - case LTTNG_KERNEL_ABI_KRETPROBE: - instrumentation_string = config_event_type_function; - break; - case LTTNG_KERNEL_ABI_NOOP: - instrumentation_string = config_event_type_noop; - break; - case LTTNG_KERNEL_ABI_SYSCALL: - instrumentation_string = config_event_type_syscall; - break; - default: - instrumentation_string = NULL; - } - - return instrumentation_string; -} - -static -const char *get_kernel_context_type_string( - enum lttng_kernel_abi_context_type context_type) -{ - const char *context_type_string; - - switch (context_type) { - case LTTNG_KERNEL_ABI_CONTEXT_PID: - context_type_string = config_event_context_pid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_PROCNAME: - context_type_string = config_event_context_procname; - break; - case LTTNG_KERNEL_ABI_CONTEXT_PRIO: - context_type_string = config_event_context_prio; - break; - case LTTNG_KERNEL_ABI_CONTEXT_NICE: - context_type_string = config_event_context_nice; - break; - case LTTNG_KERNEL_ABI_CONTEXT_VPID: - context_type_string = config_event_context_vpid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_TID: - context_type_string = config_event_context_tid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_VTID: - context_type_string = config_event_context_vtid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_PPID: - context_type_string = config_event_context_ppid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_VPPID: - context_type_string = config_event_context_vppid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_HOSTNAME: - context_type_string = config_event_context_hostname; - break; - case LTTNG_KERNEL_ABI_CONTEXT_INTERRUPTIBLE: - context_type_string = config_event_context_interruptible; - break; - case LTTNG_KERNEL_ABI_CONTEXT_PREEMPTIBLE: - context_type_string = config_event_context_preemptible; - break; - case LTTNG_KERNEL_ABI_CONTEXT_NEED_RESCHEDULE: - context_type_string = config_event_context_need_reschedule; - break; - case LTTNG_KERNEL_ABI_CONTEXT_MIGRATABLE: - context_type_string = config_event_context_migratable; - break; - case LTTNG_KERNEL_ABI_CONTEXT_CALLSTACK_USER: - context_type_string = config_event_context_callstack_user; - break; - case LTTNG_KERNEL_ABI_CONTEXT_CALLSTACK_KERNEL: - context_type_string = config_event_context_callstack_kernel; - break; - case LTTNG_KERNEL_ABI_CONTEXT_CGROUP_NS: - context_type_string = config_event_context_cgroup_ns; - break; - case LTTNG_KERNEL_ABI_CONTEXT_IPC_NS: - context_type_string = config_event_context_ipc_ns; - break; - case LTTNG_KERNEL_ABI_CONTEXT_MNT_NS: - context_type_string = config_event_context_mnt_ns; - break; - case LTTNG_KERNEL_ABI_CONTEXT_NET_NS: - context_type_string = config_event_context_net_ns; - break; - case LTTNG_KERNEL_ABI_CONTEXT_PID_NS: - context_type_string = config_event_context_pid_ns; - break; - case LTTNG_KERNEL_ABI_CONTEXT_TIME_NS: - context_type_string = config_event_context_time_ns; - break; - case LTTNG_KERNEL_ABI_CONTEXT_USER_NS: - context_type_string = config_event_context_user_ns; - break; - case LTTNG_KERNEL_ABI_CONTEXT_UTS_NS: - context_type_string = config_event_context_uts_ns; - break; - case LTTNG_KERNEL_ABI_CONTEXT_UID: - context_type_string = config_event_context_uid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_EUID: - context_type_string = config_event_context_euid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_SUID: - context_type_string = config_event_context_suid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_GID: - context_type_string = config_event_context_gid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_EGID: - context_type_string = config_event_context_egid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_SGID: - context_type_string = config_event_context_sgid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_VUID: - context_type_string = config_event_context_vuid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_VEUID: - context_type_string = config_event_context_veuid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_VSUID: - context_type_string = config_event_context_vsuid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_VGID: - context_type_string = config_event_context_vgid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_VEGID: - context_type_string = config_event_context_vegid; - break; - case LTTNG_KERNEL_ABI_CONTEXT_VSGID: - context_type_string = config_event_context_vsgid; - break; - default: - context_type_string = NULL; - } - - return context_type_string; -} - -static -const char *get_ust_context_type_string( - enum lttng_ust_abi_context_type context_type) -{ - const char *context_type_string; - - switch (context_type) { - case LTTNG_UST_ABI_CONTEXT_PROCNAME: - context_type_string = config_event_context_procname; - break; - case LTTNG_UST_ABI_CONTEXT_VPID: - context_type_string = config_event_context_vpid; - break; - case LTTNG_UST_ABI_CONTEXT_VTID: - context_type_string = config_event_context_vtid; - break; - case LTTNG_UST_ABI_CONTEXT_IP: - context_type_string = config_event_context_ip; - break; - case LTTNG_UST_ABI_CONTEXT_PTHREAD_ID: - context_type_string = config_event_context_pthread_id; - break; - case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: - context_type_string = config_event_context_app; - break; - case LTTNG_UST_ABI_CONTEXT_CGROUP_NS: - context_type_string = config_event_context_cgroup_ns; - break; - case LTTNG_UST_ABI_CONTEXT_IPC_NS: - context_type_string = config_event_context_ipc_ns; - break; - case LTTNG_UST_ABI_CONTEXT_MNT_NS: - context_type_string = config_event_context_mnt_ns; - break; - case LTTNG_UST_ABI_CONTEXT_NET_NS: - context_type_string = config_event_context_net_ns; - break; - case LTTNG_UST_ABI_CONTEXT_TIME_NS: - context_type_string = config_event_context_time_ns; - break; - case LTTNG_UST_ABI_CONTEXT_PID_NS: - context_type_string = config_event_context_pid_ns; - break; - case LTTNG_UST_ABI_CONTEXT_USER_NS: - context_type_string = config_event_context_user_ns; - break; - case LTTNG_UST_ABI_CONTEXT_UTS_NS: - context_type_string = config_event_context_uts_ns; - break; - case LTTNG_UST_ABI_CONTEXT_VUID: - context_type_string = config_event_context_vuid; - break; - case LTTNG_UST_ABI_CONTEXT_VEUID: - context_type_string = config_event_context_veuid; - break; - case LTTNG_UST_ABI_CONTEXT_VSUID: - context_type_string = config_event_context_vsuid; - break; - case LTTNG_UST_ABI_CONTEXT_VGID: - context_type_string = config_event_context_vgid; - break; - case LTTNG_UST_ABI_CONTEXT_VEGID: - context_type_string = config_event_context_vegid; - break; - case LTTNG_UST_ABI_CONTEXT_VSGID: - context_type_string = config_event_context_vsgid; - break; - case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: - /* - * Error, should not be stored in the XML, perf contexts - * are stored as a node of type event_perf_context_type. - */ - default: - context_type_string = NULL; - break; - } - - return context_type_string; -} - -static -const char *get_buffer_type_string( - enum lttng_buffer_type buffer_type) -{ - const char *buffer_type_string; - - switch (buffer_type) { - case LTTNG_BUFFER_PER_PID: - buffer_type_string = config_buffer_type_per_pid; - break; - case LTTNG_BUFFER_PER_UID: - buffer_type_string = config_buffer_type_per_uid; - break; - case LTTNG_BUFFER_GLOBAL: - buffer_type_string = config_buffer_type_global; - break; - default: - buffer_type_string = NULL; - } - - return buffer_type_string; -} - -static -const char *get_loglevel_type_string( - enum lttng_ust_abi_loglevel_type loglevel_type) -{ - const char *loglevel_type_string; - - switch (loglevel_type) { - case LTTNG_UST_ABI_LOGLEVEL_ALL: - loglevel_type_string = config_loglevel_type_all; - break; - case LTTNG_UST_ABI_LOGLEVEL_RANGE: - loglevel_type_string = config_loglevel_type_range; - break; - case LTTNG_UST_ABI_LOGLEVEL_SINGLE: - loglevel_type_string = config_loglevel_type_single; - break; - default: - loglevel_type_string = NULL; - } - - return loglevel_type_string; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_kernel_function_event(struct config_writer *writer, - struct ltt_kernel_event *event) -{ - int ret; - - ret = config_writer_open_element(writer, config_element_function_attributes); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, config_element_name, - event->event->u.ftrace.symbol_name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* /function attributes */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } -end: - return ret; -} - -static -int save_kernel_kprobe_event(struct config_writer *writer, - struct ltt_kernel_event *event) -{ - int ret; - const char *symbol_name; - uint64_t addr; - uint64_t offset; - - switch (event->event->instrumentation) { - case LTTNG_KERNEL_ABI_KPROBE: - /* - * Comments in lttng-kernel.h mention that - * either addr or symbol_name are set, not both. - */ - addr = event->event->u.kprobe.addr; - offset = event->event->u.kprobe.offset; - symbol_name = addr ? NULL : event->event->u.kprobe.symbol_name; - break; - case LTTNG_KERNEL_ABI_KRETPROBE: - addr = event->event->u.kretprobe.addr; - offset = event->event->u.kretprobe.offset; - symbol_name = addr ? NULL : event->event->u.kretprobe.symbol_name; - break; - default: - LTTNG_ASSERT(1); - ERR("Unsupported kernel instrumentation type."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - ret = config_writer_open_element(writer, config_element_probe_attributes); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (addr) { - ret = config_writer_write_element_unsigned_int( writer, - config_element_address, addr); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } else if (symbol_name) { - ret = config_writer_write_element_string(writer, - config_element_symbol_name, symbol_name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - /* If the offset is non-zero, write it.*/ - if (offset) { - ret = config_writer_write_element_unsigned_int(writer, - config_element_offset, offset); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - } else { - /* - * This really should not happen as we are either setting the - * address or the symbol above. - */ - ERR("Invalid probe/function description."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } -end: - return ret; -} - -/* - * Save the userspace probe tracepoint event associated with the event to the - * config writer. - */ -static -int save_kernel_userspace_probe_tracepoint_event(struct config_writer *writer, - struct ltt_kernel_event *event) -{ - int ret = 0; - const char *probe_name, *provider_name, *binary_path; - const struct lttng_userspace_probe_location *userspace_probe_location; - const struct lttng_userspace_probe_location_lookup_method *lookup_method; - enum lttng_userspace_probe_location_lookup_method_type lookup_type; - - /* Get userspace probe location from the event. */ - userspace_probe_location = event->userspace_probe_location; - if (!userspace_probe_location) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* Get lookup method and lookup method type. */ - lookup_method = lttng_userspace_probe_location_get_lookup_method(userspace_probe_location); - if (!lookup_method) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - lookup_type = lttng_userspace_probe_location_lookup_method_get_type(lookup_method); - - /* Get the binary path, probe name and provider name. */ - binary_path = - lttng_userspace_probe_location_tracepoint_get_binary_path( - userspace_probe_location); - if (!binary_path) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - probe_name = - lttng_userspace_probe_location_tracepoint_get_probe_name( - userspace_probe_location); - if (!probe_name) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - provider_name = - lttng_userspace_probe_location_tracepoint_get_provider_name( - userspace_probe_location); - if (!provider_name) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* Open a userspace probe tracepoint attribute. */ - ret = config_writer_open_element(writer, config_element_userspace_probe_tracepoint_attributes); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - switch (lookup_type) { - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: - ret = config_writer_write_element_string(writer, - config_element_userspace_probe_lookup, - config_element_userspace_probe_lookup_tracepoint_sdt); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - break; - default: - ERR("Unsupported kernel userspace probe tracepoint lookup method."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - /* Write the binary path, provider name and the probe name. */ - ret = config_writer_write_element_string(writer, - config_element_userspace_probe_location_binary_path, - binary_path); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_userspace_probe_tracepoint_location_provider_name, - provider_name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_userspace_probe_tracepoint_location_probe_name, - probe_name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* Close the userspace probe tracepoint attribute. */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - -end: - return ret; -} - -/* - * Save the userspace probe function event associated with the event to the - * config writer. - */ -static -int save_kernel_userspace_probe_function_event(struct config_writer *writer, - struct ltt_kernel_event *event) -{ - int ret = 0; - const char *function_name, *binary_path; - const struct lttng_userspace_probe_location *userspace_probe_location; - const struct lttng_userspace_probe_location_lookup_method *lookup_method; - enum lttng_userspace_probe_location_lookup_method_type lookup_type; - - /* Get userspace probe location from the event. */ - userspace_probe_location = event->userspace_probe_location; - if (!userspace_probe_location) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* Get lookup method and lookup method type. */ - lookup_method = lttng_userspace_probe_location_get_lookup_method( - userspace_probe_location); - if (!lookup_method) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* Get the binary path and the function name. */ - binary_path = - lttng_userspace_probe_location_function_get_binary_path( - userspace_probe_location); - if (!binary_path) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - function_name = - lttng_userspace_probe_location_function_get_function_name( - userspace_probe_location); - if (!function_name) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* Open a userspace probe function attribute. */ - ret = config_writer_open_element(writer, - config_element_userspace_probe_function_attributes); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - lookup_type = lttng_userspace_probe_location_lookup_method_get_type(lookup_method); - switch (lookup_type) { - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF: - ret = config_writer_write_element_string(writer, - config_element_userspace_probe_lookup, - config_element_userspace_probe_lookup_function_elf); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - break; - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_DEFAULT: - ret = config_writer_write_element_string(writer, - config_element_userspace_probe_lookup, - config_element_userspace_probe_lookup_function_default); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - break; - default: - ERR("Unsupported kernel userspace probe function lookup method."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - /* Write the binary path and the function name. */ - ret = config_writer_write_element_string(writer, - config_element_userspace_probe_location_binary_path, - binary_path); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_userspace_probe_function_location_function_name, - function_name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* Close the userspace probe function attribute. */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - -end: - return ret; -} - -static -int save_kernel_userspace_probe_event(struct config_writer *writer, - struct ltt_kernel_event *event) -{ - int ret; - struct lttng_userspace_probe_location *userspace_probe_location; - - /* Get userspace probe location from the event. */ - userspace_probe_location = event->userspace_probe_location; - if (!userspace_probe_location) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - switch(lttng_userspace_probe_location_get_type(userspace_probe_location)) { - case LTTNG_USERSPACE_PROBE_LOCATION_TYPE_FUNCTION: - { - ret = save_kernel_userspace_probe_function_event(writer, event); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - break; - } - case LTTNG_USERSPACE_PROBE_LOCATION_TYPE_TRACEPOINT: - { - ret = save_kernel_userspace_probe_tracepoint_event(writer, event); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - break; - } - case LTTNG_USERSPACE_PROBE_LOCATION_TYPE_UNKNOWN: - default: - ERR("Unsupported kernel userspace probe location type."); - ret = LTTNG_ERR_INVALID; - goto end; - } - -end: - return ret; -} - -static -int save_kernel_event(struct config_writer *writer, - struct ltt_kernel_event *event) -{ - int ret; - const char *instrumentation_type; - - ret = config_writer_open_element(writer, config_element_event); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (event->event->name[0]) { - ret = config_writer_write_element_string(writer, - config_element_name, event->event->name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - ret = config_writer_write_element_bool(writer, config_element_enabled, - event->enabled); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - instrumentation_type = get_kernel_instrumentation_string( - event->event->instrumentation); - if (!instrumentation_type) { - ret = LTTNG_ERR_INVALID; - goto end; - } - - ret = config_writer_write_element_string(writer, config_element_type, - instrumentation_type); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (event->filter_expression) { - ret = config_writer_write_element_string(writer, - config_element_filter, - event->filter_expression); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - if (event->event->instrumentation == LTTNG_KERNEL_ABI_FUNCTION || - event->event->instrumentation == LTTNG_KERNEL_ABI_KPROBE || - event->event->instrumentation == LTTNG_KERNEL_ABI_UPROBE || - event->event->instrumentation == LTTNG_KERNEL_ABI_KRETPROBE) { - - ret = config_writer_open_element(writer, - config_element_attributes); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - switch (event->event->instrumentation) { - case LTTNG_KERNEL_ABI_SYSCALL: - case LTTNG_KERNEL_ABI_FUNCTION: - ret = save_kernel_function_event(writer, event); - if (ret) { - goto end; - } - break; - case LTTNG_KERNEL_ABI_KPROBE: - case LTTNG_KERNEL_ABI_KRETPROBE: - ret = save_kernel_kprobe_event(writer, event); - if (ret) { - goto end; - } - break; - case LTTNG_KERNEL_ABI_UPROBE: - ret = save_kernel_userspace_probe_event(writer, event); - if (ret) { - goto end; - } - break; - default: - ERR("Unsupported kernel instrumentation type."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - /* /attributes */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - /* /event */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_kernel_events(struct config_writer *writer, - struct ltt_kernel_channel *kchan) -{ - int ret; - struct ltt_kernel_event *event; - - ret = config_writer_open_element(writer, config_element_events); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - cds_list_for_each_entry(event, &kchan->events_list.head, list) { - ret = save_kernel_event(writer, event); - if (ret != LTTNG_OK) { - goto end; - } - } - - /* /events */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_ust_event(struct config_writer *writer, - struct ltt_ust_event *event) -{ - int ret; - const char *loglevel_type_string; - - ret = config_writer_open_element(writer, config_element_event); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (event->attr.name[0]) { - ret = config_writer_write_element_string(writer, - config_element_name, event->attr.name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - ret = config_writer_write_element_bool(writer, config_element_enabled, - event->enabled); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (event->attr.instrumentation != LTTNG_UST_ABI_TRACEPOINT) { - ERR("Unsupported UST instrumentation type."); - ret = LTTNG_ERR_INVALID; - goto end; - } - ret = config_writer_write_element_string(writer, config_element_type, - config_event_type_tracepoint); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - loglevel_type_string = get_loglevel_type_string( - event->attr.loglevel_type); - if (!loglevel_type_string) { - ERR("Unsupported UST loglevel type."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_loglevel_type, loglevel_type_string); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* The log level is irrelevant if no "filtering" is enabled */ - if (event->attr.loglevel_type != LTTNG_UST_ABI_LOGLEVEL_ALL) { - ret = config_writer_write_element_signed_int(writer, - config_element_loglevel, event->attr.loglevel); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - if (event->filter_expression) { - ret = config_writer_write_element_string(writer, - config_element_filter, event->filter_expression); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - if (event->exclusion && event->exclusion->count) { - uint32_t i; - - ret = config_writer_open_element(writer, - config_element_exclusions); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - for (i = 0; i < event->exclusion->count; i++) { - ret = config_writer_write_element_string(writer, - config_element_exclusion, - LTTNG_EVENT_EXCLUSION_NAME_AT( - event->exclusion, i)); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - /* /exclusions */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - /* /event */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_ust_events(struct config_writer *writer, - struct lttng_ht *events) -{ - int ret; - struct ltt_ust_event *event; - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - - ret = config_writer_open_element(writer, config_element_events); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - rcu_read_lock(); - cds_lfht_for_each_entry(events->ht, &iter.iter, node, node) { - event = caa_container_of(node, struct ltt_ust_event, node); - - if (event->internal) { - /* Internal events must not be exposed to clients */ - continue; - } - ret = save_ust_event(writer, event); - if (ret != LTTNG_OK) { - rcu_read_unlock(); - goto end; - } - } - rcu_read_unlock(); - - /* /events */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int init_ust_event_from_agent_event(struct ltt_ust_event *ust_event, - struct agent_event *agent_event) -{ - int ret; - enum lttng_ust_abi_loglevel_type ust_loglevel_type; - - ust_event->enabled = AGENT_EVENT_IS_ENABLED(agent_event); - ust_event->attr.instrumentation = LTTNG_UST_ABI_TRACEPOINT; - if (lttng_strncpy(ust_event->attr.name, agent_event->name, - LTTNG_SYMBOL_NAME_LEN)) { - ret = LTTNG_ERR_INVALID; - goto end; - } - switch (agent_event->loglevel_type) { - case LTTNG_EVENT_LOGLEVEL_ALL: - ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_ALL; - break; - case LTTNG_EVENT_LOGLEVEL_SINGLE: - ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_SINGLE; - break; - case LTTNG_EVENT_LOGLEVEL_RANGE: - ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_RANGE; - break; - default: - ERR("Invalid agent_event loglevel_type."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - ust_event->attr.loglevel_type = ust_loglevel_type; - ust_event->attr.loglevel = agent_event->loglevel_value; - ust_event->filter_expression = agent_event->filter_expression; - ust_event->exclusion = agent_event->exclusion; - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_agent_events(struct config_writer *writer, - struct agent *agent) -{ - int ret; - struct lttng_ht_iter iter; - struct lttng_ht_node_str *node; - - ret = config_writer_open_element(writer, config_element_events); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - rcu_read_lock(); - cds_lfht_for_each_entry(agent->events->ht, &iter.iter, node, node) { - struct agent_event *agent_event; - struct ltt_ust_event fake_event; - - memset(&fake_event, 0, sizeof(fake_event)); - agent_event = caa_container_of(node, struct agent_event, node); - - /* - * Initialize a fake ust event to reuse the same serialization - * function since UST and agent events contain the same info - * (and one could wonder why they don't reuse the same - * structures...). - */ - ret = init_ust_event_from_agent_event(&fake_event, agent_event); - if (ret != LTTNG_OK) { - rcu_read_unlock(); - goto end; - } - ret = save_ust_event(writer, &fake_event); - if (ret != LTTNG_OK) { - rcu_read_unlock(); - goto end; - } - } - rcu_read_unlock(); - - /* /events */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_kernel_context(struct config_writer *writer, - struct lttng_kernel_abi_context *ctx) -{ - int ret = LTTNG_OK; - - if (!ctx) { - goto end; - } - - ret = config_writer_open_element(writer, config_element_context); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (ctx->ctx == LTTNG_KERNEL_ABI_CONTEXT_PERF_CPU_COUNTER) { - ret = config_writer_open_element(writer, - config_element_context_perf); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_type, ctx->u.perf_counter.type); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_config, ctx->u.perf_counter.config); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_name, ctx->u.perf_counter.name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* /perf */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } else { - const char *context_type_string = - get_kernel_context_type_string(ctx->ctx); - - if (!context_type_string) { - ERR("Unsupported kernel context type."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_type, context_type_string); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - /* /context */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_kernel_contexts(struct config_writer *writer, - struct ltt_kernel_channel *kchan) -{ - int ret; - struct ltt_kernel_context *ctx; - - if (cds_list_empty(&kchan->ctx_list)) { - ret = LTTNG_OK; - goto end; - } - - ret = config_writer_open_element(writer, config_element_contexts); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - cds_list_for_each_entry(ctx, &kchan->ctx_list, list) { - ret = save_kernel_context(writer, &ctx->ctx); - if (ret != LTTNG_OK) { - goto end; - } - } - - /* /contexts */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_ust_context_perf_thread_counter(struct config_writer *writer, - struct ltt_ust_context *ctx) -{ - int ret; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(ctx); - - /* Perf contexts are saved as event_perf_context_type */ - ret = config_writer_open_element(writer, config_element_context_perf); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_type, ctx->ctx.u.perf_counter.type); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_config, ctx->ctx.u.perf_counter.config); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, config_element_name, - ctx->ctx.u.perf_counter.name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* /perf */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_ust_context_app_ctx(struct config_writer *writer, - struct ltt_ust_context *ctx) -{ - int ret; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(ctx); - - /* Application contexts are saved as application_context_type */ - ret = config_writer_open_element(writer, config_element_context_app); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_context_app_provider_name, - ctx->ctx.u.app_ctx.provider_name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_context_app_ctx_name, - ctx->ctx.u.app_ctx.ctx_name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* /app */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_ust_context_generic(struct config_writer *writer, - struct ltt_ust_context *ctx) -{ - int ret; - const char *context_type_string; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(ctx); - - /* Save context as event_context_type_type */ - context_type_string = get_ust_context_type_string( - ctx->ctx.ctx); - if (!context_type_string) { - ERR("Unsupported UST context type."); - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_type, context_type_string); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_ust_context(struct config_writer *writer, - struct cds_list_head *ctx_list) -{ - int ret; - struct ltt_ust_context *ctx; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(ctx_list); - - ret = config_writer_open_element(writer, config_element_contexts); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - cds_list_for_each_entry(ctx, ctx_list, list) { - ret = config_writer_open_element(writer, - config_element_context); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - switch (ctx->ctx.ctx) { - case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: - ret = save_ust_context_perf_thread_counter(writer, ctx); - break; - case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: - ret = save_ust_context_app_ctx(writer, ctx); - break; - default: - /* Save generic context. */ - ret = save_ust_context_generic(writer, ctx); - } - if (ret != LTTNG_OK) { - goto end; - } - - /* /context */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - /* /contexts */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_kernel_channel(struct config_writer *writer, - struct ltt_kernel_channel *kchan) -{ - int ret; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(kchan); - - ret = config_writer_open_element(writer, config_element_channel); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, config_element_name, - kchan->channel->name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_bool(writer, config_element_enabled, - kchan->channel->enabled); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = save_kernel_channel_attributes(writer, &kchan->channel->attr); - if (ret != LTTNG_OK) { - goto end; - } - - ret = save_kernel_events(writer, kchan); - if (ret != LTTNG_OK) { - goto end; - } - - ret = save_kernel_contexts(writer, kchan); - if (ret != LTTNG_OK) { - goto end; - } - - /* /channel */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_ust_channel(struct config_writer *writer, - struct ltt_ust_channel *ust_chan, - struct ltt_ust_session *session) -{ - int ret; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(ust_chan); - LTTNG_ASSERT(session); - - ret = config_writer_open_element(writer, config_element_channel); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, config_element_name, - ust_chan->name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_bool(writer, config_element_enabled, - ust_chan->enabled); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = save_ust_channel_attributes(writer, &ust_chan->attr); - if (ret != LTTNG_OK) { - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_tracefile_size, ust_chan->tracefile_size); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_tracefile_count, ust_chan->tracefile_count); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_live_timer_interval, - session->live_timer_interval); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (ust_chan->domain == LTTNG_DOMAIN_UST) { - ret = save_ust_events(writer, ust_chan->events); - if (ret != LTTNG_OK) { - goto end; - } - } else { - struct agent *agent = NULL; - - agent = trace_ust_find_agent(session, ust_chan->domain); - if (!agent) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - ERR("Could not find agent associated to UST subdomain"); - goto end; - } - - /* - * Channels associated with a UST sub-domain (such as JUL, Log4j - * or Python) don't have any non-internal events. We retrieve - * the "agent" events associated with this channel and serialize - * them. - */ - ret = save_agent_events(writer, agent); - if (ret != LTTNG_OK) { - goto end; - } - } - - ret = save_ust_context(writer, &ust_chan->ctx_list); - if (ret != LTTNG_OK) { - goto end; - } - - /* /channel */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_kernel_session(struct config_writer *writer, - struct ltt_session *session) -{ - int ret; - struct ltt_kernel_channel *kchan; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(session); - - ret = config_writer_write_element_string(writer, config_element_type, - config_domain_type_kernel); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_buffer_type, config_buffer_type_global); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_open_element(writer, - config_element_channels); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - cds_list_for_each_entry(kchan, &session->kernel_session->channel_list.head, - list) { - ret = save_kernel_channel(writer, kchan); - if (ret != LTTNG_OK) { - goto end; - } - } - - /* /channels */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -static -const char *get_config_domain_str(enum lttng_domain_type domain) -{ - const char *str_dom; - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - str_dom = config_domain_type_kernel; - break; - case LTTNG_DOMAIN_UST: - str_dom = config_domain_type_ust; - break; - case LTTNG_DOMAIN_JUL: - str_dom = config_domain_type_jul; - break; - case LTTNG_DOMAIN_LOG4J: - str_dom = config_domain_type_log4j; - break; - case LTTNG_DOMAIN_PYTHON: - str_dom = config_domain_type_python; - break; - default: - abort(); - } - - return str_dom; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static int save_process_attr_tracker(struct config_writer *writer, - struct ltt_session *sess, - int domain, - enum lttng_process_attr process_attr) -{ - int ret = LTTNG_OK; - const char *element_id_tracker, *element_target_id, *element_id; - const struct process_attr_tracker *tracker; - enum lttng_tracking_policy tracking_policy; - struct lttng_process_attr_values *values = NULL; - - switch (process_attr) { - case LTTNG_PROCESS_ATTR_PROCESS_ID: - element_id_tracker = config_element_process_attr_tracker_pid; - element_target_id = config_element_process_attr_pid_value; - element_id = config_element_process_attr_id; - break; - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - element_id_tracker = config_element_process_attr_tracker_vpid; - element_target_id = config_element_process_attr_vpid_value; - element_id = config_element_process_attr_id; - break; - case LTTNG_PROCESS_ATTR_USER_ID: - element_id_tracker = config_element_process_attr_tracker_uid; - element_target_id = config_element_process_attr_uid_value; - element_id = config_element_process_attr_id; - break; - case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: - element_id_tracker = config_element_process_attr_tracker_vuid; - element_target_id = config_element_process_attr_vuid_value; - element_id = config_element_process_attr_id; - break; - case LTTNG_PROCESS_ATTR_GROUP_ID: - element_id_tracker = config_element_process_attr_tracker_gid; - element_target_id = config_element_process_attr_gid_value; - element_id = config_element_process_attr_id; - break; - case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: - element_id_tracker = config_element_process_attr_tracker_vgid; - element_target_id = config_element_process_attr_vgid_value; - element_id = config_element_process_attr_id; - break; - default: - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - { - tracker = kernel_get_process_attr_tracker( - sess->kernel_session, process_attr); - LTTNG_ASSERT(tracker); - break; - } - case LTTNG_DOMAIN_UST: - { - tracker = trace_ust_get_process_attr_tracker( - sess->ust_session, process_attr); - LTTNG_ASSERT(tracker); - break; - } - case LTTNG_DOMAIN_JUL: - case LTTNG_DOMAIN_LOG4J: - case LTTNG_DOMAIN_PYTHON: - default: - ret = LTTNG_ERR_UNSUPPORTED_DOMAIN; - goto end; - } - - tracking_policy = process_attr_tracker_get_tracking_policy(tracker); - if (tracking_policy == LTTNG_TRACKING_POLICY_INCLUDE_ALL) { - /* Tracking all, nothing to output. */ - ret = LTTNG_OK; - goto end; - } - - ret = config_writer_open_element(writer, element_id_tracker); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_open_element( - writer, config_element_process_attr_values); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (tracking_policy == LTTNG_TRACKING_POLICY_INCLUDE_SET) { - unsigned int i, count; - enum process_attr_tracker_status status = - process_attr_tracker_get_inclusion_set( - tracker, &values); - - if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - - count = _lttng_process_attr_values_get_count(values); - - for (i = 0; i < count; i++) { - unsigned int integral_value = UINT_MAX; - const char *name = NULL; - const struct process_attr_value *value = - lttng_process_attr_tracker_values_get_at_index( - values, i); - - LTTNG_ASSERT(value); - ret = config_writer_open_element( - writer, element_target_id); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - switch (value->type) { - case LTTNG_PROCESS_ATTR_VALUE_TYPE_PID: - integral_value = - (unsigned int) value->value.pid; - break; - case LTTNG_PROCESS_ATTR_VALUE_TYPE_UID: - integral_value = - (unsigned int) value->value.uid; - break; - case LTTNG_PROCESS_ATTR_VALUE_TYPE_GID: - integral_value = - (unsigned int) value->value.gid; - break; - case LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME: - name = value->value.user_name; - LTTNG_ASSERT(name); - break; - case LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME: - name = value->value.group_name; - LTTNG_ASSERT(name); - break; - default: - abort(); - } - - if (name) { - ret = config_writer_write_element_string(writer, - config_element_name, name); - } else { - ret = config_writer_write_element_unsigned_int( - writer, element_id, - integral_value); - } - - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* /$element_target_id */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - } - - /* /values */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* /$element_id_tracker */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - lttng_process_attr_values_destroy(values); - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static int save_process_attr_trackers(struct config_writer *writer, - struct ltt_session *sess, - int domain) -{ - int ret; - - switch (domain) { - case LTTNG_DOMAIN_KERNEL: - ret = save_process_attr_tracker(writer, sess, domain, - LTTNG_PROCESS_ATTR_PROCESS_ID); - if (ret != LTTNG_OK) { - goto end; - } - ret = save_process_attr_tracker(writer, sess, domain, - LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID); - if (ret != LTTNG_OK) { - goto end; - } - ret = save_process_attr_tracker(writer, sess, domain, - LTTNG_PROCESS_ATTR_USER_ID); - if (ret != LTTNG_OK) { - goto end; - } - ret = save_process_attr_tracker(writer, sess, domain, - LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID); - if (ret != LTTNG_OK) { - goto end; - } - ret = save_process_attr_tracker(writer, sess, domain, - LTTNG_PROCESS_ATTR_GROUP_ID); - if (ret != LTTNG_OK) { - goto end; - } - ret = save_process_attr_tracker(writer, sess, domain, - LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID); - if (ret != LTTNG_OK) { - goto end; - } - break; - case LTTNG_DOMAIN_UST: - ret = save_process_attr_tracker(writer, sess, domain, - LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID); - if (ret != LTTNG_OK) { - goto end; - } - ret = save_process_attr_tracker(writer, sess, domain, - LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID); - if (ret != LTTNG_OK) { - goto end; - } - ret = save_process_attr_tracker(writer, sess, domain, - LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID); - if (ret != LTTNG_OK) { - goto end; - } - break; - default: - ret = LTTNG_ERR_INVALID; - goto end; - } - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_ust_domain(struct config_writer *writer, - struct ltt_session *session, enum lttng_domain_type domain) -{ - int ret; - struct ltt_ust_channel *ust_chan; - const char *buffer_type_string; - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - const char *config_domain_name; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(session); - - ret = config_writer_open_element(writer, - config_element_domain); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - config_domain_name = get_config_domain_str(domain); - if (!config_domain_name) { - ret = LTTNG_ERR_INVALID; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_type, config_domain_name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - buffer_type_string = get_buffer_type_string( - session->ust_session->buffer_type); - if (!buffer_type_string) { - ERR("Unsupported buffer type."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - ret = config_writer_write_element_string(writer, - config_element_buffer_type, buffer_type_string); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_open_element(writer, config_element_channels); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - rcu_read_lock(); - cds_lfht_for_each_entry(session->ust_session->domain_global.channels->ht, - &iter.iter, node, node) { - ust_chan = caa_container_of(node, struct ltt_ust_channel, node); - if (domain == ust_chan->domain) { - ret = save_ust_channel(writer, ust_chan, session->ust_session); - if (ret != LTTNG_OK) { - rcu_read_unlock(); - goto end; - } - } - } - rcu_read_unlock(); - - /* /channels */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (domain == LTTNG_DOMAIN_UST) { - ret = config_writer_open_element( - writer, config_element_process_attr_trackers); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = save_process_attr_trackers( - writer, session, LTTNG_DOMAIN_UST); - if (ret != LTTNG_OK) { - goto end; - } - - /* /trackers */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - /* /domain */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_domains(struct config_writer *writer, struct ltt_session *session) -{ - int ret = LTTNG_OK; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(session); - - if (!session->kernel_session && !session->ust_session) { - goto end; - } - - ret = config_writer_open_element(writer, config_element_domains); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (session->kernel_session) { - ret = config_writer_open_element(writer, - config_element_domain); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = save_kernel_session(writer, session); - if (ret != LTTNG_OK) { - goto end; - } - - ret = config_writer_open_element( - writer, config_element_process_attr_trackers); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = save_process_attr_trackers( - writer, session, LTTNG_DOMAIN_KERNEL); - if (ret != LTTNG_OK) { - goto end; - } - - /* /trackers */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - /* /domain */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - if (session->ust_session) { - ret = save_ust_domain(writer, session, LTTNG_DOMAIN_UST); - if (ret != LTTNG_OK) { - goto end; - } - - ret = save_ust_domain(writer, session, LTTNG_DOMAIN_JUL); - if (ret != LTTNG_OK) { - goto end; - } - - ret = save_ust_domain(writer, session, LTTNG_DOMAIN_LOG4J); - if (ret != LTTNG_OK) { - goto end; - } - - ret = save_ust_domain(writer, session, LTTNG_DOMAIN_PYTHON); - if (ret != LTTNG_OK) { - goto end; - } - } - - /* /domains */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_consumer_output(struct config_writer *writer, - struct consumer_output *output) -{ - int ret; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(output); - - ret = config_writer_open_element(writer, config_element_consumer_output); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_bool(writer, config_element_enabled, - output->enabled); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_open_element(writer, config_element_destination); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - switch (output->type) { - case CONSUMER_DST_LOCAL: - ret = config_writer_write_element_string(writer, - config_element_path, output->dst.session_root_path); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - break; - case CONSUMER_DST_NET: - { - char *uri; - - uri = zmalloc(PATH_MAX); - if (!uri) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - - ret = config_writer_open_element(writer, config_element_net_output); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end_net_output; - } - - if (output->dst.net.control_isset && - output->dst.net.data_isset) { - ret = uri_to_str_url(&output->dst.net.control, uri, PATH_MAX); - if (ret < 0) { - ret = LTTNG_ERR_INVALID; - goto end_net_output; - } - - ret = config_writer_write_element_string(writer, - config_element_control_uri, uri); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end_net_output; - } - - ret = uri_to_str_url(&output->dst.net.data, uri, PATH_MAX); - if (ret < 0) { - ret = LTTNG_ERR_INVALID; - goto end_net_output; - } - - ret = config_writer_write_element_string(writer, - config_element_data_uri, uri); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end_net_output; - } - ret = LTTNG_OK; -end_net_output: - free(uri); - if (ret != LTTNG_OK) { - goto end; - } - } else { - ret = !output->dst.net.control_isset ? - LTTNG_ERR_URL_CTRL_MISS : - LTTNG_ERR_URL_DATA_MISS; - free(uri); - goto end; - } - - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - break; - } - default: - ERR("Unsupported consumer output type."); - ret = LTTNG_ERR_INVALID; - goto end; - } - - /* /destination */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* /consumer_output */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_snapshot_outputs(struct config_writer *writer, - struct snapshot *snapshot) -{ - int ret; - struct lttng_ht_iter iter; - struct snapshot_output *output; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(snapshot); - - ret = config_writer_open_element(writer, config_element_snapshot_outputs); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - rcu_read_lock(); - cds_lfht_for_each_entry(snapshot->output_ht->ht, &iter.iter, output, - node.node) { - ret = config_writer_open_element(writer, - config_element_output); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end_unlock; - } - - ret = config_writer_write_element_string(writer, - config_element_name, output->name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end_unlock; - } - - ret = config_writer_write_element_unsigned_int(writer, - config_element_max_size, output->max_size); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end_unlock; - } - - ret = save_consumer_output(writer, output->consumer); - if (ret != LTTNG_OK) { - goto end_unlock; - } - - /* /output */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end_unlock; - } - } - rcu_read_unlock(); - - /* /snapshot_outputs */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - return ret; -end_unlock: - rcu_read_unlock(); - return ret; -} - -/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ -static -int save_session_output(struct config_writer *writer, - struct ltt_session *session) -{ - int ret; - - LTTNG_ASSERT(writer); - LTTNG_ASSERT(session); - - if ((session->snapshot_mode && session->snapshot.nb_output == 0) || - (!session->snapshot_mode && !session->consumer)) { - /* Session is in no output mode */ - ret = LTTNG_OK; - goto end; - } - - ret = config_writer_open_element(writer, config_element_output); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (session->snapshot_mode) { - ret = save_snapshot_outputs(writer, &session->snapshot); - if (ret != LTTNG_OK) { - goto end; - } - } else { - if (session->consumer) { - ret = save_consumer_output(writer, session->consumer); - if (ret != LTTNG_OK) { - goto end; - } - } - } - - /* /output */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - ret = LTTNG_OK; -end: - return ret; -} - -static -int save_session_rotation_schedule(struct config_writer *writer, - enum lttng_rotation_schedule_type type, uint64_t value) -{ - int ret = 0; - const char *element_name; - const char *value_name; - - switch (type) { - case LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC: - element_name = config_element_rotation_schedule_periodic; - value_name = config_element_rotation_schedule_periodic_time_us; - break; - case LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD: - element_name = config_element_rotation_schedule_size_threshold; - value_name = config_element_rotation_schedule_size_threshold_bytes; - break; - default: - ret = -1; - goto end; - } - - ret = config_writer_open_element(writer, element_name); - if (ret) { - goto end; - } - - ret = config_writer_write_element_unsigned_int(writer, - value_name, value); - if (ret) { - goto end; - } - - /* Close schedule descriptor element. */ - ret = config_writer_close_element(writer); - if (ret) { - goto end; - } -end: - return ret; -} - -static -int save_session_rotation_schedules(struct config_writer *writer, - struct ltt_session *session) -{ - int ret; - - ret = config_writer_open_element(writer, - config_element_rotation_schedules); - if (ret) { - goto end; - } - if (session->rotate_timer_period) { - ret = save_session_rotation_schedule(writer, - LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC, - session->rotate_timer_period); - if (ret) { - goto close_schedules; - } - } - if (session->rotate_size) { - ret = save_session_rotation_schedule(writer, - LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD, - session->rotate_size); - if (ret) { - goto close_schedules; - } - } - -close_schedules: - /* Close rotation schedules element. */ - ret = config_writer_close_element(writer); - if (ret) { - goto end; - } -end: - return ret; -} - -/* - * Save the given session. - * - * Return LTTNG_OK on success else a LTTNG_ERR* code. - */ -static -int save_session(struct ltt_session *session, - struct lttng_save_session_attr *attr, lttng_sock_cred *creds) -{ - int ret, fd = -1; - char config_file_path[LTTNG_PATH_MAX]; - size_t len; - struct config_writer *writer = NULL; - size_t session_name_len; - const char *provided_path; - int file_open_flags = O_CREAT | O_WRONLY | O_TRUNC; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(attr); - LTTNG_ASSERT(creds); - - session_name_len = strlen(session->name); - memset(config_file_path, 0, sizeof(config_file_path)); - - if (!session_access_ok(session, - LTTNG_SOCK_GET_UID_CRED(creds)) || session->destroyed) { - ret = LTTNG_ERR_EPERM; - goto end; - } - - provided_path = lttng_save_session_attr_get_output_url(attr); - if (provided_path) { - DBG3("Save session in provided path %s", provided_path); - len = strlen(provided_path); - if (len >= sizeof(config_file_path)) { - ret = LTTNG_ERR_SET_URL; - goto end; - } - strncpy(config_file_path, provided_path, sizeof(config_file_path)); - } else { - ssize_t ret_len; - char *home_dir = utils_get_user_home_dir( - LTTNG_SOCK_GET_UID_CRED(creds)); - if (!home_dir) { - ret = LTTNG_ERR_SET_URL; - goto end; - } - - ret_len = snprintf(config_file_path, sizeof(config_file_path), - DEFAULT_SESSION_HOME_CONFIGPATH, home_dir); - free(home_dir); - if (ret_len < 0) { - PERROR("snprintf save session"); - ret = LTTNG_ERR_SET_URL; - goto end; - } - len = ret_len; - } - - /* - * Check the path fits in the config file path dst including the '/' - * followed by trailing .lttng extension and the NULL terminated string. - */ - if ((len + session_name_len + 2 + - sizeof(DEFAULT_SESSION_CONFIG_FILE_EXTENSION)) - > sizeof(config_file_path)) { - ret = LTTNG_ERR_SET_URL; - goto end; - } - - ret = run_as_mkdir_recursive(config_file_path, S_IRWXU | S_IRWXG, - LTTNG_SOCK_GET_UID_CRED(creds), LTTNG_SOCK_GET_GID_CRED(creds)); - if (ret) { - ret = LTTNG_ERR_SET_URL; - goto end; - } - - /* - * At this point, we know that everything fits in the buffer. Validation - * was done just above. - */ - config_file_path[len++] = '/'; - strncpy(config_file_path + len, session->name, sizeof(config_file_path) - len); - len += session_name_len; - strcpy(config_file_path + len, DEFAULT_SESSION_CONFIG_FILE_EXTENSION); - len += sizeof(DEFAULT_SESSION_CONFIG_FILE_EXTENSION); - config_file_path[len] = '\0'; - - if (!attr->overwrite) { - file_open_flags |= O_EXCL; - } - - fd = run_as_open(config_file_path, file_open_flags, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, - LTTNG_SOCK_GET_UID_CRED(creds), LTTNG_SOCK_GET_GID_CRED(creds)); - if (fd < 0) { - PERROR("Could not create configuration file"); - switch (errno) { - case EEXIST: - ret = LTTNG_ERR_SAVE_FILE_EXIST; - break; - case EACCES: - ret = LTTNG_ERR_EPERM; - break; - default: - ret = LTTNG_ERR_SAVE_IO_FAIL; - break; - } - goto end; - } - - writer = config_writer_create(fd, 1); - if (!writer) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - - ret = config_writer_open_element(writer, config_element_sessions); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_open_element(writer, config_element_session); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = config_writer_write_element_string(writer, config_element_name, - session->name); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (session->shm_path[0] != '\0') { - ret = config_writer_write_element_string(writer, - config_element_shared_memory_path, - session->shm_path); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - ret = save_domains(writer, session); - if (ret != LTTNG_OK) { - goto end; - } - - ret = config_writer_write_element_bool(writer, config_element_started, - session->active); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (session->snapshot_mode || session->live_timer || - session->rotate_timer_period || session->rotate_size) { - ret = config_writer_open_element(writer, config_element_attributes); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - if (session->snapshot_mode) { - ret = config_writer_write_element_bool(writer, - config_element_snapshot_mode, 1); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } else if (session->live_timer) { - ret = config_writer_write_element_unsigned_int(writer, - config_element_live_timer_interval, session->live_timer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - if (session->rotate_timer_period || session->rotate_size) { - ret = save_session_rotation_schedules(writer, - session); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - /* /attributes */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - } - - ret = save_session_output(writer, session); - if (ret != LTTNG_OK) { - goto end; - } - - /* /session */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - /* /sessions */ - ret = config_writer_close_element(writer); - if (ret) { - ret = LTTNG_ERR_SAVE_IO_FAIL; - goto end; - } - - ret = LTTNG_OK; -end: - if (writer && config_writer_destroy(writer)) { - /* Preserve the original error code */ - ret = ret != LTTNG_OK ? ret : LTTNG_ERR_SAVE_IO_FAIL; - } - if (ret != LTTNG_OK) { - /* Delete file in case of error */ - if ((fd >= 0) && unlink(config_file_path)) { - PERROR("Unlinking XML session configuration."); - } - } - - if (fd >= 0) { - int closeret; - - closeret = close(fd); - if (closeret) { - PERROR("Closing XML session configuration"); - } - } - - return ret; -} - -int cmd_save_sessions(struct lttng_save_session_attr *attr, - lttng_sock_cred *creds) -{ - int ret; - const char *session_name; - struct ltt_session *session; - - session_lock_list(); - - session_name = lttng_save_session_attr_get_session_name(attr); - if (session_name) { - session = session_find_by_name(session_name); - if (!session) { - ret = LTTNG_ERR_SESS_NOT_FOUND; - goto end; - } - - session_lock(session); - ret = save_session(session, attr, creds); - session_unlock(session); - session_put(session); - if (ret != LTTNG_OK) { - goto end; - } - } else { - struct ltt_session_list *list = session_get_list(); - - cds_list_for_each_entry(session, &list->head, list) { - if (!session_get(session)) { - continue; - } - session_lock(session); - ret = save_session(session, attr, creds); - session_unlock(session); - session_put(session); - /* Don't abort if we don't have the required permissions. */ - if (ret != LTTNG_OK && ret != LTTNG_ERR_EPERM) { - goto end; - } - } - } - ret = LTTNG_OK; - -end: - session_unlock_list(); - return ret; -} diff --git a/src/bin/lttng-sessiond/save.cpp b/src/bin/lttng-sessiond/save.cpp new file mode 100644 index 000000000..2055e867d --- /dev/null +++ b/src/bin/lttng-sessiond/save.cpp @@ -0,0 +1,2897 @@ +/* + * Copyright (C) 2014 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kernel.h" +#include "save.h" +#include "session.h" +#include "lttng-syscall.h" +#include "trace-ust.h" +#include "agent.h" + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_kernel_channel_attributes(struct config_writer *writer, + struct lttng_channel_attr *attr) +{ + int ret; + + ret = config_writer_write_element_string(writer, + config_element_overwrite_mode, + attr->overwrite ? config_overwrite_mode_overwrite : + config_overwrite_mode_discard); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_subbuf_size, attr->subbuf_size); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_num_subbuf, + attr->num_subbuf); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_switch_timer_interval, + attr->switch_timer_interval); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_read_timer_interval, + attr->read_timer_interval); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_output_type, + attr->output == LTTNG_EVENT_SPLICE ? + config_output_type_splice : config_output_type_mmap); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_tracefile_size, attr->tracefile_size); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_tracefile_count, + attr->tracefile_count); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_live_timer_interval, + attr->live_timer_interval); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (attr->extended.ptr) { + struct lttng_channel_extended *ext = NULL; + + ext = (struct lttng_channel_extended *) attr->extended.ptr; + ret = config_writer_write_element_unsigned_int(writer, + config_element_monitor_timer_interval, + ext->monitor_timer_interval); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_signed_int(writer, + config_element_blocking_timeout, + ext->blocking_timeout); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_ust_channel_attributes(struct config_writer *writer, + struct lttng_ust_abi_channel_attr *attr) +{ + int ret; + struct ltt_ust_channel *channel = NULL; + + ret = config_writer_write_element_string(writer, + config_element_overwrite_mode, + attr->overwrite ? config_overwrite_mode_overwrite : + config_overwrite_mode_discard); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_subbuf_size, attr->subbuf_size); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_num_subbuf, + attr->num_subbuf); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_switch_timer_interval, + attr->switch_timer_interval); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_read_timer_interval, + attr->read_timer_interval); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_output_type, + attr->output == LTTNG_UST_ABI_MMAP ? + config_output_type_mmap : config_output_type_splice); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_signed_int(writer, + config_element_blocking_timeout, + attr->u.s.blocking_timeout); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* + * Fetch the monitor timer which is located in the parent of + * lttng_ust_channel_attr + */ + channel = caa_container_of(attr, struct ltt_ust_channel, attr); + ret = config_writer_write_element_unsigned_int(writer, + config_element_monitor_timer_interval, + channel->monitor_timer_interval); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +static +const char *get_kernel_instrumentation_string( + enum lttng_kernel_abi_instrumentation instrumentation) +{ + const char *instrumentation_string; + + switch (instrumentation) { + case LTTNG_KERNEL_ABI_ALL: + instrumentation_string = config_event_type_all; + break; + case LTTNG_KERNEL_ABI_TRACEPOINT: + instrumentation_string = config_event_type_tracepoint; + break; + case LTTNG_KERNEL_ABI_KPROBE: + instrumentation_string = config_event_type_probe; + break; + case LTTNG_KERNEL_ABI_UPROBE: + instrumentation_string = config_event_type_userspace_probe; + break; + case LTTNG_KERNEL_ABI_FUNCTION: + instrumentation_string = config_event_type_function_entry; + break; + case LTTNG_KERNEL_ABI_KRETPROBE: + instrumentation_string = config_event_type_function; + break; + case LTTNG_KERNEL_ABI_NOOP: + instrumentation_string = config_event_type_noop; + break; + case LTTNG_KERNEL_ABI_SYSCALL: + instrumentation_string = config_event_type_syscall; + break; + default: + instrumentation_string = NULL; + } + + return instrumentation_string; +} + +static +const char *get_kernel_context_type_string( + enum lttng_kernel_abi_context_type context_type) +{ + const char *context_type_string; + + switch (context_type) { + case LTTNG_KERNEL_ABI_CONTEXT_PID: + context_type_string = config_event_context_pid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_PROCNAME: + context_type_string = config_event_context_procname; + break; + case LTTNG_KERNEL_ABI_CONTEXT_PRIO: + context_type_string = config_event_context_prio; + break; + case LTTNG_KERNEL_ABI_CONTEXT_NICE: + context_type_string = config_event_context_nice; + break; + case LTTNG_KERNEL_ABI_CONTEXT_VPID: + context_type_string = config_event_context_vpid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_TID: + context_type_string = config_event_context_tid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_VTID: + context_type_string = config_event_context_vtid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_PPID: + context_type_string = config_event_context_ppid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_VPPID: + context_type_string = config_event_context_vppid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_HOSTNAME: + context_type_string = config_event_context_hostname; + break; + case LTTNG_KERNEL_ABI_CONTEXT_INTERRUPTIBLE: + context_type_string = config_event_context_interruptible; + break; + case LTTNG_KERNEL_ABI_CONTEXT_PREEMPTIBLE: + context_type_string = config_event_context_preemptible; + break; + case LTTNG_KERNEL_ABI_CONTEXT_NEED_RESCHEDULE: + context_type_string = config_event_context_need_reschedule; + break; + case LTTNG_KERNEL_ABI_CONTEXT_MIGRATABLE: + context_type_string = config_event_context_migratable; + break; + case LTTNG_KERNEL_ABI_CONTEXT_CALLSTACK_USER: + context_type_string = config_event_context_callstack_user; + break; + case LTTNG_KERNEL_ABI_CONTEXT_CALLSTACK_KERNEL: + context_type_string = config_event_context_callstack_kernel; + break; + case LTTNG_KERNEL_ABI_CONTEXT_CGROUP_NS: + context_type_string = config_event_context_cgroup_ns; + break; + case LTTNG_KERNEL_ABI_CONTEXT_IPC_NS: + context_type_string = config_event_context_ipc_ns; + break; + case LTTNG_KERNEL_ABI_CONTEXT_MNT_NS: + context_type_string = config_event_context_mnt_ns; + break; + case LTTNG_KERNEL_ABI_CONTEXT_NET_NS: + context_type_string = config_event_context_net_ns; + break; + case LTTNG_KERNEL_ABI_CONTEXT_PID_NS: + context_type_string = config_event_context_pid_ns; + break; + case LTTNG_KERNEL_ABI_CONTEXT_TIME_NS: + context_type_string = config_event_context_time_ns; + break; + case LTTNG_KERNEL_ABI_CONTEXT_USER_NS: + context_type_string = config_event_context_user_ns; + break; + case LTTNG_KERNEL_ABI_CONTEXT_UTS_NS: + context_type_string = config_event_context_uts_ns; + break; + case LTTNG_KERNEL_ABI_CONTEXT_UID: + context_type_string = config_event_context_uid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_EUID: + context_type_string = config_event_context_euid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_SUID: + context_type_string = config_event_context_suid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_GID: + context_type_string = config_event_context_gid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_EGID: + context_type_string = config_event_context_egid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_SGID: + context_type_string = config_event_context_sgid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_VUID: + context_type_string = config_event_context_vuid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_VEUID: + context_type_string = config_event_context_veuid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_VSUID: + context_type_string = config_event_context_vsuid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_VGID: + context_type_string = config_event_context_vgid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_VEGID: + context_type_string = config_event_context_vegid; + break; + case LTTNG_KERNEL_ABI_CONTEXT_VSGID: + context_type_string = config_event_context_vsgid; + break; + default: + context_type_string = NULL; + } + + return context_type_string; +} + +static +const char *get_ust_context_type_string( + enum lttng_ust_abi_context_type context_type) +{ + const char *context_type_string; + + switch (context_type) { + case LTTNG_UST_ABI_CONTEXT_PROCNAME: + context_type_string = config_event_context_procname; + break; + case LTTNG_UST_ABI_CONTEXT_VPID: + context_type_string = config_event_context_vpid; + break; + case LTTNG_UST_ABI_CONTEXT_VTID: + context_type_string = config_event_context_vtid; + break; + case LTTNG_UST_ABI_CONTEXT_IP: + context_type_string = config_event_context_ip; + break; + case LTTNG_UST_ABI_CONTEXT_PTHREAD_ID: + context_type_string = config_event_context_pthread_id; + break; + case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: + context_type_string = config_event_context_app; + break; + case LTTNG_UST_ABI_CONTEXT_CGROUP_NS: + context_type_string = config_event_context_cgroup_ns; + break; + case LTTNG_UST_ABI_CONTEXT_IPC_NS: + context_type_string = config_event_context_ipc_ns; + break; + case LTTNG_UST_ABI_CONTEXT_MNT_NS: + context_type_string = config_event_context_mnt_ns; + break; + case LTTNG_UST_ABI_CONTEXT_NET_NS: + context_type_string = config_event_context_net_ns; + break; + case LTTNG_UST_ABI_CONTEXT_TIME_NS: + context_type_string = config_event_context_time_ns; + break; + case LTTNG_UST_ABI_CONTEXT_PID_NS: + context_type_string = config_event_context_pid_ns; + break; + case LTTNG_UST_ABI_CONTEXT_USER_NS: + context_type_string = config_event_context_user_ns; + break; + case LTTNG_UST_ABI_CONTEXT_UTS_NS: + context_type_string = config_event_context_uts_ns; + break; + case LTTNG_UST_ABI_CONTEXT_VUID: + context_type_string = config_event_context_vuid; + break; + case LTTNG_UST_ABI_CONTEXT_VEUID: + context_type_string = config_event_context_veuid; + break; + case LTTNG_UST_ABI_CONTEXT_VSUID: + context_type_string = config_event_context_vsuid; + break; + case LTTNG_UST_ABI_CONTEXT_VGID: + context_type_string = config_event_context_vgid; + break; + case LTTNG_UST_ABI_CONTEXT_VEGID: + context_type_string = config_event_context_vegid; + break; + case LTTNG_UST_ABI_CONTEXT_VSGID: + context_type_string = config_event_context_vsgid; + break; + case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: + /* + * Error, should not be stored in the XML, perf contexts + * are stored as a node of type event_perf_context_type. + */ + default: + context_type_string = NULL; + break; + } + + return context_type_string; +} + +static +const char *get_buffer_type_string( + enum lttng_buffer_type buffer_type) +{ + const char *buffer_type_string; + + switch (buffer_type) { + case LTTNG_BUFFER_PER_PID: + buffer_type_string = config_buffer_type_per_pid; + break; + case LTTNG_BUFFER_PER_UID: + buffer_type_string = config_buffer_type_per_uid; + break; + case LTTNG_BUFFER_GLOBAL: + buffer_type_string = config_buffer_type_global; + break; + default: + buffer_type_string = NULL; + } + + return buffer_type_string; +} + +static +const char *get_loglevel_type_string( + enum lttng_ust_abi_loglevel_type loglevel_type) +{ + const char *loglevel_type_string; + + switch (loglevel_type) { + case LTTNG_UST_ABI_LOGLEVEL_ALL: + loglevel_type_string = config_loglevel_type_all; + break; + case LTTNG_UST_ABI_LOGLEVEL_RANGE: + loglevel_type_string = config_loglevel_type_range; + break; + case LTTNG_UST_ABI_LOGLEVEL_SINGLE: + loglevel_type_string = config_loglevel_type_single; + break; + default: + loglevel_type_string = NULL; + } + + return loglevel_type_string; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_kernel_function_event(struct config_writer *writer, + struct ltt_kernel_event *event) +{ + int ret; + + ret = config_writer_open_element(writer, config_element_function_attributes); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, config_element_name, + event->event->u.ftrace.symbol_name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* /function attributes */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } +end: + return ret; +} + +static +int save_kernel_kprobe_event(struct config_writer *writer, + struct ltt_kernel_event *event) +{ + int ret; + const char *symbol_name; + uint64_t addr; + uint64_t offset; + + switch (event->event->instrumentation) { + case LTTNG_KERNEL_ABI_KPROBE: + /* + * Comments in lttng-kernel.h mention that + * either addr or symbol_name are set, not both. + */ + addr = event->event->u.kprobe.addr; + offset = event->event->u.kprobe.offset; + symbol_name = addr ? NULL : event->event->u.kprobe.symbol_name; + break; + case LTTNG_KERNEL_ABI_KRETPROBE: + addr = event->event->u.kretprobe.addr; + offset = event->event->u.kretprobe.offset; + symbol_name = addr ? NULL : event->event->u.kretprobe.symbol_name; + break; + default: + LTTNG_ASSERT(1); + ERR("Unsupported kernel instrumentation type."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + ret = config_writer_open_element(writer, config_element_probe_attributes); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (addr) { + ret = config_writer_write_element_unsigned_int( writer, + config_element_address, addr); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } else if (symbol_name) { + ret = config_writer_write_element_string(writer, + config_element_symbol_name, symbol_name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + /* If the offset is non-zero, write it.*/ + if (offset) { + ret = config_writer_write_element_unsigned_int(writer, + config_element_offset, offset); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + } else { + /* + * This really should not happen as we are either setting the + * address or the symbol above. + */ + ERR("Invalid probe/function description."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } +end: + return ret; +} + +/* + * Save the userspace probe tracepoint event associated with the event to the + * config writer. + */ +static +int save_kernel_userspace_probe_tracepoint_event(struct config_writer *writer, + struct ltt_kernel_event *event) +{ + int ret = 0; + const char *probe_name, *provider_name, *binary_path; + const struct lttng_userspace_probe_location *userspace_probe_location; + const struct lttng_userspace_probe_location_lookup_method *lookup_method; + enum lttng_userspace_probe_location_lookup_method_type lookup_type; + + /* Get userspace probe location from the event. */ + userspace_probe_location = event->userspace_probe_location; + if (!userspace_probe_location) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* Get lookup method and lookup method type. */ + lookup_method = lttng_userspace_probe_location_get_lookup_method(userspace_probe_location); + if (!lookup_method) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + lookup_type = lttng_userspace_probe_location_lookup_method_get_type(lookup_method); + + /* Get the binary path, probe name and provider name. */ + binary_path = + lttng_userspace_probe_location_tracepoint_get_binary_path( + userspace_probe_location); + if (!binary_path) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + probe_name = + lttng_userspace_probe_location_tracepoint_get_probe_name( + userspace_probe_location); + if (!probe_name) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + provider_name = + lttng_userspace_probe_location_tracepoint_get_provider_name( + userspace_probe_location); + if (!provider_name) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* Open a userspace probe tracepoint attribute. */ + ret = config_writer_open_element(writer, config_element_userspace_probe_tracepoint_attributes); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + switch (lookup_type) { + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: + ret = config_writer_write_element_string(writer, + config_element_userspace_probe_lookup, + config_element_userspace_probe_lookup_tracepoint_sdt); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + break; + default: + ERR("Unsupported kernel userspace probe tracepoint lookup method."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + /* Write the binary path, provider name and the probe name. */ + ret = config_writer_write_element_string(writer, + config_element_userspace_probe_location_binary_path, + binary_path); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_userspace_probe_tracepoint_location_provider_name, + provider_name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_userspace_probe_tracepoint_location_probe_name, + probe_name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* Close the userspace probe tracepoint attribute. */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + +end: + return ret; +} + +/* + * Save the userspace probe function event associated with the event to the + * config writer. + */ +static +int save_kernel_userspace_probe_function_event(struct config_writer *writer, + struct ltt_kernel_event *event) +{ + int ret = 0; + const char *function_name, *binary_path; + const struct lttng_userspace_probe_location *userspace_probe_location; + const struct lttng_userspace_probe_location_lookup_method *lookup_method; + enum lttng_userspace_probe_location_lookup_method_type lookup_type; + + /* Get userspace probe location from the event. */ + userspace_probe_location = event->userspace_probe_location; + if (!userspace_probe_location) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* Get lookup method and lookup method type. */ + lookup_method = lttng_userspace_probe_location_get_lookup_method( + userspace_probe_location); + if (!lookup_method) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* Get the binary path and the function name. */ + binary_path = + lttng_userspace_probe_location_function_get_binary_path( + userspace_probe_location); + if (!binary_path) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + function_name = + lttng_userspace_probe_location_function_get_function_name( + userspace_probe_location); + if (!function_name) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* Open a userspace probe function attribute. */ + ret = config_writer_open_element(writer, + config_element_userspace_probe_function_attributes); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + lookup_type = lttng_userspace_probe_location_lookup_method_get_type(lookup_method); + switch (lookup_type) { + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_ELF: + ret = config_writer_write_element_string(writer, + config_element_userspace_probe_lookup, + config_element_userspace_probe_lookup_function_elf); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + break; + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_FUNCTION_DEFAULT: + ret = config_writer_write_element_string(writer, + config_element_userspace_probe_lookup, + config_element_userspace_probe_lookup_function_default); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + break; + default: + ERR("Unsupported kernel userspace probe function lookup method."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + /* Write the binary path and the function name. */ + ret = config_writer_write_element_string(writer, + config_element_userspace_probe_location_binary_path, + binary_path); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_userspace_probe_function_location_function_name, + function_name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* Close the userspace probe function attribute. */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + +end: + return ret; +} + +static +int save_kernel_userspace_probe_event(struct config_writer *writer, + struct ltt_kernel_event *event) +{ + int ret; + struct lttng_userspace_probe_location *userspace_probe_location; + + /* Get userspace probe location from the event. */ + userspace_probe_location = event->userspace_probe_location; + if (!userspace_probe_location) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + switch(lttng_userspace_probe_location_get_type(userspace_probe_location)) { + case LTTNG_USERSPACE_PROBE_LOCATION_TYPE_FUNCTION: + { + ret = save_kernel_userspace_probe_function_event(writer, event); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + break; + } + case LTTNG_USERSPACE_PROBE_LOCATION_TYPE_TRACEPOINT: + { + ret = save_kernel_userspace_probe_tracepoint_event(writer, event); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + break; + } + case LTTNG_USERSPACE_PROBE_LOCATION_TYPE_UNKNOWN: + default: + ERR("Unsupported kernel userspace probe location type."); + ret = LTTNG_ERR_INVALID; + goto end; + } + +end: + return ret; +} + +static +int save_kernel_event(struct config_writer *writer, + struct ltt_kernel_event *event) +{ + int ret; + const char *instrumentation_type; + + ret = config_writer_open_element(writer, config_element_event); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (event->event->name[0]) { + ret = config_writer_write_element_string(writer, + config_element_name, event->event->name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + ret = config_writer_write_element_bool(writer, config_element_enabled, + event->enabled); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + instrumentation_type = get_kernel_instrumentation_string( + event->event->instrumentation); + if (!instrumentation_type) { + ret = LTTNG_ERR_INVALID; + goto end; + } + + ret = config_writer_write_element_string(writer, config_element_type, + instrumentation_type); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (event->filter_expression) { + ret = config_writer_write_element_string(writer, + config_element_filter, + event->filter_expression); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + if (event->event->instrumentation == LTTNG_KERNEL_ABI_FUNCTION || + event->event->instrumentation == LTTNG_KERNEL_ABI_KPROBE || + event->event->instrumentation == LTTNG_KERNEL_ABI_UPROBE || + event->event->instrumentation == LTTNG_KERNEL_ABI_KRETPROBE) { + + ret = config_writer_open_element(writer, + config_element_attributes); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + switch (event->event->instrumentation) { + case LTTNG_KERNEL_ABI_SYSCALL: + case LTTNG_KERNEL_ABI_FUNCTION: + ret = save_kernel_function_event(writer, event); + if (ret) { + goto end; + } + break; + case LTTNG_KERNEL_ABI_KPROBE: + case LTTNG_KERNEL_ABI_KRETPROBE: + ret = save_kernel_kprobe_event(writer, event); + if (ret) { + goto end; + } + break; + case LTTNG_KERNEL_ABI_UPROBE: + ret = save_kernel_userspace_probe_event(writer, event); + if (ret) { + goto end; + } + break; + default: + ERR("Unsupported kernel instrumentation type."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + /* /attributes */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + /* /event */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_kernel_events(struct config_writer *writer, + struct ltt_kernel_channel *kchan) +{ + int ret; + struct ltt_kernel_event *event; + + ret = config_writer_open_element(writer, config_element_events); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + cds_list_for_each_entry(event, &kchan->events_list.head, list) { + ret = save_kernel_event(writer, event); + if (ret != LTTNG_OK) { + goto end; + } + } + + /* /events */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_ust_event(struct config_writer *writer, + struct ltt_ust_event *event) +{ + int ret; + const char *loglevel_type_string; + + ret = config_writer_open_element(writer, config_element_event); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (event->attr.name[0]) { + ret = config_writer_write_element_string(writer, + config_element_name, event->attr.name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + ret = config_writer_write_element_bool(writer, config_element_enabled, + event->enabled); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (event->attr.instrumentation != LTTNG_UST_ABI_TRACEPOINT) { + ERR("Unsupported UST instrumentation type."); + ret = LTTNG_ERR_INVALID; + goto end; + } + ret = config_writer_write_element_string(writer, config_element_type, + config_event_type_tracepoint); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + loglevel_type_string = get_loglevel_type_string( + (lttng_ust_abi_loglevel_type) event->attr.loglevel_type); + if (!loglevel_type_string) { + ERR("Unsupported UST loglevel type."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_loglevel_type, loglevel_type_string); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* The log level is irrelevant if no "filtering" is enabled */ + if (event->attr.loglevel_type != LTTNG_UST_ABI_LOGLEVEL_ALL) { + ret = config_writer_write_element_signed_int(writer, + config_element_loglevel, event->attr.loglevel); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + if (event->filter_expression) { + ret = config_writer_write_element_string(writer, + config_element_filter, event->filter_expression); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + if (event->exclusion && event->exclusion->count) { + uint32_t i; + + ret = config_writer_open_element(writer, + config_element_exclusions); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + for (i = 0; i < event->exclusion->count; i++) { + ret = config_writer_write_element_string(writer, + config_element_exclusion, + LTTNG_EVENT_EXCLUSION_NAME_AT( + event->exclusion, i)); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + /* /exclusions */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + /* /event */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_ust_events(struct config_writer *writer, + struct lttng_ht *events) +{ + int ret; + struct ltt_ust_event *event; + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + + ret = config_writer_open_element(writer, config_element_events); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + rcu_read_lock(); + cds_lfht_for_each_entry(events->ht, &iter.iter, node, node) { + event = caa_container_of(node, struct ltt_ust_event, node); + + if (event->internal) { + /* Internal events must not be exposed to clients */ + continue; + } + ret = save_ust_event(writer, event); + if (ret != LTTNG_OK) { + rcu_read_unlock(); + goto end; + } + } + rcu_read_unlock(); + + /* /events */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int init_ust_event_from_agent_event(struct ltt_ust_event *ust_event, + struct agent_event *agent_event) +{ + int ret; + enum lttng_ust_abi_loglevel_type ust_loglevel_type; + + ust_event->enabled = AGENT_EVENT_IS_ENABLED(agent_event); + ust_event->attr.instrumentation = LTTNG_UST_ABI_TRACEPOINT; + if (lttng_strncpy(ust_event->attr.name, agent_event->name, + LTTNG_SYMBOL_NAME_LEN)) { + ret = LTTNG_ERR_INVALID; + goto end; + } + switch (agent_event->loglevel_type) { + case LTTNG_EVENT_LOGLEVEL_ALL: + ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_ALL; + break; + case LTTNG_EVENT_LOGLEVEL_SINGLE: + ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_SINGLE; + break; + case LTTNG_EVENT_LOGLEVEL_RANGE: + ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_RANGE; + break; + default: + ERR("Invalid agent_event loglevel_type."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + ust_event->attr.loglevel_type = ust_loglevel_type; + ust_event->attr.loglevel = agent_event->loglevel_value; + ust_event->filter_expression = agent_event->filter_expression; + ust_event->exclusion = agent_event->exclusion; + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_agent_events(struct config_writer *writer, + struct agent *agent) +{ + int ret; + struct lttng_ht_iter iter; + struct lttng_ht_node_str *node; + + ret = config_writer_open_element(writer, config_element_events); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + rcu_read_lock(); + cds_lfht_for_each_entry(agent->events->ht, &iter.iter, node, node) { + struct agent_event *agent_event; + struct ltt_ust_event fake_event; + + memset(&fake_event, 0, sizeof(fake_event)); + agent_event = caa_container_of(node, struct agent_event, node); + + /* + * Initialize a fake ust event to reuse the same serialization + * function since UST and agent events contain the same info + * (and one could wonder why they don't reuse the same + * structures...). + */ + ret = init_ust_event_from_agent_event(&fake_event, agent_event); + if (ret != LTTNG_OK) { + rcu_read_unlock(); + goto end; + } + ret = save_ust_event(writer, &fake_event); + if (ret != LTTNG_OK) { + rcu_read_unlock(); + goto end; + } + } + rcu_read_unlock(); + + /* /events */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_kernel_context(struct config_writer *writer, + struct lttng_kernel_abi_context *ctx) +{ + int ret = LTTNG_OK; + + if (!ctx) { + goto end; + } + + ret = config_writer_open_element(writer, config_element_context); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (ctx->ctx == LTTNG_KERNEL_ABI_CONTEXT_PERF_CPU_COUNTER) { + ret = config_writer_open_element(writer, + config_element_context_perf); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_type, ctx->u.perf_counter.type); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_config, ctx->u.perf_counter.config); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_name, ctx->u.perf_counter.name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* /perf */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } else { + const char *context_type_string = + get_kernel_context_type_string(ctx->ctx); + + if (!context_type_string) { + ERR("Unsupported kernel context type."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_type, context_type_string); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + /* /context */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_kernel_contexts(struct config_writer *writer, + struct ltt_kernel_channel *kchan) +{ + int ret; + struct ltt_kernel_context *ctx; + + if (cds_list_empty(&kchan->ctx_list)) { + ret = LTTNG_OK; + goto end; + } + + ret = config_writer_open_element(writer, config_element_contexts); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + cds_list_for_each_entry(ctx, &kchan->ctx_list, list) { + ret = save_kernel_context(writer, &ctx->ctx); + if (ret != LTTNG_OK) { + goto end; + } + } + + /* /contexts */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_ust_context_perf_thread_counter(struct config_writer *writer, + struct ltt_ust_context *ctx) +{ + int ret; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(ctx); + + /* Perf contexts are saved as event_perf_context_type */ + ret = config_writer_open_element(writer, config_element_context_perf); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_type, ctx->ctx.u.perf_counter.type); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_config, ctx->ctx.u.perf_counter.config); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, config_element_name, + ctx->ctx.u.perf_counter.name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* /perf */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_ust_context_app_ctx(struct config_writer *writer, + struct ltt_ust_context *ctx) +{ + int ret; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(ctx); + + /* Application contexts are saved as application_context_type */ + ret = config_writer_open_element(writer, config_element_context_app); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_context_app_provider_name, + ctx->ctx.u.app_ctx.provider_name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_context_app_ctx_name, + ctx->ctx.u.app_ctx.ctx_name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* /app */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_ust_context_generic(struct config_writer *writer, + struct ltt_ust_context *ctx) +{ + int ret; + const char *context_type_string; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(ctx); + + /* Save context as event_context_type_type */ + context_type_string = get_ust_context_type_string( + ctx->ctx.ctx); + if (!context_type_string) { + ERR("Unsupported UST context type."); + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_type, context_type_string); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_ust_context(struct config_writer *writer, + struct cds_list_head *ctx_list) +{ + int ret; + struct ltt_ust_context *ctx; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(ctx_list); + + ret = config_writer_open_element(writer, config_element_contexts); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + cds_list_for_each_entry(ctx, ctx_list, list) { + ret = config_writer_open_element(writer, + config_element_context); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + switch (ctx->ctx.ctx) { + case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: + ret = save_ust_context_perf_thread_counter(writer, ctx); + break; + case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: + ret = save_ust_context_app_ctx(writer, ctx); + break; + default: + /* Save generic context. */ + ret = save_ust_context_generic(writer, ctx); + } + if (ret != LTTNG_OK) { + goto end; + } + + /* /context */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + /* /contexts */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_kernel_channel(struct config_writer *writer, + struct ltt_kernel_channel *kchan) +{ + int ret; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(kchan); + + ret = config_writer_open_element(writer, config_element_channel); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, config_element_name, + kchan->channel->name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_bool(writer, config_element_enabled, + kchan->channel->enabled); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = save_kernel_channel_attributes(writer, &kchan->channel->attr); + if (ret != LTTNG_OK) { + goto end; + } + + ret = save_kernel_events(writer, kchan); + if (ret != LTTNG_OK) { + goto end; + } + + ret = save_kernel_contexts(writer, kchan); + if (ret != LTTNG_OK) { + goto end; + } + + /* /channel */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_ust_channel(struct config_writer *writer, + struct ltt_ust_channel *ust_chan, + struct ltt_ust_session *session) +{ + int ret; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(ust_chan); + LTTNG_ASSERT(session); + + ret = config_writer_open_element(writer, config_element_channel); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, config_element_name, + ust_chan->name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_bool(writer, config_element_enabled, + ust_chan->enabled); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = save_ust_channel_attributes(writer, &ust_chan->attr); + if (ret != LTTNG_OK) { + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_tracefile_size, ust_chan->tracefile_size); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_tracefile_count, ust_chan->tracefile_count); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_live_timer_interval, + session->live_timer_interval); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (ust_chan->domain == LTTNG_DOMAIN_UST) { + ret = save_ust_events(writer, ust_chan->events); + if (ret != LTTNG_OK) { + goto end; + } + } else { + struct agent *agent = NULL; + + agent = trace_ust_find_agent(session, ust_chan->domain); + if (!agent) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + ERR("Could not find agent associated to UST subdomain"); + goto end; + } + + /* + * Channels associated with a UST sub-domain (such as JUL, Log4j + * or Python) don't have any non-internal events. We retrieve + * the "agent" events associated with this channel and serialize + * them. + */ + ret = save_agent_events(writer, agent); + if (ret != LTTNG_OK) { + goto end; + } + } + + ret = save_ust_context(writer, &ust_chan->ctx_list); + if (ret != LTTNG_OK) { + goto end; + } + + /* /channel */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_kernel_session(struct config_writer *writer, + struct ltt_session *session) +{ + int ret; + struct ltt_kernel_channel *kchan; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(session); + + ret = config_writer_write_element_string(writer, config_element_type, + config_domain_type_kernel); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_buffer_type, config_buffer_type_global); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_open_element(writer, + config_element_channels); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + cds_list_for_each_entry(kchan, &session->kernel_session->channel_list.head, + list) { + ret = save_kernel_channel(writer, kchan); + if (ret != LTTNG_OK) { + goto end; + } + } + + /* /channels */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +static +const char *get_config_domain_str(enum lttng_domain_type domain) +{ + const char *str_dom; + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + str_dom = config_domain_type_kernel; + break; + case LTTNG_DOMAIN_UST: + str_dom = config_domain_type_ust; + break; + case LTTNG_DOMAIN_JUL: + str_dom = config_domain_type_jul; + break; + case LTTNG_DOMAIN_LOG4J: + str_dom = config_domain_type_log4j; + break; + case LTTNG_DOMAIN_PYTHON: + str_dom = config_domain_type_python; + break; + default: + abort(); + } + + return str_dom; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static int save_process_attr_tracker(struct config_writer *writer, + struct ltt_session *sess, + int domain, + enum lttng_process_attr process_attr) +{ + int ret = LTTNG_OK; + const char *element_id_tracker, *element_target_id, *element_id; + const struct process_attr_tracker *tracker; + enum lttng_tracking_policy tracking_policy; + struct lttng_process_attr_values *values = NULL; + + switch (process_attr) { + case LTTNG_PROCESS_ATTR_PROCESS_ID: + element_id_tracker = config_element_process_attr_tracker_pid; + element_target_id = config_element_process_attr_pid_value; + element_id = config_element_process_attr_id; + break; + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + element_id_tracker = config_element_process_attr_tracker_vpid; + element_target_id = config_element_process_attr_vpid_value; + element_id = config_element_process_attr_id; + break; + case LTTNG_PROCESS_ATTR_USER_ID: + element_id_tracker = config_element_process_attr_tracker_uid; + element_target_id = config_element_process_attr_uid_value; + element_id = config_element_process_attr_id; + break; + case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: + element_id_tracker = config_element_process_attr_tracker_vuid; + element_target_id = config_element_process_attr_vuid_value; + element_id = config_element_process_attr_id; + break; + case LTTNG_PROCESS_ATTR_GROUP_ID: + element_id_tracker = config_element_process_attr_tracker_gid; + element_target_id = config_element_process_attr_gid_value; + element_id = config_element_process_attr_id; + break; + case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: + element_id_tracker = config_element_process_attr_tracker_vgid; + element_target_id = config_element_process_attr_vgid_value; + element_id = config_element_process_attr_id; + break; + default: + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + { + tracker = kernel_get_process_attr_tracker( + sess->kernel_session, process_attr); + LTTNG_ASSERT(tracker); + break; + } + case LTTNG_DOMAIN_UST: + { + tracker = trace_ust_get_process_attr_tracker( + sess->ust_session, process_attr); + LTTNG_ASSERT(tracker); + break; + } + case LTTNG_DOMAIN_JUL: + case LTTNG_DOMAIN_LOG4J: + case LTTNG_DOMAIN_PYTHON: + default: + ret = LTTNG_ERR_UNSUPPORTED_DOMAIN; + goto end; + } + + tracking_policy = process_attr_tracker_get_tracking_policy(tracker); + if (tracking_policy == LTTNG_TRACKING_POLICY_INCLUDE_ALL) { + /* Tracking all, nothing to output. */ + ret = LTTNG_OK; + goto end; + } + + ret = config_writer_open_element(writer, element_id_tracker); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_open_element( + writer, config_element_process_attr_values); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (tracking_policy == LTTNG_TRACKING_POLICY_INCLUDE_SET) { + unsigned int i, count; + enum process_attr_tracker_status status = + process_attr_tracker_get_inclusion_set( + tracker, &values); + + if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + count = _lttng_process_attr_values_get_count(values); + + for (i = 0; i < count; i++) { + unsigned int integral_value = UINT_MAX; + const char *name = NULL; + const struct process_attr_value *value = + lttng_process_attr_tracker_values_get_at_index( + values, i); + + LTTNG_ASSERT(value); + ret = config_writer_open_element( + writer, element_target_id); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + switch (value->type) { + case LTTNG_PROCESS_ATTR_VALUE_TYPE_PID: + integral_value = + (unsigned int) value->value.pid; + break; + case LTTNG_PROCESS_ATTR_VALUE_TYPE_UID: + integral_value = + (unsigned int) value->value.uid; + break; + case LTTNG_PROCESS_ATTR_VALUE_TYPE_GID: + integral_value = + (unsigned int) value->value.gid; + break; + case LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME: + name = value->value.user_name; + LTTNG_ASSERT(name); + break; + case LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME: + name = value->value.group_name; + LTTNG_ASSERT(name); + break; + default: + abort(); + } + + if (name) { + ret = config_writer_write_element_string(writer, + config_element_name, name); + } else { + ret = config_writer_write_element_unsigned_int( + writer, element_id, + integral_value); + } + + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* /$element_target_id */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + } + + /* /values */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* /$element_id_tracker */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + lttng_process_attr_values_destroy(values); + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static int save_process_attr_trackers(struct config_writer *writer, + struct ltt_session *sess, + int domain) +{ + int ret; + + switch (domain) { + case LTTNG_DOMAIN_KERNEL: + ret = save_process_attr_tracker(writer, sess, domain, + LTTNG_PROCESS_ATTR_PROCESS_ID); + if (ret != LTTNG_OK) { + goto end; + } + ret = save_process_attr_tracker(writer, sess, domain, + LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID); + if (ret != LTTNG_OK) { + goto end; + } + ret = save_process_attr_tracker(writer, sess, domain, + LTTNG_PROCESS_ATTR_USER_ID); + if (ret != LTTNG_OK) { + goto end; + } + ret = save_process_attr_tracker(writer, sess, domain, + LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID); + if (ret != LTTNG_OK) { + goto end; + } + ret = save_process_attr_tracker(writer, sess, domain, + LTTNG_PROCESS_ATTR_GROUP_ID); + if (ret != LTTNG_OK) { + goto end; + } + ret = save_process_attr_tracker(writer, sess, domain, + LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID); + if (ret != LTTNG_OK) { + goto end; + } + break; + case LTTNG_DOMAIN_UST: + ret = save_process_attr_tracker(writer, sess, domain, + LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID); + if (ret != LTTNG_OK) { + goto end; + } + ret = save_process_attr_tracker(writer, sess, domain, + LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID); + if (ret != LTTNG_OK) { + goto end; + } + ret = save_process_attr_tracker(writer, sess, domain, + LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID); + if (ret != LTTNG_OK) { + goto end; + } + break; + default: + ret = LTTNG_ERR_INVALID; + goto end; + } + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_ust_domain(struct config_writer *writer, + struct ltt_session *session, enum lttng_domain_type domain) +{ + int ret; + struct ltt_ust_channel *ust_chan; + const char *buffer_type_string; + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + const char *config_domain_name; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(session); + + ret = config_writer_open_element(writer, + config_element_domain); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + config_domain_name = get_config_domain_str(domain); + if (!config_domain_name) { + ret = LTTNG_ERR_INVALID; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_type, config_domain_name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + buffer_type_string = get_buffer_type_string( + session->ust_session->buffer_type); + if (!buffer_type_string) { + ERR("Unsupported buffer type."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + ret = config_writer_write_element_string(writer, + config_element_buffer_type, buffer_type_string); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_open_element(writer, config_element_channels); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + rcu_read_lock(); + cds_lfht_for_each_entry(session->ust_session->domain_global.channels->ht, + &iter.iter, node, node) { + ust_chan = caa_container_of(node, struct ltt_ust_channel, node); + if (domain == ust_chan->domain) { + ret = save_ust_channel(writer, ust_chan, session->ust_session); + if (ret != LTTNG_OK) { + rcu_read_unlock(); + goto end; + } + } + } + rcu_read_unlock(); + + /* /channels */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (domain == LTTNG_DOMAIN_UST) { + ret = config_writer_open_element( + writer, config_element_process_attr_trackers); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = save_process_attr_trackers( + writer, session, LTTNG_DOMAIN_UST); + if (ret != LTTNG_OK) { + goto end; + } + + /* /trackers */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + /* /domain */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_domains(struct config_writer *writer, struct ltt_session *session) +{ + int ret = LTTNG_OK; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(session); + + if (!session->kernel_session && !session->ust_session) { + goto end; + } + + ret = config_writer_open_element(writer, config_element_domains); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (session->kernel_session) { + ret = config_writer_open_element(writer, + config_element_domain); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = save_kernel_session(writer, session); + if (ret != LTTNG_OK) { + goto end; + } + + ret = config_writer_open_element( + writer, config_element_process_attr_trackers); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = save_process_attr_trackers( + writer, session, LTTNG_DOMAIN_KERNEL); + if (ret != LTTNG_OK) { + goto end; + } + + /* /trackers */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + /* /domain */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + if (session->ust_session) { + ret = save_ust_domain(writer, session, LTTNG_DOMAIN_UST); + if (ret != LTTNG_OK) { + goto end; + } + + ret = save_ust_domain(writer, session, LTTNG_DOMAIN_JUL); + if (ret != LTTNG_OK) { + goto end; + } + + ret = save_ust_domain(writer, session, LTTNG_DOMAIN_LOG4J); + if (ret != LTTNG_OK) { + goto end; + } + + ret = save_ust_domain(writer, session, LTTNG_DOMAIN_PYTHON); + if (ret != LTTNG_OK) { + goto end; + } + } + + /* /domains */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_consumer_output(struct config_writer *writer, + struct consumer_output *output) +{ + int ret; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(output); + + ret = config_writer_open_element(writer, config_element_consumer_output); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_bool(writer, config_element_enabled, + output->enabled); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_open_element(writer, config_element_destination); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + switch (output->type) { + case CONSUMER_DST_LOCAL: + ret = config_writer_write_element_string(writer, + config_element_path, output->dst.session_root_path); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + break; + case CONSUMER_DST_NET: + { + char *uri; + + uri = (char *) zmalloc(PATH_MAX); + if (!uri) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + ret = config_writer_open_element(writer, config_element_net_output); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end_net_output; + } + + if (output->dst.net.control_isset && + output->dst.net.data_isset) { + ret = uri_to_str_url(&output->dst.net.control, uri, PATH_MAX); + if (ret < 0) { + ret = LTTNG_ERR_INVALID; + goto end_net_output; + } + + ret = config_writer_write_element_string(writer, + config_element_control_uri, uri); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end_net_output; + } + + ret = uri_to_str_url(&output->dst.net.data, uri, PATH_MAX); + if (ret < 0) { + ret = LTTNG_ERR_INVALID; + goto end_net_output; + } + + ret = config_writer_write_element_string(writer, + config_element_data_uri, uri); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end_net_output; + } + ret = LTTNG_OK; +end_net_output: + free(uri); + if (ret != LTTNG_OK) { + goto end; + } + } else { + ret = !output->dst.net.control_isset ? + LTTNG_ERR_URL_CTRL_MISS : + LTTNG_ERR_URL_DATA_MISS; + free(uri); + goto end; + } + + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + break; + } + default: + ERR("Unsupported consumer output type."); + ret = LTTNG_ERR_INVALID; + goto end; + } + + /* /destination */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* /consumer_output */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_snapshot_outputs(struct config_writer *writer, + struct snapshot *snapshot) +{ + int ret; + struct lttng_ht_iter iter; + struct snapshot_output *output; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(snapshot); + + ret = config_writer_open_element(writer, config_element_snapshot_outputs); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + rcu_read_lock(); + cds_lfht_for_each_entry(snapshot->output_ht->ht, &iter.iter, output, + node.node) { + ret = config_writer_open_element(writer, + config_element_output); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end_unlock; + } + + ret = config_writer_write_element_string(writer, + config_element_name, output->name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end_unlock; + } + + ret = config_writer_write_element_unsigned_int(writer, + config_element_max_size, output->max_size); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end_unlock; + } + + ret = save_consumer_output(writer, output->consumer); + if (ret != LTTNG_OK) { + goto end_unlock; + } + + /* /output */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end_unlock; + } + } + rcu_read_unlock(); + + /* /snapshot_outputs */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + return ret; +end_unlock: + rcu_read_unlock(); + return ret; +} + +/* Return LTTNG_OK on success else a LTTNG_ERR* code. */ +static +int save_session_output(struct config_writer *writer, + struct ltt_session *session) +{ + int ret; + + LTTNG_ASSERT(writer); + LTTNG_ASSERT(session); + + if ((session->snapshot_mode && session->snapshot.nb_output == 0) || + (!session->snapshot_mode && !session->consumer)) { + /* Session is in no output mode */ + ret = LTTNG_OK; + goto end; + } + + ret = config_writer_open_element(writer, config_element_output); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (session->snapshot_mode) { + ret = save_snapshot_outputs(writer, &session->snapshot); + if (ret != LTTNG_OK) { + goto end; + } + } else { + if (session->consumer) { + ret = save_consumer_output(writer, session->consumer); + if (ret != LTTNG_OK) { + goto end; + } + } + } + + /* /output */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + ret = LTTNG_OK; +end: + return ret; +} + +static +int save_session_rotation_schedule(struct config_writer *writer, + enum lttng_rotation_schedule_type type, uint64_t value) +{ + int ret = 0; + const char *element_name; + const char *value_name; + + switch (type) { + case LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC: + element_name = config_element_rotation_schedule_periodic; + value_name = config_element_rotation_schedule_periodic_time_us; + break; + case LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD: + element_name = config_element_rotation_schedule_size_threshold; + value_name = config_element_rotation_schedule_size_threshold_bytes; + break; + default: + ret = -1; + goto end; + } + + ret = config_writer_open_element(writer, element_name); + if (ret) { + goto end; + } + + ret = config_writer_write_element_unsigned_int(writer, + value_name, value); + if (ret) { + goto end; + } + + /* Close schedule descriptor element. */ + ret = config_writer_close_element(writer); + if (ret) { + goto end; + } +end: + return ret; +} + +static +int save_session_rotation_schedules(struct config_writer *writer, + struct ltt_session *session) +{ + int ret; + + ret = config_writer_open_element(writer, + config_element_rotation_schedules); + if (ret) { + goto end; + } + if (session->rotate_timer_period) { + ret = save_session_rotation_schedule(writer, + LTTNG_ROTATION_SCHEDULE_TYPE_PERIODIC, + session->rotate_timer_period); + if (ret) { + goto close_schedules; + } + } + if (session->rotate_size) { + ret = save_session_rotation_schedule(writer, + LTTNG_ROTATION_SCHEDULE_TYPE_SIZE_THRESHOLD, + session->rotate_size); + if (ret) { + goto close_schedules; + } + } + +close_schedules: + /* Close rotation schedules element. */ + ret = config_writer_close_element(writer); + if (ret) { + goto end; + } +end: + return ret; +} + +/* + * Save the given session. + * + * Return LTTNG_OK on success else a LTTNG_ERR* code. + */ +static +int save_session(struct ltt_session *session, + struct lttng_save_session_attr *attr, lttng_sock_cred *creds) +{ + int ret, fd = -1; + char config_file_path[LTTNG_PATH_MAX]; + size_t len; + struct config_writer *writer = NULL; + size_t session_name_len; + const char *provided_path; + int file_open_flags = O_CREAT | O_WRONLY | O_TRUNC; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(attr); + LTTNG_ASSERT(creds); + + session_name_len = strlen(session->name); + memset(config_file_path, 0, sizeof(config_file_path)); + + if (!session_access_ok(session, + LTTNG_SOCK_GET_UID_CRED(creds)) || session->destroyed) { + ret = LTTNG_ERR_EPERM; + goto end; + } + + provided_path = lttng_save_session_attr_get_output_url(attr); + if (provided_path) { + DBG3("Save session in provided path %s", provided_path); + len = strlen(provided_path); + if (len >= sizeof(config_file_path)) { + ret = LTTNG_ERR_SET_URL; + goto end; + } + strncpy(config_file_path, provided_path, sizeof(config_file_path)); + } else { + ssize_t ret_len; + char *home_dir = utils_get_user_home_dir( + LTTNG_SOCK_GET_UID_CRED(creds)); + if (!home_dir) { + ret = LTTNG_ERR_SET_URL; + goto end; + } + + ret_len = snprintf(config_file_path, sizeof(config_file_path), + DEFAULT_SESSION_HOME_CONFIGPATH, home_dir); + free(home_dir); + if (ret_len < 0) { + PERROR("snprintf save session"); + ret = LTTNG_ERR_SET_URL; + goto end; + } + len = ret_len; + } + + /* + * Check the path fits in the config file path dst including the '/' + * followed by trailing .lttng extension and the NULL terminated string. + */ + if ((len + session_name_len + 2 + + sizeof(DEFAULT_SESSION_CONFIG_FILE_EXTENSION)) + > sizeof(config_file_path)) { + ret = LTTNG_ERR_SET_URL; + goto end; + } + + ret = run_as_mkdir_recursive(config_file_path, S_IRWXU | S_IRWXG, + LTTNG_SOCK_GET_UID_CRED(creds), LTTNG_SOCK_GET_GID_CRED(creds)); + if (ret) { + ret = LTTNG_ERR_SET_URL; + goto end; + } + + /* + * At this point, we know that everything fits in the buffer. Validation + * was done just above. + */ + config_file_path[len++] = '/'; + strncpy(config_file_path + len, session->name, sizeof(config_file_path) - len); + len += session_name_len; + strcpy(config_file_path + len, DEFAULT_SESSION_CONFIG_FILE_EXTENSION); + len += sizeof(DEFAULT_SESSION_CONFIG_FILE_EXTENSION); + config_file_path[len] = '\0'; + + if (!attr->overwrite) { + file_open_flags |= O_EXCL; + } + + fd = run_as_open(config_file_path, file_open_flags, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP, + LTTNG_SOCK_GET_UID_CRED(creds), LTTNG_SOCK_GET_GID_CRED(creds)); + if (fd < 0) { + PERROR("Could not create configuration file"); + switch (errno) { + case EEXIST: + ret = LTTNG_ERR_SAVE_FILE_EXIST; + break; + case EACCES: + ret = LTTNG_ERR_EPERM; + break; + default: + ret = LTTNG_ERR_SAVE_IO_FAIL; + break; + } + goto end; + } + + writer = config_writer_create(fd, 1); + if (!writer) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + + ret = config_writer_open_element(writer, config_element_sessions); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_open_element(writer, config_element_session); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = config_writer_write_element_string(writer, config_element_name, + session->name); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (session->shm_path[0] != '\0') { + ret = config_writer_write_element_string(writer, + config_element_shared_memory_path, + session->shm_path); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + ret = save_domains(writer, session); + if (ret != LTTNG_OK) { + goto end; + } + + ret = config_writer_write_element_bool(writer, config_element_started, + session->active); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (session->snapshot_mode || session->live_timer || + session->rotate_timer_period || session->rotate_size) { + ret = config_writer_open_element(writer, config_element_attributes); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + if (session->snapshot_mode) { + ret = config_writer_write_element_bool(writer, + config_element_snapshot_mode, 1); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } else if (session->live_timer) { + ret = config_writer_write_element_unsigned_int(writer, + config_element_live_timer_interval, session->live_timer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + if (session->rotate_timer_period || session->rotate_size) { + ret = save_session_rotation_schedules(writer, + session); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + /* /attributes */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + } + + ret = save_session_output(writer, session); + if (ret != LTTNG_OK) { + goto end; + } + + /* /session */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + /* /sessions */ + ret = config_writer_close_element(writer); + if (ret) { + ret = LTTNG_ERR_SAVE_IO_FAIL; + goto end; + } + + ret = LTTNG_OK; +end: + if (writer && config_writer_destroy(writer)) { + /* Preserve the original error code */ + ret = ret != LTTNG_OK ? ret : LTTNG_ERR_SAVE_IO_FAIL; + } + if (ret != LTTNG_OK) { + /* Delete file in case of error */ + if ((fd >= 0) && unlink(config_file_path)) { + PERROR("Unlinking XML session configuration."); + } + } + + if (fd >= 0) { + int closeret; + + closeret = close(fd); + if (closeret) { + PERROR("Closing XML session configuration"); + } + } + + return ret; +} + +int cmd_save_sessions(struct lttng_save_session_attr *attr, + lttng_sock_cred *creds) +{ + int ret; + const char *session_name; + struct ltt_session *session; + + session_lock_list(); + + session_name = lttng_save_session_attr_get_session_name(attr); + if (session_name) { + session = session_find_by_name(session_name); + if (!session) { + ret = LTTNG_ERR_SESS_NOT_FOUND; + goto end; + } + + session_lock(session); + ret = save_session(session, attr, creds); + session_unlock(session); + session_put(session); + if (ret != LTTNG_OK) { + goto end; + } + } else { + struct ltt_session_list *list = session_get_list(); + + cds_list_for_each_entry(session, &list->head, list) { + if (!session_get(session)) { + continue; + } + session_lock(session); + ret = save_session(session, attr, creds); + session_unlock(session); + session_put(session); + /* Don't abort if we don't have the required permissions. */ + if (ret != LTTNG_OK && ret != LTTNG_ERR_EPERM) { + goto end; + } + } + } + ret = LTTNG_OK; + +end: + session_unlock_list(); + return ret; +} diff --git a/src/bin/lttng-sessiond/session.c b/src/bin/lttng-sessiond/session.c deleted file mode 100644 index dc6dea443..000000000 --- a/src/bin/lttng-sessiond/session.c +++ /dev/null @@ -1,1427 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include "lttng-sessiond.h" -#include "kernel.h" - -#include "session.h" -#include "utils.h" -#include "trace-ust.h" -#include "timer.h" -#include "cmd.h" - -struct ltt_session_destroy_notifier_element { - ltt_session_destroy_notifier notifier; - void *user_data; -}; - -struct ltt_session_clear_notifier_element { - ltt_session_clear_notifier notifier; - void *user_data; -}; - -/* - * NOTES: - * - * No ltt_session.lock is taken here because those data structure are widely - * spread across the lttng-tools code base so before calling functions below - * that can read/write a session, the caller MUST acquire the session lock - * using session_lock() and session_unlock(). - */ - -/* - * Init tracing session list. - * - * Please see session.h for more explanation and correct usage of the list. - */ -static struct ltt_session_list ltt_session_list = { - .head = CDS_LIST_HEAD_INIT(ltt_session_list.head), - .lock = PTHREAD_MUTEX_INITIALIZER, - .removal_cond = PTHREAD_COND_INITIALIZER, - .next_uuid = 0, -}; - -/* These characters are forbidden in a session name. Used by validate_name. */ -static const char *forbidden_name_chars = "/"; - -/* Global hash table to keep the sessions, indexed by id. */ -static struct lttng_ht *ltt_sessions_ht_by_id = NULL; -/* Global hash table to keep the sessions, indexed by name. */ -static struct lttng_ht *ltt_sessions_ht_by_name = NULL; - -/* - * Validate the session name for forbidden characters. - * - * Return 0 on success else -1 meaning a forbidden char. has been found. - */ -static int validate_name(const char *name) -{ - int ret; - char *tok, *tmp_name; - - LTTNG_ASSERT(name); - - tmp_name = strdup(name); - if (!tmp_name) { - /* ENOMEM here. */ - ret = -1; - goto error; - } - - tok = strpbrk(tmp_name, forbidden_name_chars); - if (tok) { - DBG("Session name %s contains a forbidden character", name); - /* Forbidden character has been found. */ - ret = -1; - goto error; - } - ret = 0; - -error: - free(tmp_name); - return ret; -} - -/* - * Add a ltt_session structure to the global list. - * - * The caller MUST acquire the session list lock before. - * Returns the unique identifier for the session. - */ -static uint64_t add_session_list(struct ltt_session *ls) -{ - LTTNG_ASSERT(ls); - - cds_list_add(&ls->list, <t_session_list.head); - return ltt_session_list.next_uuid++; -} - -/* - * Delete a ltt_session structure to the global list. - * - * The caller MUST acquire the session list lock before. - */ -static void del_session_list(struct ltt_session *ls) -{ - LTTNG_ASSERT(ls); - - cds_list_del(&ls->list); -} - -/* - * Return a pointer to the session list. - */ -struct ltt_session_list *session_get_list(void) -{ - return <t_session_list; -} - -/* - * Returns once the session list is empty. - */ -void session_list_wait_empty(void) -{ - pthread_mutex_lock(<t_session_list.lock); - while (!cds_list_empty(<t_session_list.head)) { - pthread_cond_wait(<t_session_list.removal_cond, - <t_session_list.lock); - } - pthread_mutex_unlock(<t_session_list.lock); -} - -/* - * Acquire session list lock - */ -void session_lock_list(void) -{ - pthread_mutex_lock(<t_session_list.lock); -} - -/* - * Try to acquire session list lock - */ -int session_trylock_list(void) -{ - return pthread_mutex_trylock(<t_session_list.lock); -} - -/* - * Release session list lock - */ -void session_unlock_list(void) -{ - pthread_mutex_unlock(<t_session_list.lock); -} - -/* - * Get the session's consumer destination type. - * - * The caller must hold the session lock. - */ -enum consumer_dst_type session_get_consumer_destination_type( - const struct ltt_session *session) -{ - /* - * The output information is duplicated in both of those session types. - * Hence, it doesn't matter from which it is retrieved. However, it is - * possible for only one of them to be set. - */ - return session->kernel_session ? - session->kernel_session->consumer->type : - session->ust_session->consumer->type; -} - -/* - * Get the session's consumer network hostname. - * The caller must ensure that the destination is of type "net". - * - * The caller must hold the session lock. - */ -const char *session_get_net_consumer_hostname(const struct ltt_session *session) -{ - const char *hostname = NULL; - const struct consumer_output *output; - - output = session->kernel_session ? - session->kernel_session->consumer : - session->ust_session->consumer; - - /* - * hostname is assumed to be the same for both control and data - * connections. - */ - switch (output->dst.net.control.dtype) { - case LTTNG_DST_IPV4: - hostname = output->dst.net.control.dst.ipv4; - break; - case LTTNG_DST_IPV6: - hostname = output->dst.net.control.dst.ipv6; - break; - default: - abort(); - } - return hostname; -} - -/* - * Get the session's consumer network control and data ports. - * The caller must ensure that the destination is of type "net". - * - * The caller must hold the session lock. - */ -void session_get_net_consumer_ports(const struct ltt_session *session, - uint16_t *control_port, uint16_t *data_port) -{ - const struct consumer_output *output; - - output = session->kernel_session ? - session->kernel_session->consumer : - session->ust_session->consumer; - *control_port = output->dst.net.control.port; - *data_port = output->dst.net.data.port; -} - -/* - * Get the location of the latest trace archive produced by a rotation. - * - * The caller must hold the session lock. - */ -struct lttng_trace_archive_location *session_get_trace_archive_location( - const struct ltt_session *session) -{ - int ret; - struct lttng_trace_archive_location *location = NULL; - char *chunk_path = NULL; - - if (session->rotation_state != LTTNG_ROTATION_STATE_COMPLETED || - !session->last_archived_chunk_name) { - goto end; - } - - switch (session_get_consumer_destination_type(session)) { - case CONSUMER_DST_LOCAL: - ret = asprintf(&chunk_path, - "%s/" DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY "/%s", - session_get_base_path(session), - session->last_archived_chunk_name); - if (ret == -1) { - goto end; - } - location = lttng_trace_archive_location_local_create( - chunk_path); - break; - case CONSUMER_DST_NET: - { - const char *hostname; - uint16_t control_port, data_port; - - hostname = session_get_net_consumer_hostname(session); - session_get_net_consumer_ports(session, - &control_port, - &data_port); - location = lttng_trace_archive_location_relay_create( - hostname, - LTTNG_TRACE_ARCHIVE_LOCATION_RELAY_PROTOCOL_TYPE_TCP, - control_port, data_port, session->last_chunk_path); - break; - } - default: - abort(); - } -end: - free(chunk_path); - return location; -} - -/* - * Allocate the ltt_sessions_ht_by_id and ltt_sessions_ht_by_name HT. - * - * The session list lock must be held. - */ -static int ltt_sessions_ht_alloc(void) -{ - int ret = 0; - - DBG("Allocating ltt_sessions_ht_by_id"); - ltt_sessions_ht_by_id = lttng_ht_new(0, LTTNG_HT_TYPE_U64); - if (!ltt_sessions_ht_by_id) { - ret = -1; - ERR("Failed to allocate ltt_sessions_ht_by_id"); - goto end; - } - - DBG("Allocating ltt_sessions_ht_by_name"); - ltt_sessions_ht_by_name = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); - if (!ltt_sessions_ht_by_name) { - ret = -1; - ERR("Failed to allocate ltt_sessions_ht_by_name"); - goto end; - } - -end: - return ret; -} - -/* - * Destroy the ltt_sessions_ht_by_id HT. - * - * The session list lock must be held. - */ -static void ltt_sessions_ht_destroy(void) -{ - if (ltt_sessions_ht_by_id) { - ht_cleanup_push(ltt_sessions_ht_by_id); - ltt_sessions_ht_by_id = NULL; - } - - if (ltt_sessions_ht_by_name) { - ht_cleanup_push(ltt_sessions_ht_by_name); - ltt_sessions_ht_by_name = NULL; - } - - return; -} - -/* - * Add a ltt_session to the ltt_sessions_ht_by_id and ltt_sessions_ht_by_name. - * If unallocated, the ltt_sessions_ht_by_id and ltt_sessions_ht_by_name. HTs - * are allocated. The session list lock must be held. - */ -static void add_session_ht(struct ltt_session *ls) -{ - int ret; - - LTTNG_ASSERT(ls); - - if (!ltt_sessions_ht_by_id) { - ret = ltt_sessions_ht_alloc(); - if (ret) { - ERR("Error allocating the sessions HT"); - goto end; - } - } - - /* Should always be present with ltt_sessions_ht_by_id. */ - LTTNG_ASSERT(ltt_sessions_ht_by_name); - - lttng_ht_node_init_u64(&ls->node, ls->id); - lttng_ht_add_unique_u64(ltt_sessions_ht_by_id, &ls->node); - - lttng_ht_node_init_str(&ls->node_by_name, ls->name); - lttng_ht_add_unique_str(ltt_sessions_ht_by_name, &ls->node_by_name); - -end: - return; -} - -/* - * Test if ltt_sessions_ht_by_id/name are empty. - * Return 1 if empty, 0 if not empty. - * The session list lock must be held. - */ -static int ltt_sessions_ht_empty(void) -{ - unsigned long count; - - if (!ltt_sessions_ht_by_id) { - count = 0; - goto end; - } - - LTTNG_ASSERT(ltt_sessions_ht_by_name); - - count = lttng_ht_get_count(ltt_sessions_ht_by_id); - LTTNG_ASSERT(count == lttng_ht_get_count(ltt_sessions_ht_by_name)); -end: - return count ? 0 : 1; -} - -/* - * Remove a ltt_session from the ltt_sessions_ht_by_id/name. - * If empty, the ltt_sessions_ht_by_id/name HTs are freed. - * The session list lock must be held. - */ -static void del_session_ht(struct ltt_session *ls) -{ - struct lttng_ht_iter iter; - int ret; - - LTTNG_ASSERT(ls); - LTTNG_ASSERT(ltt_sessions_ht_by_id); - LTTNG_ASSERT(ltt_sessions_ht_by_name); - - iter.iter.node = &ls->node.node; - ret = lttng_ht_del(ltt_sessions_ht_by_id, &iter); - LTTNG_ASSERT(!ret); - - iter.iter.node = &ls->node_by_name.node; - ret = lttng_ht_del(ltt_sessions_ht_by_name, &iter); - LTTNG_ASSERT(!ret); - - if (ltt_sessions_ht_empty()) { - DBG("Empty ltt_sessions_ht_by_id/name, destroying hast tables"); - ltt_sessions_ht_destroy(); - } -} - -/* - * Acquire session lock - */ -void session_lock(struct ltt_session *session) -{ - LTTNG_ASSERT(session); - - pthread_mutex_lock(&session->lock); -} - -/* - * Release session lock - */ -void session_unlock(struct ltt_session *session) -{ - LTTNG_ASSERT(session); - - pthread_mutex_unlock(&session->lock); -} - -static -int _session_set_trace_chunk_no_lock_check(struct ltt_session *session, - struct lttng_trace_chunk *new_trace_chunk, - struct lttng_trace_chunk **_current_trace_chunk) -{ - int ret = 0; - unsigned int i, refs_to_acquire = 0, refs_acquired = 0, refs_to_release = 0; - struct cds_lfht_iter iter; - struct consumer_socket *socket; - struct lttng_trace_chunk *current_trace_chunk; - uint64_t chunk_id; - enum lttng_trace_chunk_status chunk_status; - - rcu_read_lock(); - /* - * Ownership of current trace chunk is transferred to - * `current_trace_chunk`. - */ - current_trace_chunk = session->current_trace_chunk; - session->current_trace_chunk = NULL; - if (session->ust_session) { - lttng_trace_chunk_put( - session->ust_session->current_trace_chunk); - session->ust_session->current_trace_chunk = NULL; - } - if (session->kernel_session) { - lttng_trace_chunk_put( - session->kernel_session->current_trace_chunk); - session->kernel_session->current_trace_chunk = NULL; - } - if (!new_trace_chunk) { - ret = 0; - goto end; - } - chunk_status = lttng_trace_chunk_get_id(new_trace_chunk, &chunk_id); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - - refs_to_acquire = 1; - refs_to_acquire += !!session->ust_session; - refs_to_acquire += !!session->kernel_session; - - for (refs_acquired = 0; refs_acquired < refs_to_acquire; - refs_acquired++) { - if (!lttng_trace_chunk_get(new_trace_chunk)) { - ERR("Failed to acquire reference to new trace chunk of session \"%s\"", - session->name); - goto error; - } - } - - if (session->ust_session) { - const uint64_t relayd_id = - session->ust_session->consumer->net_seq_index; - const bool is_local_trace = - session->ust_session->consumer->type == - CONSUMER_DST_LOCAL; - - session->ust_session->current_trace_chunk = new_trace_chunk; - if (is_local_trace) { - enum lttng_error_code ret_error_code; - - ret_error_code = ust_app_create_channel_subdirectories( - session->ust_session); - if (ret_error_code != LTTNG_OK) { - goto error; - } - } - cds_lfht_for_each_entry( - session->ust_session->consumer->socks->ht, - &iter, socket, node.node) { - pthread_mutex_lock(socket->lock); - ret = consumer_create_trace_chunk(socket, - relayd_id, - session->id, new_trace_chunk, - DEFAULT_UST_TRACE_DIR); - pthread_mutex_unlock(socket->lock); - if (ret) { - goto error; - } - } - } - if (session->kernel_session) { - const uint64_t relayd_id = - session->kernel_session->consumer->net_seq_index; - const bool is_local_trace = - session->kernel_session->consumer->type == - CONSUMER_DST_LOCAL; - - session->kernel_session->current_trace_chunk = new_trace_chunk; - if (is_local_trace) { - enum lttng_error_code ret_error_code; - - ret_error_code = kernel_create_channel_subdirectories( - session->kernel_session); - if (ret_error_code != LTTNG_OK) { - goto error; - } - } - cds_lfht_for_each_entry( - session->kernel_session->consumer->socks->ht, - &iter, socket, node.node) { - pthread_mutex_lock(socket->lock); - ret = consumer_create_trace_chunk(socket, - relayd_id, - session->id, new_trace_chunk, - DEFAULT_KERNEL_TRACE_DIR); - pthread_mutex_unlock(socket->lock); - if (ret) { - goto error; - } - } - } - - /* - * Update local current trace chunk state last, only if all remote - * creations succeeded. - */ - session->current_trace_chunk = new_trace_chunk; - LTTNG_OPTIONAL_SET(&session->most_recent_chunk_id, chunk_id); -end: - if (_current_trace_chunk) { - *_current_trace_chunk = current_trace_chunk; - current_trace_chunk = NULL; - } -end_no_move: - rcu_read_unlock(); - lttng_trace_chunk_put(current_trace_chunk); - return ret; -error: - if (session->ust_session) { - session->ust_session->current_trace_chunk = NULL; - } - if (session->kernel_session) { - session->kernel_session->current_trace_chunk = NULL; - } - /* - * Release references taken in the case where all references could not - * be acquired. - */ - refs_to_release = refs_to_acquire - refs_acquired; - for (i = 0; i < refs_to_release; i++) { - lttng_trace_chunk_put(new_trace_chunk); - } - ret = -1; - goto end_no_move; -} - -struct lttng_trace_chunk *session_create_new_trace_chunk( - const struct ltt_session *session, - const struct consumer_output *consumer_output_override, - const char *session_base_path_override, - const char *chunk_name_override) -{ - int ret; - struct lttng_trace_chunk *trace_chunk = NULL; - enum lttng_trace_chunk_status chunk_status; - const time_t chunk_creation_ts = time(NULL); - bool is_local_trace; - const char *base_path; - struct lttng_directory_handle *session_output_directory = NULL; - const struct lttng_credentials session_credentials = { - .uid = LTTNG_OPTIONAL_INIT_VALUE(session->uid), - .gid = LTTNG_OPTIONAL_INIT_VALUE(session->gid), - }; - uint64_t next_chunk_id; - const struct consumer_output *output; - const char *new_path; - - if (consumer_output_override) { - output = consumer_output_override; - } else { - LTTNG_ASSERT(session->ust_session || session->kernel_session); - output = session->ust_session ? - session->ust_session->consumer : - session->kernel_session->consumer; - } - - is_local_trace = output->type == CONSUMER_DST_LOCAL; - base_path = session_base_path_override ? : - consumer_output_get_base_path(output); - - if (chunk_creation_ts == (time_t) -1) { - PERROR("Failed to sample time while creation session \"%s\" trace chunk", - session->name); - goto error; - } - - next_chunk_id = session->most_recent_chunk_id.is_set ? - session->most_recent_chunk_id.value + 1 : 0; - - if (session->current_trace_chunk && - !lttng_trace_chunk_get_name_overridden(session->current_trace_chunk)) { - chunk_status = lttng_trace_chunk_rename_path(session->current_trace_chunk, - DEFAULT_CHUNK_TMP_OLD_DIRECTORY); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - goto error; - } - } - if (!session->current_trace_chunk) { - if (!session->rotated) { - new_path = ""; - } else { - new_path = NULL; - } - } else { - new_path = DEFAULT_CHUNK_TMP_NEW_DIRECTORY; - } - - trace_chunk = lttng_trace_chunk_create(next_chunk_id, - chunk_creation_ts, new_path); - if (!trace_chunk) { - goto error; - } - - if (chunk_name_override) { - chunk_status = lttng_trace_chunk_override_name(trace_chunk, - chunk_name_override); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - goto error; - } - } - - if (!is_local_trace) { - /* - * No need to set crendentials and output directory - * for remote trace chunks. - */ - goto end; - } - - chunk_status = lttng_trace_chunk_set_credentials(trace_chunk, - &session_credentials); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - goto error; - } - - DBG("Creating base output directory of session \"%s\" at %s", - session->name, base_path); - ret = utils_mkdir_recursive(base_path, S_IRWXU | S_IRWXG, - session->uid, session->gid); - if (ret) { - goto error; - } - session_output_directory = lttng_directory_handle_create(base_path); - if (!session_output_directory) { - goto error; - } - chunk_status = lttng_trace_chunk_set_as_owner(trace_chunk, - session_output_directory); - lttng_directory_handle_put(session_output_directory); - session_output_directory = NULL; - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - goto error; - } -end: - return trace_chunk; -error: - lttng_directory_handle_put(session_output_directory); - lttng_trace_chunk_put(trace_chunk); - trace_chunk = NULL; - goto end; -} - -int session_close_trace_chunk(struct ltt_session *session, - struct lttng_trace_chunk *trace_chunk, - enum lttng_trace_chunk_command_type close_command, - char *closed_trace_chunk_path) -{ - int ret = 0; - bool error_occurred = false; - struct cds_lfht_iter iter; - struct consumer_socket *socket; - enum lttng_trace_chunk_status chunk_status; - const time_t chunk_close_timestamp = time(NULL); - const char *new_path; - - chunk_status = lttng_trace_chunk_set_close_command( - trace_chunk, close_command); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = -1; - goto end; - } - - if (chunk_close_timestamp == (time_t) -1) { - ERR("Failed to sample the close timestamp of the current trace chunk of session \"%s\"", - session->name); - ret = -1; - goto end; - } - - if (close_command == LTTNG_TRACE_CHUNK_COMMAND_TYPE_DELETE && !session->rotated) { - /* New chunk stays in session output directory. */ - new_path = ""; - } else { - /* Use chunk name for new chunk. */ - new_path = NULL; - } - if (session->current_trace_chunk && - !lttng_trace_chunk_get_name_overridden(session->current_trace_chunk)) { - /* Rename new chunk path. */ - chunk_status = lttng_trace_chunk_rename_path(session->current_trace_chunk, - new_path); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = -1; - goto end; - } - } - if (!lttng_trace_chunk_get_name_overridden(trace_chunk) && - close_command == LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION) { - const char *old_path; - - if (!session->rotated) { - old_path = ""; - } else { - old_path = NULL; - } - /* We need to move back the .tmp_old_chunk to its rightful place. */ - chunk_status = lttng_trace_chunk_rename_path(trace_chunk, - old_path); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = -1; - goto end; - } - } - if (close_command == LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED) { - session->rotated = true; - } - chunk_status = lttng_trace_chunk_set_close_timestamp(trace_chunk, - chunk_close_timestamp); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ERR("Failed to set the close timestamp of the current trace chunk of session \"%s\"", - session->name); - ret = -1; - goto end; - } - - if (session->ust_session) { - const uint64_t relayd_id = - session->ust_session->consumer->net_seq_index; - - cds_lfht_for_each_entry( - session->ust_session->consumer->socks->ht, - &iter, socket, node.node) { - pthread_mutex_lock(socket->lock); - ret = consumer_close_trace_chunk(socket, - relayd_id, - session->id, - trace_chunk, closed_trace_chunk_path); - pthread_mutex_unlock(socket->lock); - if (ret) { - ERR("Failed to close trace chunk on user space consumer"); - error_occurred = true; - } - } - } - if (session->kernel_session) { - const uint64_t relayd_id = - session->kernel_session->consumer->net_seq_index; - - cds_lfht_for_each_entry( - session->kernel_session->consumer->socks->ht, - &iter, socket, node.node) { - pthread_mutex_lock(socket->lock); - ret = consumer_close_trace_chunk(socket, - relayd_id, - session->id, - trace_chunk, closed_trace_chunk_path); - pthread_mutex_unlock(socket->lock); - if (ret) { - ERR("Failed to close trace chunk on kernel consumer"); - error_occurred = true; - } - } - } - ret = error_occurred ? -1 : 0; -end: - return ret; -} - -/* - * This function skips the metadata channel as the begin/end timestamps of a - * metadata packet are useless. - * - * Moreover, opening a packet after a "clear" will cause problems for live - * sessions as it will introduce padding that was not part of the first trace - * chunk. The relay daemon expects the content of the metadata stream of - * successive metadata trace chunks to be strict supersets of one another. - * - * For example, flushing a packet at the beginning of the metadata stream of - * a trace chunk resulting from a "clear" session command will cause the - * size of the metadata stream of the new trace chunk to not match the size of - * the metadata stream of the original chunk. This will confuse the relay - * daemon as the same "offset" in a metadata stream will no longer point - * to the same content. - */ -static -enum lttng_error_code session_kernel_open_packets(struct ltt_session *session) -{ - enum lttng_error_code ret = LTTNG_OK; - struct consumer_socket *socket; - struct lttng_ht_iter iter; - struct cds_lfht_node *node; - struct ltt_kernel_channel *chan; - - rcu_read_lock(); - - cds_lfht_first(session->kernel_session->consumer->socks->ht, &iter.iter); - node = cds_lfht_iter_get_node(&iter.iter); - socket = container_of(node, typeof(*socket), node.node); - - cds_list_for_each_entry(chan, - &session->kernel_session->channel_list.head, list) { - int open_ret; - - DBG("Open packet of kernel channel: channel key = %" PRIu64 - ", session name = %s, session_id = %" PRIu64, - chan->key, session->name, session->id); - - open_ret = consumer_open_channel_packets(socket, chan->key); - if (open_ret < 0) { - /* General error (no known error expected). */ - ret = LTTNG_ERR_UNK; - goto end; - } - } - -end: - rcu_read_unlock(); - return ret; -} - -enum lttng_error_code session_open_packets(struct ltt_session *session) -{ - enum lttng_error_code ret = LTTNG_OK; - - DBG("Opening packets of session channels: session name = %s, session id = %" PRIu64, - session->name, session->id); - - if (session->ust_session) { - ret = ust_app_open_packets(session); - if (ret != LTTNG_OK) { - goto end; - } - } - - if (session->kernel_session) { - ret = session_kernel_open_packets(session); - if (ret != LTTNG_OK) { - goto end; - } - } - -end: - return ret; -} - -/* - * Set a session's current trace chunk. - * - * Must be called with the session lock held. - */ -int session_set_trace_chunk(struct ltt_session *session, - struct lttng_trace_chunk *new_trace_chunk, - struct lttng_trace_chunk **current_trace_chunk) -{ - ASSERT_LOCKED(session->lock); - return _session_set_trace_chunk_no_lock_check(session, new_trace_chunk, - current_trace_chunk); -} - -static -void session_notify_destruction(const struct ltt_session *session) -{ - size_t i; - const size_t count = lttng_dynamic_array_get_count( - &session->destroy_notifiers); - - for (i = 0; i < count; i++) { - const struct ltt_session_destroy_notifier_element *element = - lttng_dynamic_array_get_element( - &session->destroy_notifiers, i); - - element->notifier(session, element->user_data); - } -} - -/* - * Fire each clear notifier once, and remove them from the array. - */ -void session_notify_clear(struct ltt_session *session) -{ - size_t i; - const size_t count = lttng_dynamic_array_get_count( - &session->clear_notifiers); - - for (i = 0; i < count; i++) { - const struct ltt_session_clear_notifier_element *element = - lttng_dynamic_array_get_element( - &session->clear_notifiers, i); - - element->notifier(session, element->user_data); - } - lttng_dynamic_array_clear(&session->clear_notifiers); -} - -static -void session_release(struct urcu_ref *ref) -{ - int ret; - struct ltt_ust_session *usess; - struct ltt_kernel_session *ksess; - struct ltt_session *session = container_of(ref, typeof(*session), ref); - const bool session_published = session->published; - - LTTNG_ASSERT(!session->chunk_being_archived); - - usess = session->ust_session; - ksess = session->kernel_session; - - /* Clean kernel session teardown, keeping data for destroy notifier. */ - kernel_destroy_session(ksess); - - /* UST session teardown, keeping data for destroy notifier. */ - if (usess) { - /* Close any relayd session */ - consumer_output_send_destroy_relayd(usess->consumer); - - /* Destroy every UST application related to this session. */ - ret = ust_app_destroy_trace_all(usess); - if (ret) { - ERR("Error in ust_app_destroy_trace_all"); - } - - /* Clean up the rest, keeping destroy notifier data. */ - trace_ust_destroy_session(usess); - } - - /* - * Must notify the kernel thread here to update it's poll set in order to - * remove the channel(s)' fd just destroyed. - */ - ret = notify_thread_pipe(the_kernel_poll_pipe[1]); - if (ret < 0) { - PERROR("write kernel poll pipe"); - } - - DBG("Destroying session %s (id %" PRIu64 ")", session->name, session->id); - - snapshot_destroy(&session->snapshot); - - pthread_mutex_destroy(&session->lock); - - if (session_published) { - ASSERT_LOCKED(ltt_session_list.lock); - del_session_list(session); - del_session_ht(session); - } - session_notify_destruction(session); - - consumer_output_put(session->consumer); - kernel_free_session(ksess); - session->kernel_session = NULL; - if (usess) { - trace_ust_free_session(usess); - session->ust_session = NULL; - } - lttng_dynamic_array_reset(&session->destroy_notifiers); - lttng_dynamic_array_reset(&session->clear_notifiers); - free(session->last_archived_chunk_name); - free(session->base_path); - free(session); - if (session_published) { - /* - * Broadcast after free-ing to ensure the memory is - * reclaimed before the main thread exits. - */ - ASSERT_LOCKED(ltt_session_list.lock); - pthread_cond_broadcast(<t_session_list.removal_cond); - } -} - -/* - * Acquire a reference to a session. - * This function may fail (return false); its return value must be checked. - */ -bool session_get(struct ltt_session *session) -{ - return urcu_ref_get_unless_zero(&session->ref); -} - -/* - * Release a reference to a session. - */ -void session_put(struct ltt_session *session) -{ - if (!session) { - return; - } - /* - * The session list lock must be held as any session_put() - * may cause the removal of the session from the session_list. - */ - ASSERT_LOCKED(ltt_session_list.lock); - LTTNG_ASSERT(session->ref.refcount); - urcu_ref_put(&session->ref, session_release); -} - -/* - * Destroy a session. - * - * This method does not immediately release/free the session as other - * components may still hold a reference to the session. However, - * the session should no longer be presented to the user. - * - * Releases the session list's reference to the session - * and marks it as destroyed. Iterations on the session list should be - * mindful of the "destroyed" flag. - */ -void session_destroy(struct ltt_session *session) -{ - LTTNG_ASSERT(!session->destroyed); - session->destroyed = true; - session_put(session); -} - -int session_add_destroy_notifier(struct ltt_session *session, - ltt_session_destroy_notifier notifier, void *user_data) -{ - const struct ltt_session_destroy_notifier_element element = { - .notifier = notifier, - .user_data = user_data - }; - - return lttng_dynamic_array_add_element(&session->destroy_notifiers, - &element); -} - -int session_add_clear_notifier(struct ltt_session *session, - ltt_session_clear_notifier notifier, void *user_data) -{ - const struct ltt_session_clear_notifier_element element = { - .notifier = notifier, - .user_data = user_data - }; - - return lttng_dynamic_array_add_element(&session->clear_notifiers, - &element); -} - -/* - * Return a ltt_session structure ptr that matches name. If no session found, - * NULL is returned. This must be called with the session list lock held using - * session_lock_list and session_unlock_list. - * A reference to the session is implicitly acquired by this function. - */ -struct ltt_session *session_find_by_name(const char *name) -{ - struct ltt_session *iter; - - LTTNG_ASSERT(name); - ASSERT_LOCKED(ltt_session_list.lock); - - DBG2("Trying to find session by name %s", name); - - cds_list_for_each_entry(iter, <t_session_list.head, list) { - if (!strncmp(iter->name, name, NAME_MAX) && - !iter->destroyed) { - goto found; - } - } - - return NULL; -found: - return session_get(iter) ? iter : NULL; -} - -/* - * Return an ltt_session that matches the id. If no session is found, - * NULL is returned. This must be called with rcu_read_lock and - * session list lock held (to guarantee the lifetime of the session). - */ -struct ltt_session *session_find_by_id(uint64_t id) -{ - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - struct ltt_session *ls; - - ASSERT_LOCKED(ltt_session_list.lock); - - if (!ltt_sessions_ht_by_id) { - goto end; - } - - lttng_ht_lookup(ltt_sessions_ht_by_id, &id, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (node == NULL) { - goto end; - } - ls = caa_container_of(node, struct ltt_session, node); - - DBG3("Session %" PRIu64 " found by id.", id); - return session_get(ls) ? ls : NULL; - -end: - DBG3("Session %" PRIu64 " NOT found by id", id); - return NULL; -} - -/* - * Create a new session and add it to the session list. - * Session list lock must be held by the caller. - */ -enum lttng_error_code session_create(const char *name, uid_t uid, gid_t gid, - struct ltt_session **out_session) -{ - int ret; - enum lttng_error_code ret_code; - struct ltt_session *new_session = NULL; - - ASSERT_LOCKED(ltt_session_list.lock); - if (name) { - struct ltt_session *clashing_session; - - clashing_session = session_find_by_name(name); - if (clashing_session) { - session_put(clashing_session); - ret_code = LTTNG_ERR_EXIST_SESS; - goto error; - } - } - new_session = zmalloc(sizeof(struct ltt_session)); - if (!new_session) { - PERROR("Failed to allocate an ltt_session structure"); - ret_code = LTTNG_ERR_NOMEM; - goto error; - } - - lttng_dynamic_array_init(&new_session->destroy_notifiers, - sizeof(struct ltt_session_destroy_notifier_element), - NULL); - lttng_dynamic_array_init(&new_session->clear_notifiers, - sizeof(struct ltt_session_clear_notifier_element), - NULL); - urcu_ref_init(&new_session->ref); - pthread_mutex_init(&new_session->lock, NULL); - - new_session->creation_time = time(NULL); - if (new_session->creation_time == (time_t) -1) { - PERROR("Failed to sample session creation time"); - ret_code = LTTNG_ERR_SESSION_FAIL; - goto error; - } - - /* Create default consumer output. */ - new_session->consumer = consumer_create_output(CONSUMER_DST_LOCAL); - if (new_session->consumer == NULL) { - ret_code = LTTNG_ERR_NOMEM; - goto error; - } - - if (name) { - ret = lttng_strncpy(new_session->name, name, sizeof(new_session->name)); - if (ret) { - ret_code = LTTNG_ERR_SESSION_INVALID_CHAR; - goto error; - } - ret = validate_name(name); - if (ret < 0) { - ret_code = LTTNG_ERR_SESSION_INVALID_CHAR; - goto error; - } - } else { - int i = 0; - bool found_name = false; - char datetime[16]; - struct tm *timeinfo; - - timeinfo = localtime(&new_session->creation_time); - if (!timeinfo) { - ret_code = LTTNG_ERR_SESSION_FAIL; - goto error; - } - strftime(datetime, sizeof(datetime), "%Y%m%d-%H%M%S", timeinfo); - for (i = 0; i < INT_MAX; i++) { - struct ltt_session *clashing_session; - - if (i == 0) { - ret = snprintf(new_session->name, - sizeof(new_session->name), - "%s-%s", - DEFAULT_SESSION_NAME, - datetime); - } else { - ret = snprintf(new_session->name, - sizeof(new_session->name), - "%s%d-%s", - DEFAULT_SESSION_NAME, i, - datetime); - } - new_session->name_contains_creation_time = true; - if (ret == -1 || ret >= sizeof(new_session->name)) { - /* - * Null-terminate in case the name is used - * in logging statements. - */ - new_session->name[sizeof(new_session->name) - 1] = '\0'; - ret_code = LTTNG_ERR_SESSION_FAIL; - goto error; - } - - clashing_session = - session_find_by_name(new_session->name); - session_put(clashing_session); - if (!clashing_session) { - found_name = true; - break; - } - } - if (found_name) { - DBG("Generated session name \"%s\"", new_session->name); - new_session->has_auto_generated_name = true; - } else { - ERR("Failed to auto-generate a session name"); - ret_code = LTTNG_ERR_SESSION_FAIL; - goto error; - } - } - - ret = gethostname(new_session->hostname, sizeof(new_session->hostname)); - if (ret < 0) { - if (errno == ENAMETOOLONG) { - new_session->hostname[sizeof(new_session->hostname) - 1] = '\0'; - ERR("Hostname exceeds the maximal permitted length and has been truncated to %s", - new_session->hostname); - } else { - ret_code = LTTNG_ERR_SESSION_FAIL; - goto error; - } - } - - new_session->uid = uid; - new_session->gid = gid; - - ret = snapshot_init(&new_session->snapshot); - if (ret < 0) { - ret_code = LTTNG_ERR_NOMEM; - goto error; - } - - new_session->rotation_state = LTTNG_ROTATION_STATE_NO_ROTATION; - - /* Add new session to the session list. */ - new_session->id = add_session_list(new_session); - - /* - * Add the new session to the ltt_sessions_ht_by_id. - * No ownership is taken by the hash table; it is merely - * a wrapper around the session list used for faster access - * by session id. - */ - add_session_ht(new_session); - new_session->published = true; - - /* - * Consumer is left to NULL since the create_session_uri command will - * set it up and, if valid, assign it to the session. - */ - DBG("Tracing session %s created with ID %" PRIu64 " by uid = %d, gid = %d", - new_session->name, new_session->id, new_session->uid, - new_session->gid); - ret_code = LTTNG_OK; -end: - if (new_session) { - (void) session_get(new_session); - *out_session = new_session; - } - return ret_code; -error: - session_put(new_session); - new_session = NULL; - goto end; -} - -/* - * Check if the UID matches the session. Root user has access to all - * sessions. - */ -bool session_access_ok(struct ltt_session *session, uid_t uid) -{ - LTTNG_ASSERT(session); - return (uid == session->uid) || uid == 0; -} - -/* - * Set a session's rotation state and reset all associated state. - * - * This function resets the rotation state (check timers, pending - * flags, etc.) and sets the result of the last rotation. The result - * can be queries by a liblttng-ctl client. - * - * Be careful of the result passed to this function. For instance, - * on failure to launch a rotation, a client will expect the rotation - * state to be set to "NO_ROTATION". If an error occurred while the - * rotation was "ONGOING", result should be set to "ERROR", which will - * allow a client to report it. - * - * Must be called with the session and session_list locks held. - */ -int session_reset_rotation_state(struct ltt_session *session, - enum lttng_rotation_state result) -{ - int ret = 0; - - ASSERT_LOCKED(ltt_session_list.lock); - ASSERT_LOCKED(session->lock); - - session->rotation_state = result; - if (session->rotation_pending_check_timer_enabled) { - ret = timer_session_rotation_pending_check_stop(session); - } - if (session->chunk_being_archived) { - uint64_t chunk_id; - enum lttng_trace_chunk_status chunk_status; - - chunk_status = lttng_trace_chunk_get_id( - session->chunk_being_archived, - &chunk_id); - LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); - LTTNG_OPTIONAL_SET(&session->last_archived_chunk_id, - chunk_id); - lttng_trace_chunk_put(session->chunk_being_archived); - session->chunk_being_archived = NULL; - /* - * Fire the clear reply notifiers if we are completing a clear - * rotation. - */ - session_notify_clear(session); - } - return ret; -} - -/* - * Sample the id of a session looked up via its name. - * Here the term "sampling" hint the caller that this return the id at a given - * point in time with no guarantee that the session for which the id was - * sampled still exist at that point. - * - * Return 0 when the session is not found, - * Return 1 when the session is found and set `id`. - */ -bool sample_session_id_by_name(const char *name, uint64_t *id) -{ - bool found = false; - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - struct ltt_session *ls; - - rcu_read_lock(); - - if (!ltt_sessions_ht_by_name) { - found = false; - goto end; - } - - lttng_ht_lookup(ltt_sessions_ht_by_name, name, &iter); - node = lttng_ht_iter_get_node_str(&iter); - if (node == NULL) { - found = false; - goto end; - } - - ls = caa_container_of(node, struct ltt_session, node_by_name); - *id = ls->id; - found = true; - - DBG3("Session id `%" PRIu64 "` sampled for session `%s", *id, name); -end: - rcu_read_unlock(); - return found; -} diff --git a/src/bin/lttng-sessiond/session.cpp b/src/bin/lttng-sessiond/session.cpp new file mode 100644 index 000000000..0d19042b5 --- /dev/null +++ b/src/bin/lttng-sessiond/session.cpp @@ -0,0 +1,1427 @@ +/* + * Copyright (C) 2011 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "lttng-sessiond.h" +#include "kernel.h" + +#include "session.h" +#include "utils.h" +#include "trace-ust.h" +#include "timer.h" +#include "cmd.h" + +struct ltt_session_destroy_notifier_element { + ltt_session_destroy_notifier notifier; + void *user_data; +}; + +struct ltt_session_clear_notifier_element { + ltt_session_clear_notifier notifier; + void *user_data; +}; + +/* + * NOTES: + * + * No ltt_session.lock is taken here because those data structure are widely + * spread across the lttng-tools code base so before calling functions below + * that can read/write a session, the caller MUST acquire the session lock + * using session_lock() and session_unlock(). + */ + +/* + * Init tracing session list. + * + * Please see session.h for more explanation and correct usage of the list. + */ +static struct ltt_session_list ltt_session_list = { + .lock = PTHREAD_MUTEX_INITIALIZER, + .removal_cond = PTHREAD_COND_INITIALIZER, + .next_uuid = 0, + .head = CDS_LIST_HEAD_INIT(ltt_session_list.head), +}; + +/* These characters are forbidden in a session name. Used by validate_name. */ +static const char *forbidden_name_chars = "/"; + +/* Global hash table to keep the sessions, indexed by id. */ +static struct lttng_ht *ltt_sessions_ht_by_id = NULL; +/* Global hash table to keep the sessions, indexed by name. */ +static struct lttng_ht *ltt_sessions_ht_by_name = NULL; + +/* + * Validate the session name for forbidden characters. + * + * Return 0 on success else -1 meaning a forbidden char. has been found. + */ +static int validate_name(const char *name) +{ + int ret; + char *tok, *tmp_name; + + LTTNG_ASSERT(name); + + tmp_name = strdup(name); + if (!tmp_name) { + /* ENOMEM here. */ + ret = -1; + goto error; + } + + tok = strpbrk(tmp_name, forbidden_name_chars); + if (tok) { + DBG("Session name %s contains a forbidden character", name); + /* Forbidden character has been found. */ + ret = -1; + goto error; + } + ret = 0; + +error: + free(tmp_name); + return ret; +} + +/* + * Add a ltt_session structure to the global list. + * + * The caller MUST acquire the session list lock before. + * Returns the unique identifier for the session. + */ +static uint64_t add_session_list(struct ltt_session *ls) +{ + LTTNG_ASSERT(ls); + + cds_list_add(&ls->list, <t_session_list.head); + return ltt_session_list.next_uuid++; +} + +/* + * Delete a ltt_session structure to the global list. + * + * The caller MUST acquire the session list lock before. + */ +static void del_session_list(struct ltt_session *ls) +{ + LTTNG_ASSERT(ls); + + cds_list_del(&ls->list); +} + +/* + * Return a pointer to the session list. + */ +struct ltt_session_list *session_get_list(void) +{ + return <t_session_list; +} + +/* + * Returns once the session list is empty. + */ +void session_list_wait_empty(void) +{ + pthread_mutex_lock(<t_session_list.lock); + while (!cds_list_empty(<t_session_list.head)) { + pthread_cond_wait(<t_session_list.removal_cond, + <t_session_list.lock); + } + pthread_mutex_unlock(<t_session_list.lock); +} + +/* + * Acquire session list lock + */ +void session_lock_list(void) +{ + pthread_mutex_lock(<t_session_list.lock); +} + +/* + * Try to acquire session list lock + */ +int session_trylock_list(void) +{ + return pthread_mutex_trylock(<t_session_list.lock); +} + +/* + * Release session list lock + */ +void session_unlock_list(void) +{ + pthread_mutex_unlock(<t_session_list.lock); +} + +/* + * Get the session's consumer destination type. + * + * The caller must hold the session lock. + */ +enum consumer_dst_type session_get_consumer_destination_type( + const struct ltt_session *session) +{ + /* + * The output information is duplicated in both of those session types. + * Hence, it doesn't matter from which it is retrieved. However, it is + * possible for only one of them to be set. + */ + return session->kernel_session ? + session->kernel_session->consumer->type : + session->ust_session->consumer->type; +} + +/* + * Get the session's consumer network hostname. + * The caller must ensure that the destination is of type "net". + * + * The caller must hold the session lock. + */ +const char *session_get_net_consumer_hostname(const struct ltt_session *session) +{ + const char *hostname = NULL; + const struct consumer_output *output; + + output = session->kernel_session ? + session->kernel_session->consumer : + session->ust_session->consumer; + + /* + * hostname is assumed to be the same for both control and data + * connections. + */ + switch (output->dst.net.control.dtype) { + case LTTNG_DST_IPV4: + hostname = output->dst.net.control.dst.ipv4; + break; + case LTTNG_DST_IPV6: + hostname = output->dst.net.control.dst.ipv6; + break; + default: + abort(); + } + return hostname; +} + +/* + * Get the session's consumer network control and data ports. + * The caller must ensure that the destination is of type "net". + * + * The caller must hold the session lock. + */ +void session_get_net_consumer_ports(const struct ltt_session *session, + uint16_t *control_port, uint16_t *data_port) +{ + const struct consumer_output *output; + + output = session->kernel_session ? + session->kernel_session->consumer : + session->ust_session->consumer; + *control_port = output->dst.net.control.port; + *data_port = output->dst.net.data.port; +} + +/* + * Get the location of the latest trace archive produced by a rotation. + * + * The caller must hold the session lock. + */ +struct lttng_trace_archive_location *session_get_trace_archive_location( + const struct ltt_session *session) +{ + int ret; + struct lttng_trace_archive_location *location = NULL; + char *chunk_path = NULL; + + if (session->rotation_state != LTTNG_ROTATION_STATE_COMPLETED || + !session->last_archived_chunk_name) { + goto end; + } + + switch (session_get_consumer_destination_type(session)) { + case CONSUMER_DST_LOCAL: + ret = asprintf(&chunk_path, + "%s/" DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY "/%s", + session_get_base_path(session), + session->last_archived_chunk_name); + if (ret == -1) { + goto end; + } + location = lttng_trace_archive_location_local_create( + chunk_path); + break; + case CONSUMER_DST_NET: + { + const char *hostname; + uint16_t control_port, data_port; + + hostname = session_get_net_consumer_hostname(session); + session_get_net_consumer_ports(session, + &control_port, + &data_port); + location = lttng_trace_archive_location_relay_create( + hostname, + LTTNG_TRACE_ARCHIVE_LOCATION_RELAY_PROTOCOL_TYPE_TCP, + control_port, data_port, session->last_chunk_path); + break; + } + default: + abort(); + } +end: + free(chunk_path); + return location; +} + +/* + * Allocate the ltt_sessions_ht_by_id and ltt_sessions_ht_by_name HT. + * + * The session list lock must be held. + */ +static int ltt_sessions_ht_alloc(void) +{ + int ret = 0; + + DBG("Allocating ltt_sessions_ht_by_id"); + ltt_sessions_ht_by_id = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + if (!ltt_sessions_ht_by_id) { + ret = -1; + ERR("Failed to allocate ltt_sessions_ht_by_id"); + goto end; + } + + DBG("Allocating ltt_sessions_ht_by_name"); + ltt_sessions_ht_by_name = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); + if (!ltt_sessions_ht_by_name) { + ret = -1; + ERR("Failed to allocate ltt_sessions_ht_by_name"); + goto end; + } + +end: + return ret; +} + +/* + * Destroy the ltt_sessions_ht_by_id HT. + * + * The session list lock must be held. + */ +static void ltt_sessions_ht_destroy(void) +{ + if (ltt_sessions_ht_by_id) { + ht_cleanup_push(ltt_sessions_ht_by_id); + ltt_sessions_ht_by_id = NULL; + } + + if (ltt_sessions_ht_by_name) { + ht_cleanup_push(ltt_sessions_ht_by_name); + ltt_sessions_ht_by_name = NULL; + } + + return; +} + +/* + * Add a ltt_session to the ltt_sessions_ht_by_id and ltt_sessions_ht_by_name. + * If unallocated, the ltt_sessions_ht_by_id and ltt_sessions_ht_by_name. HTs + * are allocated. The session list lock must be held. + */ +static void add_session_ht(struct ltt_session *ls) +{ + int ret; + + LTTNG_ASSERT(ls); + + if (!ltt_sessions_ht_by_id) { + ret = ltt_sessions_ht_alloc(); + if (ret) { + ERR("Error allocating the sessions HT"); + goto end; + } + } + + /* Should always be present with ltt_sessions_ht_by_id. */ + LTTNG_ASSERT(ltt_sessions_ht_by_name); + + lttng_ht_node_init_u64(&ls->node, ls->id); + lttng_ht_add_unique_u64(ltt_sessions_ht_by_id, &ls->node); + + lttng_ht_node_init_str(&ls->node_by_name, ls->name); + lttng_ht_add_unique_str(ltt_sessions_ht_by_name, &ls->node_by_name); + +end: + return; +} + +/* + * Test if ltt_sessions_ht_by_id/name are empty. + * Return 1 if empty, 0 if not empty. + * The session list lock must be held. + */ +static int ltt_sessions_ht_empty(void) +{ + unsigned long count; + + if (!ltt_sessions_ht_by_id) { + count = 0; + goto end; + } + + LTTNG_ASSERT(ltt_sessions_ht_by_name); + + count = lttng_ht_get_count(ltt_sessions_ht_by_id); + LTTNG_ASSERT(count == lttng_ht_get_count(ltt_sessions_ht_by_name)); +end: + return count ? 0 : 1; +} + +/* + * Remove a ltt_session from the ltt_sessions_ht_by_id/name. + * If empty, the ltt_sessions_ht_by_id/name HTs are freed. + * The session list lock must be held. + */ +static void del_session_ht(struct ltt_session *ls) +{ + struct lttng_ht_iter iter; + int ret; + + LTTNG_ASSERT(ls); + LTTNG_ASSERT(ltt_sessions_ht_by_id); + LTTNG_ASSERT(ltt_sessions_ht_by_name); + + iter.iter.node = &ls->node.node; + ret = lttng_ht_del(ltt_sessions_ht_by_id, &iter); + LTTNG_ASSERT(!ret); + + iter.iter.node = &ls->node_by_name.node; + ret = lttng_ht_del(ltt_sessions_ht_by_name, &iter); + LTTNG_ASSERT(!ret); + + if (ltt_sessions_ht_empty()) { + DBG("Empty ltt_sessions_ht_by_id/name, destroying hast tables"); + ltt_sessions_ht_destroy(); + } +} + +/* + * Acquire session lock + */ +void session_lock(struct ltt_session *session) +{ + LTTNG_ASSERT(session); + + pthread_mutex_lock(&session->lock); +} + +/* + * Release session lock + */ +void session_unlock(struct ltt_session *session) +{ + LTTNG_ASSERT(session); + + pthread_mutex_unlock(&session->lock); +} + +static +int _session_set_trace_chunk_no_lock_check(struct ltt_session *session, + struct lttng_trace_chunk *new_trace_chunk, + struct lttng_trace_chunk **_current_trace_chunk) +{ + int ret = 0; + unsigned int i, refs_to_acquire = 0, refs_acquired = 0, refs_to_release = 0; + struct cds_lfht_iter iter; + struct consumer_socket *socket; + struct lttng_trace_chunk *current_trace_chunk; + uint64_t chunk_id; + enum lttng_trace_chunk_status chunk_status; + + rcu_read_lock(); + /* + * Ownership of current trace chunk is transferred to + * `current_trace_chunk`. + */ + current_trace_chunk = session->current_trace_chunk; + session->current_trace_chunk = NULL; + if (session->ust_session) { + lttng_trace_chunk_put( + session->ust_session->current_trace_chunk); + session->ust_session->current_trace_chunk = NULL; + } + if (session->kernel_session) { + lttng_trace_chunk_put( + session->kernel_session->current_trace_chunk); + session->kernel_session->current_trace_chunk = NULL; + } + if (!new_trace_chunk) { + ret = 0; + goto end; + } + chunk_status = lttng_trace_chunk_get_id(new_trace_chunk, &chunk_id); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + + refs_to_acquire = 1; + refs_to_acquire += !!session->ust_session; + refs_to_acquire += !!session->kernel_session; + + for (refs_acquired = 0; refs_acquired < refs_to_acquire; + refs_acquired++) { + if (!lttng_trace_chunk_get(new_trace_chunk)) { + ERR("Failed to acquire reference to new trace chunk of session \"%s\"", + session->name); + goto error; + } + } + + if (session->ust_session) { + const uint64_t relayd_id = + session->ust_session->consumer->net_seq_index; + const bool is_local_trace = + session->ust_session->consumer->type == + CONSUMER_DST_LOCAL; + + session->ust_session->current_trace_chunk = new_trace_chunk; + if (is_local_trace) { + enum lttng_error_code ret_error_code; + + ret_error_code = ust_app_create_channel_subdirectories( + session->ust_session); + if (ret_error_code != LTTNG_OK) { + goto error; + } + } + cds_lfht_for_each_entry( + session->ust_session->consumer->socks->ht, + &iter, socket, node.node) { + pthread_mutex_lock(socket->lock); + ret = consumer_create_trace_chunk(socket, + relayd_id, + session->id, new_trace_chunk, + DEFAULT_UST_TRACE_DIR); + pthread_mutex_unlock(socket->lock); + if (ret) { + goto error; + } + } + } + if (session->kernel_session) { + const uint64_t relayd_id = + session->kernel_session->consumer->net_seq_index; + const bool is_local_trace = + session->kernel_session->consumer->type == + CONSUMER_DST_LOCAL; + + session->kernel_session->current_trace_chunk = new_trace_chunk; + if (is_local_trace) { + enum lttng_error_code ret_error_code; + + ret_error_code = kernel_create_channel_subdirectories( + session->kernel_session); + if (ret_error_code != LTTNG_OK) { + goto error; + } + } + cds_lfht_for_each_entry( + session->kernel_session->consumer->socks->ht, + &iter, socket, node.node) { + pthread_mutex_lock(socket->lock); + ret = consumer_create_trace_chunk(socket, + relayd_id, + session->id, new_trace_chunk, + DEFAULT_KERNEL_TRACE_DIR); + pthread_mutex_unlock(socket->lock); + if (ret) { + goto error; + } + } + } + + /* + * Update local current trace chunk state last, only if all remote + * creations succeeded. + */ + session->current_trace_chunk = new_trace_chunk; + LTTNG_OPTIONAL_SET(&session->most_recent_chunk_id, chunk_id); +end: + if (_current_trace_chunk) { + *_current_trace_chunk = current_trace_chunk; + current_trace_chunk = NULL; + } +end_no_move: + rcu_read_unlock(); + lttng_trace_chunk_put(current_trace_chunk); + return ret; +error: + if (session->ust_session) { + session->ust_session->current_trace_chunk = NULL; + } + if (session->kernel_session) { + session->kernel_session->current_trace_chunk = NULL; + } + /* + * Release references taken in the case where all references could not + * be acquired. + */ + refs_to_release = refs_to_acquire - refs_acquired; + for (i = 0; i < refs_to_release; i++) { + lttng_trace_chunk_put(new_trace_chunk); + } + ret = -1; + goto end_no_move; +} + +struct lttng_trace_chunk *session_create_new_trace_chunk( + const struct ltt_session *session, + const struct consumer_output *consumer_output_override, + const char *session_base_path_override, + const char *chunk_name_override) +{ + int ret; + struct lttng_trace_chunk *trace_chunk = NULL; + enum lttng_trace_chunk_status chunk_status; + const time_t chunk_creation_ts = time(NULL); + bool is_local_trace; + const char *base_path; + struct lttng_directory_handle *session_output_directory = NULL; + const struct lttng_credentials session_credentials = { + .uid = LTTNG_OPTIONAL_INIT_VALUE(session->uid), + .gid = LTTNG_OPTIONAL_INIT_VALUE(session->gid), + }; + uint64_t next_chunk_id; + const struct consumer_output *output; + const char *new_path; + + if (consumer_output_override) { + output = consumer_output_override; + } else { + LTTNG_ASSERT(session->ust_session || session->kernel_session); + output = session->ust_session ? + session->ust_session->consumer : + session->kernel_session->consumer; + } + + is_local_trace = output->type == CONSUMER_DST_LOCAL; + base_path = session_base_path_override ? : + consumer_output_get_base_path(output); + + if (chunk_creation_ts == (time_t) -1) { + PERROR("Failed to sample time while creation session \"%s\" trace chunk", + session->name); + goto error; + } + + next_chunk_id = session->most_recent_chunk_id.is_set ? + session->most_recent_chunk_id.value + 1 : 0; + + if (session->current_trace_chunk && + !lttng_trace_chunk_get_name_overridden(session->current_trace_chunk)) { + chunk_status = lttng_trace_chunk_rename_path(session->current_trace_chunk, + DEFAULT_CHUNK_TMP_OLD_DIRECTORY); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + goto error; + } + } + if (!session->current_trace_chunk) { + if (!session->rotated) { + new_path = ""; + } else { + new_path = NULL; + } + } else { + new_path = DEFAULT_CHUNK_TMP_NEW_DIRECTORY; + } + + trace_chunk = lttng_trace_chunk_create(next_chunk_id, + chunk_creation_ts, new_path); + if (!trace_chunk) { + goto error; + } + + if (chunk_name_override) { + chunk_status = lttng_trace_chunk_override_name(trace_chunk, + chunk_name_override); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + goto error; + } + } + + if (!is_local_trace) { + /* + * No need to set crendentials and output directory + * for remote trace chunks. + */ + goto end; + } + + chunk_status = lttng_trace_chunk_set_credentials(trace_chunk, + &session_credentials); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + goto error; + } + + DBG("Creating base output directory of session \"%s\" at %s", + session->name, base_path); + ret = utils_mkdir_recursive(base_path, S_IRWXU | S_IRWXG, + session->uid, session->gid); + if (ret) { + goto error; + } + session_output_directory = lttng_directory_handle_create(base_path); + if (!session_output_directory) { + goto error; + } + chunk_status = lttng_trace_chunk_set_as_owner(trace_chunk, + session_output_directory); + lttng_directory_handle_put(session_output_directory); + session_output_directory = NULL; + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + goto error; + } +end: + return trace_chunk; +error: + lttng_directory_handle_put(session_output_directory); + lttng_trace_chunk_put(trace_chunk); + trace_chunk = NULL; + goto end; +} + +int session_close_trace_chunk(struct ltt_session *session, + struct lttng_trace_chunk *trace_chunk, + enum lttng_trace_chunk_command_type close_command, + char *closed_trace_chunk_path) +{ + int ret = 0; + bool error_occurred = false; + struct cds_lfht_iter iter; + struct consumer_socket *socket; + enum lttng_trace_chunk_status chunk_status; + const time_t chunk_close_timestamp = time(NULL); + const char *new_path; + + chunk_status = lttng_trace_chunk_set_close_command( + trace_chunk, close_command); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = -1; + goto end; + } + + if (chunk_close_timestamp == (time_t) -1) { + ERR("Failed to sample the close timestamp of the current trace chunk of session \"%s\"", + session->name); + ret = -1; + goto end; + } + + if (close_command == LTTNG_TRACE_CHUNK_COMMAND_TYPE_DELETE && !session->rotated) { + /* New chunk stays in session output directory. */ + new_path = ""; + } else { + /* Use chunk name for new chunk. */ + new_path = NULL; + } + if (session->current_trace_chunk && + !lttng_trace_chunk_get_name_overridden(session->current_trace_chunk)) { + /* Rename new chunk path. */ + chunk_status = lttng_trace_chunk_rename_path(session->current_trace_chunk, + new_path); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = -1; + goto end; + } + } + if (!lttng_trace_chunk_get_name_overridden(trace_chunk) && + close_command == LTTNG_TRACE_CHUNK_COMMAND_TYPE_NO_OPERATION) { + const char *old_path; + + if (!session->rotated) { + old_path = ""; + } else { + old_path = NULL; + } + /* We need to move back the .tmp_old_chunk to its rightful place. */ + chunk_status = lttng_trace_chunk_rename_path(trace_chunk, + old_path); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = -1; + goto end; + } + } + if (close_command == LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED) { + session->rotated = true; + } + chunk_status = lttng_trace_chunk_set_close_timestamp(trace_chunk, + chunk_close_timestamp); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ERR("Failed to set the close timestamp of the current trace chunk of session \"%s\"", + session->name); + ret = -1; + goto end; + } + + if (session->ust_session) { + const uint64_t relayd_id = + session->ust_session->consumer->net_seq_index; + + cds_lfht_for_each_entry( + session->ust_session->consumer->socks->ht, + &iter, socket, node.node) { + pthread_mutex_lock(socket->lock); + ret = consumer_close_trace_chunk(socket, + relayd_id, + session->id, + trace_chunk, closed_trace_chunk_path); + pthread_mutex_unlock(socket->lock); + if (ret) { + ERR("Failed to close trace chunk on user space consumer"); + error_occurred = true; + } + } + } + if (session->kernel_session) { + const uint64_t relayd_id = + session->kernel_session->consumer->net_seq_index; + + cds_lfht_for_each_entry( + session->kernel_session->consumer->socks->ht, + &iter, socket, node.node) { + pthread_mutex_lock(socket->lock); + ret = consumer_close_trace_chunk(socket, + relayd_id, + session->id, + trace_chunk, closed_trace_chunk_path); + pthread_mutex_unlock(socket->lock); + if (ret) { + ERR("Failed to close trace chunk on kernel consumer"); + error_occurred = true; + } + } + } + ret = error_occurred ? -1 : 0; +end: + return ret; +} + +/* + * This function skips the metadata channel as the begin/end timestamps of a + * metadata packet are useless. + * + * Moreover, opening a packet after a "clear" will cause problems for live + * sessions as it will introduce padding that was not part of the first trace + * chunk. The relay daemon expects the content of the metadata stream of + * successive metadata trace chunks to be strict supersets of one another. + * + * For example, flushing a packet at the beginning of the metadata stream of + * a trace chunk resulting from a "clear" session command will cause the + * size of the metadata stream of the new trace chunk to not match the size of + * the metadata stream of the original chunk. This will confuse the relay + * daemon as the same "offset" in a metadata stream will no longer point + * to the same content. + */ +static +enum lttng_error_code session_kernel_open_packets(struct ltt_session *session) +{ + enum lttng_error_code ret = LTTNG_OK; + struct consumer_socket *socket; + struct lttng_ht_iter iter; + struct cds_lfht_node *node; + struct ltt_kernel_channel *chan; + + rcu_read_lock(); + + cds_lfht_first(session->kernel_session->consumer->socks->ht, &iter.iter); + node = cds_lfht_iter_get_node(&iter.iter); + socket = container_of(node, typeof(*socket), node.node); + + cds_list_for_each_entry(chan, + &session->kernel_session->channel_list.head, list) { + int open_ret; + + DBG("Open packet of kernel channel: channel key = %" PRIu64 + ", session name = %s, session_id = %" PRIu64, + chan->key, session->name, session->id); + + open_ret = consumer_open_channel_packets(socket, chan->key); + if (open_ret < 0) { + /* General error (no known error expected). */ + ret = LTTNG_ERR_UNK; + goto end; + } + } + +end: + rcu_read_unlock(); + return ret; +} + +enum lttng_error_code session_open_packets(struct ltt_session *session) +{ + enum lttng_error_code ret = LTTNG_OK; + + DBG("Opening packets of session channels: session name = %s, session id = %" PRIu64, + session->name, session->id); + + if (session->ust_session) { + ret = ust_app_open_packets(session); + if (ret != LTTNG_OK) { + goto end; + } + } + + if (session->kernel_session) { + ret = session_kernel_open_packets(session); + if (ret != LTTNG_OK) { + goto end; + } + } + +end: + return ret; +} + +/* + * Set a session's current trace chunk. + * + * Must be called with the session lock held. + */ +int session_set_trace_chunk(struct ltt_session *session, + struct lttng_trace_chunk *new_trace_chunk, + struct lttng_trace_chunk **current_trace_chunk) +{ + ASSERT_LOCKED(session->lock); + return _session_set_trace_chunk_no_lock_check(session, new_trace_chunk, + current_trace_chunk); +} + +static +void session_notify_destruction(const struct ltt_session *session) +{ + size_t i; + const size_t count = lttng_dynamic_array_get_count( + &session->destroy_notifiers); + + for (i = 0; i < count; i++) { + const struct ltt_session_destroy_notifier_element *element = + (ltt_session_destroy_notifier_element *) lttng_dynamic_array_get_element( + &session->destroy_notifiers, i); + + element->notifier(session, element->user_data); + } +} + +/* + * Fire each clear notifier once, and remove them from the array. + */ +void session_notify_clear(struct ltt_session *session) +{ + size_t i; + const size_t count = lttng_dynamic_array_get_count( + &session->clear_notifiers); + + for (i = 0; i < count; i++) { + const struct ltt_session_clear_notifier_element *element = + (ltt_session_clear_notifier_element *) lttng_dynamic_array_get_element( + &session->clear_notifiers, i); + + element->notifier(session, element->user_data); + } + lttng_dynamic_array_clear(&session->clear_notifiers); +} + +static +void session_release(struct urcu_ref *ref) +{ + int ret; + struct ltt_ust_session *usess; + struct ltt_kernel_session *ksess; + struct ltt_session *session = container_of(ref, typeof(*session), ref); + const bool session_published = session->published; + + LTTNG_ASSERT(!session->chunk_being_archived); + + usess = session->ust_session; + ksess = session->kernel_session; + + /* Clean kernel session teardown, keeping data for destroy notifier. */ + kernel_destroy_session(ksess); + + /* UST session teardown, keeping data for destroy notifier. */ + if (usess) { + /* Close any relayd session */ + consumer_output_send_destroy_relayd(usess->consumer); + + /* Destroy every UST application related to this session. */ + ret = ust_app_destroy_trace_all(usess); + if (ret) { + ERR("Error in ust_app_destroy_trace_all"); + } + + /* Clean up the rest, keeping destroy notifier data. */ + trace_ust_destroy_session(usess); + } + + /* + * Must notify the kernel thread here to update it's poll set in order to + * remove the channel(s)' fd just destroyed. + */ + ret = notify_thread_pipe(the_kernel_poll_pipe[1]); + if (ret < 0) { + PERROR("write kernel poll pipe"); + } + + DBG("Destroying session %s (id %" PRIu64 ")", session->name, session->id); + + snapshot_destroy(&session->snapshot); + + pthread_mutex_destroy(&session->lock); + + if (session_published) { + ASSERT_LOCKED(ltt_session_list.lock); + del_session_list(session); + del_session_ht(session); + } + session_notify_destruction(session); + + consumer_output_put(session->consumer); + kernel_free_session(ksess); + session->kernel_session = NULL; + if (usess) { + trace_ust_free_session(usess); + session->ust_session = NULL; + } + lttng_dynamic_array_reset(&session->destroy_notifiers); + lttng_dynamic_array_reset(&session->clear_notifiers); + free(session->last_archived_chunk_name); + free(session->base_path); + free(session); + if (session_published) { + /* + * Broadcast after free-ing to ensure the memory is + * reclaimed before the main thread exits. + */ + ASSERT_LOCKED(ltt_session_list.lock); + pthread_cond_broadcast(<t_session_list.removal_cond); + } +} + +/* + * Acquire a reference to a session. + * This function may fail (return false); its return value must be checked. + */ +bool session_get(struct ltt_session *session) +{ + return urcu_ref_get_unless_zero(&session->ref); +} + +/* + * Release a reference to a session. + */ +void session_put(struct ltt_session *session) +{ + if (!session) { + return; + } + /* + * The session list lock must be held as any session_put() + * may cause the removal of the session from the session_list. + */ + ASSERT_LOCKED(ltt_session_list.lock); + LTTNG_ASSERT(session->ref.refcount); + urcu_ref_put(&session->ref, session_release); +} + +/* + * Destroy a session. + * + * This method does not immediately release/free the session as other + * components may still hold a reference to the session. However, + * the session should no longer be presented to the user. + * + * Releases the session list's reference to the session + * and marks it as destroyed. Iterations on the session list should be + * mindful of the "destroyed" flag. + */ +void session_destroy(struct ltt_session *session) +{ + LTTNG_ASSERT(!session->destroyed); + session->destroyed = true; + session_put(session); +} + +int session_add_destroy_notifier(struct ltt_session *session, + ltt_session_destroy_notifier notifier, void *user_data) +{ + const struct ltt_session_destroy_notifier_element element = { + .notifier = notifier, + .user_data = user_data + }; + + return lttng_dynamic_array_add_element(&session->destroy_notifiers, + &element); +} + +int session_add_clear_notifier(struct ltt_session *session, + ltt_session_clear_notifier notifier, void *user_data) +{ + const struct ltt_session_clear_notifier_element element = { + .notifier = notifier, + .user_data = user_data + }; + + return lttng_dynamic_array_add_element(&session->clear_notifiers, + &element); +} + +/* + * Return a ltt_session structure ptr that matches name. If no session found, + * NULL is returned. This must be called with the session list lock held using + * session_lock_list and session_unlock_list. + * A reference to the session is implicitly acquired by this function. + */ +struct ltt_session *session_find_by_name(const char *name) +{ + struct ltt_session *iter; + + LTTNG_ASSERT(name); + ASSERT_LOCKED(ltt_session_list.lock); + + DBG2("Trying to find session by name %s", name); + + cds_list_for_each_entry(iter, <t_session_list.head, list) { + if (!strncmp(iter->name, name, NAME_MAX) && + !iter->destroyed) { + goto found; + } + } + + return NULL; +found: + return session_get(iter) ? iter : NULL; +} + +/* + * Return an ltt_session that matches the id. If no session is found, + * NULL is returned. This must be called with rcu_read_lock and + * session list lock held (to guarantee the lifetime of the session). + */ +struct ltt_session *session_find_by_id(uint64_t id) +{ + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + struct ltt_session *ls; + + ASSERT_LOCKED(ltt_session_list.lock); + + if (!ltt_sessions_ht_by_id) { + goto end; + } + + lttng_ht_lookup(ltt_sessions_ht_by_id, &id, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (node == NULL) { + goto end; + } + ls = caa_container_of(node, struct ltt_session, node); + + DBG3("Session %" PRIu64 " found by id.", id); + return session_get(ls) ? ls : NULL; + +end: + DBG3("Session %" PRIu64 " NOT found by id", id); + return NULL; +} + +/* + * Create a new session and add it to the session list. + * Session list lock must be held by the caller. + */ +enum lttng_error_code session_create(const char *name, uid_t uid, gid_t gid, + struct ltt_session **out_session) +{ + int ret; + enum lttng_error_code ret_code; + struct ltt_session *new_session = NULL; + + ASSERT_LOCKED(ltt_session_list.lock); + if (name) { + struct ltt_session *clashing_session; + + clashing_session = session_find_by_name(name); + if (clashing_session) { + session_put(clashing_session); + ret_code = LTTNG_ERR_EXIST_SESS; + goto error; + } + } + new_session = (ltt_session *) zmalloc(sizeof(struct ltt_session)); + if (!new_session) { + PERROR("Failed to allocate an ltt_session structure"); + ret_code = LTTNG_ERR_NOMEM; + goto error; + } + + lttng_dynamic_array_init(&new_session->destroy_notifiers, + sizeof(struct ltt_session_destroy_notifier_element), + NULL); + lttng_dynamic_array_init(&new_session->clear_notifiers, + sizeof(struct ltt_session_clear_notifier_element), + NULL); + urcu_ref_init(&new_session->ref); + pthread_mutex_init(&new_session->lock, NULL); + + new_session->creation_time = time(NULL); + if (new_session->creation_time == (time_t) -1) { + PERROR("Failed to sample session creation time"); + ret_code = LTTNG_ERR_SESSION_FAIL; + goto error; + } + + /* Create default consumer output. */ + new_session->consumer = consumer_create_output(CONSUMER_DST_LOCAL); + if (new_session->consumer == NULL) { + ret_code = LTTNG_ERR_NOMEM; + goto error; + } + + if (name) { + ret = lttng_strncpy(new_session->name, name, sizeof(new_session->name)); + if (ret) { + ret_code = LTTNG_ERR_SESSION_INVALID_CHAR; + goto error; + } + ret = validate_name(name); + if (ret < 0) { + ret_code = LTTNG_ERR_SESSION_INVALID_CHAR; + goto error; + } + } else { + int i = 0; + bool found_name = false; + char datetime[16]; + struct tm *timeinfo; + + timeinfo = localtime(&new_session->creation_time); + if (!timeinfo) { + ret_code = LTTNG_ERR_SESSION_FAIL; + goto error; + } + strftime(datetime, sizeof(datetime), "%Y%m%d-%H%M%S", timeinfo); + for (i = 0; i < INT_MAX; i++) { + struct ltt_session *clashing_session; + + if (i == 0) { + ret = snprintf(new_session->name, + sizeof(new_session->name), + "%s-%s", + DEFAULT_SESSION_NAME, + datetime); + } else { + ret = snprintf(new_session->name, + sizeof(new_session->name), + "%s%d-%s", + DEFAULT_SESSION_NAME, i, + datetime); + } + new_session->name_contains_creation_time = true; + if (ret == -1 || ret >= sizeof(new_session->name)) { + /* + * Null-terminate in case the name is used + * in logging statements. + */ + new_session->name[sizeof(new_session->name) - 1] = '\0'; + ret_code = LTTNG_ERR_SESSION_FAIL; + goto error; + } + + clashing_session = + session_find_by_name(new_session->name); + session_put(clashing_session); + if (!clashing_session) { + found_name = true; + break; + } + } + if (found_name) { + DBG("Generated session name \"%s\"", new_session->name); + new_session->has_auto_generated_name = true; + } else { + ERR("Failed to auto-generate a session name"); + ret_code = LTTNG_ERR_SESSION_FAIL; + goto error; + } + } + + ret = gethostname(new_session->hostname, sizeof(new_session->hostname)); + if (ret < 0) { + if (errno == ENAMETOOLONG) { + new_session->hostname[sizeof(new_session->hostname) - 1] = '\0'; + ERR("Hostname exceeds the maximal permitted length and has been truncated to %s", + new_session->hostname); + } else { + ret_code = LTTNG_ERR_SESSION_FAIL; + goto error; + } + } + + new_session->uid = uid; + new_session->gid = gid; + + ret = snapshot_init(&new_session->snapshot); + if (ret < 0) { + ret_code = LTTNG_ERR_NOMEM; + goto error; + } + + new_session->rotation_state = LTTNG_ROTATION_STATE_NO_ROTATION; + + /* Add new session to the session list. */ + new_session->id = add_session_list(new_session); + + /* + * Add the new session to the ltt_sessions_ht_by_id. + * No ownership is taken by the hash table; it is merely + * a wrapper around the session list used for faster access + * by session id. + */ + add_session_ht(new_session); + new_session->published = true; + + /* + * Consumer is left to NULL since the create_session_uri command will + * set it up and, if valid, assign it to the session. + */ + DBG("Tracing session %s created with ID %" PRIu64 " by uid = %d, gid = %d", + new_session->name, new_session->id, new_session->uid, + new_session->gid); + ret_code = LTTNG_OK; +end: + if (new_session) { + (void) session_get(new_session); + *out_session = new_session; + } + return ret_code; +error: + session_put(new_session); + new_session = NULL; + goto end; +} + +/* + * Check if the UID matches the session. Root user has access to all + * sessions. + */ +bool session_access_ok(struct ltt_session *session, uid_t uid) +{ + LTTNG_ASSERT(session); + return (uid == session->uid) || uid == 0; +} + +/* + * Set a session's rotation state and reset all associated state. + * + * This function resets the rotation state (check timers, pending + * flags, etc.) and sets the result of the last rotation. The result + * can be queries by a liblttng-ctl client. + * + * Be careful of the result passed to this function. For instance, + * on failure to launch a rotation, a client will expect the rotation + * state to be set to "NO_ROTATION". If an error occurred while the + * rotation was "ONGOING", result should be set to "ERROR", which will + * allow a client to report it. + * + * Must be called with the session and session_list locks held. + */ +int session_reset_rotation_state(struct ltt_session *session, + enum lttng_rotation_state result) +{ + int ret = 0; + + ASSERT_LOCKED(ltt_session_list.lock); + ASSERT_LOCKED(session->lock); + + session->rotation_state = result; + if (session->rotation_pending_check_timer_enabled) { + ret = timer_session_rotation_pending_check_stop(session); + } + if (session->chunk_being_archived) { + uint64_t chunk_id; + enum lttng_trace_chunk_status chunk_status; + + chunk_status = lttng_trace_chunk_get_id( + session->chunk_being_archived, + &chunk_id); + LTTNG_ASSERT(chunk_status == LTTNG_TRACE_CHUNK_STATUS_OK); + LTTNG_OPTIONAL_SET(&session->last_archived_chunk_id, + chunk_id); + lttng_trace_chunk_put(session->chunk_being_archived); + session->chunk_being_archived = NULL; + /* + * Fire the clear reply notifiers if we are completing a clear + * rotation. + */ + session_notify_clear(session); + } + return ret; +} + +/* + * Sample the id of a session looked up via its name. + * Here the term "sampling" hint the caller that this return the id at a given + * point in time with no guarantee that the session for which the id was + * sampled still exist at that point. + * + * Return 0 when the session is not found, + * Return 1 when the session is found and set `id`. + */ +bool sample_session_id_by_name(const char *name, uint64_t *id) +{ + bool found = false; + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + struct ltt_session *ls; + + rcu_read_lock(); + + if (!ltt_sessions_ht_by_name) { + found = false; + goto end; + } + + lttng_ht_lookup(ltt_sessions_ht_by_name, name, &iter); + node = lttng_ht_iter_get_node_str(&iter); + if (node == NULL) { + found = false; + goto end; + } + + ls = caa_container_of(node, struct ltt_session, node_by_name); + *id = ls->id; + found = true; + + DBG3("Session id `%" PRIu64 "` sampled for session `%s", *id, name); +end: + rcu_read_unlock(); + return found; +} diff --git a/src/bin/lttng-sessiond/sessiond-config.c b/src/bin/lttng-sessiond/sessiond-config.c deleted file mode 100644 index 707e57950..000000000 --- a/src/bin/lttng-sessiond/sessiond-config.c +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Copyright (C) 2017 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "version.h" -#include "sessiond-config.h" -#include "lttng-ust-ctl.h" -#include -#include -#include -#include -#include -#include -#include - -static -struct sessiond_config sessiond_config_build_defaults = { - .quiet = false, - .verbose = 0, - .verbose_consumer = 0, - - .agent_tcp_port = { .begin = DEFAULT_AGENT_TCP_PORT_RANGE_BEGIN, .end = DEFAULT_AGENT_TCP_PORT_RANGE_END }, - .event_notifier_buffer_size_kernel = DEFAULT_EVENT_NOTIFIER_ERROR_COUNT_MAP_SIZE, - .event_notifier_buffer_size_userspace = DEFAULT_EVENT_NOTIFIER_ERROR_COUNT_MAP_SIZE, - .app_socket_timeout = DEFAULT_APP_SOCKET_RW_TIMEOUT, - - .no_kernel = false, - .background = false, - .daemonize = false, - .sig_parent = false, - - .tracing_group_name.value = (char *) DEFAULT_TRACING_GROUP, - .kmod_probes_list.value = NULL, - .kmod_extra_probes_list.value = NULL, - - .rundir.value = NULL, - - .apps_unix_sock_path.value = NULL, - .client_unix_sock_path.value = NULL, - .wait_shm_path.value = NULL, - .health_unix_sock_path.value = NULL, - .lttng_ust_clock_plugin.value = NULL, - .pid_file_path.value = NULL, - .lock_file_path.value = NULL, - .agent_port_file_path.value = NULL, - .load_session_path.value = NULL, - - .consumerd32_path.value = NULL, - .consumerd32_bin_path.value = NULL, - .consumerd32_lib_dir.value = NULL, - .consumerd32_err_unix_sock_path.value = NULL, - .consumerd32_cmd_unix_sock_path.value = NULL, - - .consumerd64_path.value = NULL, - .consumerd64_bin_path.value = NULL, - .consumerd64_lib_dir.value = NULL, - .consumerd64_err_unix_sock_path.value = NULL, - .consumerd64_cmd_unix_sock_path.value = NULL, - - .kconsumerd_path.value = NULL, - .kconsumerd_err_unix_sock_path.value = NULL, - .kconsumerd_cmd_unix_sock_path.value = NULL, -}; - -static -void config_string_fini(struct config_string *str) -{ - config_string_set(str, NULL); -} - -static -void config_string_set_static(struct config_string *config_str, - const char *value) -{ - config_string_set(config_str, (char *) value); - config_str->should_free = false; -} - -/* Only use for dynamically-allocated strings. */ -void config_string_set(struct config_string *config_str, char *value) -{ - LTTNG_ASSERT(config_str); - if (config_str->should_free) { - free(config_str->value); - config_str->should_free = false; - } - - config_str->should_free = !!value; - config_str->value = value; -} - -int sessiond_config_apply_env_config(struct sessiond_config *config) -{ - int ret = 0; - const char *env_value; - - env_value = getenv(DEFAULT_APP_SOCKET_TIMEOUT_ENV); - if (env_value) { - char *endptr; - long int_val; - - errno = 0; - int_val = strtoul(env_value, &endptr, 0); - if (errno != 0 || int_val > INT_MAX || - (int_val < 0 && int_val != -1)) { - ERR("Invalid value \"%s\" used for \"%s\" environment variable", - env_value, DEFAULT_APP_SOCKET_TIMEOUT_ENV); - ret = -1; - goto end; - } - - config->app_socket_timeout = int_val; - } - - env_value = lttng_secure_getenv("LTTNG_CONSUMERD32_BIN"); - if (env_value) { - config_string_set_static(&config->consumerd32_bin_path, - env_value); - } - env_value = lttng_secure_getenv("LTTNG_CONSUMERD64_BIN"); - if (env_value) { - config_string_set_static(&config->consumerd64_bin_path, - env_value); - } - - env_value = lttng_secure_getenv("LTTNG_CONSUMERD32_LIBDIR"); - if (env_value) { - config_string_set_static(&config->consumerd32_lib_dir, - env_value); - } - env_value = lttng_secure_getenv("LTTNG_CONSUMERD64_LIBDIR"); - if (env_value) { - config_string_set_static(&config->consumerd64_lib_dir, - env_value); - } - - env_value = lttng_secure_getenv("LTTNG_UST_CLOCK_PLUGIN"); - if (env_value) { - config_string_set_static(&config->lttng_ust_clock_plugin, - env_value); - } - - env_value = lttng_secure_getenv(DEFAULT_LTTNG_KMOD_PROBES); - if (env_value) { - config_string_set_static(&config->kmod_probes_list, - env_value); - } - - env_value = lttng_secure_getenv(DEFAULT_LTTNG_EXTRA_KMOD_PROBES); - if (env_value) { - config_string_set_static(&config->kmod_extra_probes_list, - env_value); - } -end: - return ret; -} - -static -int config_set_paths_root(struct sessiond_config *config) -{ - int ret = 0; - - config_string_set(&config->rundir, strdup(DEFAULT_LTTNG_RUNDIR)); - if (!config->rundir.value) { - ERR("Failed to set rundir"); - ret = -1; - goto end; - } - - config_string_set_static(&config->apps_unix_sock_path, - DEFAULT_GLOBAL_APPS_UNIX_SOCK); - config_string_set_static(&config->client_unix_sock_path, - DEFAULT_GLOBAL_CLIENT_UNIX_SOCK); - config_string_set_static(&config->wait_shm_path, - DEFAULT_GLOBAL_APPS_WAIT_SHM_PATH); - config_string_set_static(&config->health_unix_sock_path, - DEFAULT_GLOBAL_HEALTH_UNIX_SOCK); -end: - return ret; -} - -static -int config_set_paths_non_root(struct sessiond_config *config) -{ - int ret = 0; - const char *home_path = utils_get_home_dir(); - char *str; - - if (home_path == NULL) { - ERR("Can't get HOME directory for sockets creation."); - ret = -1; - goto end; - } - - /* - * Create rundir from home path. This will create something like - * $HOME/.lttng - */ - ret = asprintf(&str, DEFAULT_LTTNG_HOME_RUNDIR, home_path); - if (ret < 0) { - ERR("Failed to set rundir"); - goto end; - } - config_string_set(&config->rundir, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_HOME_APPS_UNIX_SOCK, home_path); - if (ret < 0) { - ERR("Failed to set default home apps unix socket path"); - goto end; - } - config_string_set(&config->apps_unix_sock_path, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_HOME_CLIENT_UNIX_SOCK, home_path); - if (ret < 0) { - ERR("Failed to set default home client unix socket path"); - goto end; - } - config_string_set(&config->client_unix_sock_path, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_HOME_APPS_WAIT_SHM_PATH, getuid()); - if (ret < 0) { - ERR("Failed to set default home apps wait shm path"); - goto end; - } - config_string_set(&config->wait_shm_path, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_HOME_HEALTH_UNIX_SOCK, home_path); - if (ret < 0) { - ERR("Failed to set default home health UNIX socket path"); - goto end; - } - config_string_set(&config->health_unix_sock_path, str); - str = NULL; - - ret = 0; -end: - return ret; -} - -int sessiond_config_init(struct sessiond_config *config) -{ - int ret; - bool is_root = (getuid() == 0); - char *str; - - LTTNG_ASSERT(config); - memcpy(config, &sessiond_config_build_defaults, sizeof(*config)); - - if (is_root) { - ret = config_set_paths_root(config); - } else { - ret = config_set_paths_non_root(config); - } - if (ret < 0) { - goto error; - } - - /* 32 bits consumerd path setup */ - ret = asprintf(&str, DEFAULT_USTCONSUMERD32_PATH, - config->rundir.value); - if (ret < 0) { - ERR("Failed to set 32-bit consumer path"); - goto error; - } - config_string_set(&config->consumerd32_path, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_USTCONSUMERD32_ERR_SOCK_PATH, - config->rundir.value); - if (ret < 0) { - ERR("Failed to set 32-bit consumer error socket path"); - goto error; - } - config_string_set(&config->consumerd32_err_unix_sock_path, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_USTCONSUMERD32_CMD_SOCK_PATH, - config->rundir.value); - if (ret < 0) { - ERR("Failed to set 32-bit consumer command socket path"); - goto error; - } - config_string_set(&config->consumerd32_cmd_unix_sock_path, str); - str = NULL; - - /* 64 bits consumerd path setup */ - ret = asprintf(&str, DEFAULT_USTCONSUMERD64_PATH, - config->rundir.value); - if (ret < 0) { - ERR("Failed to set 64-bit consumer path"); - goto error; - } - config_string_set(&config->consumerd64_path, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_USTCONSUMERD64_ERR_SOCK_PATH, - config->rundir.value); - if (ret < 0) { - ERR("Failed to set 64-bit consumer error socket path"); - goto error; - } - config_string_set(&config->consumerd64_err_unix_sock_path, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_USTCONSUMERD64_CMD_SOCK_PATH, - config->rundir.value); - if (ret < 0) { - ERR("Failed to set 64-bit consumer command socket path"); - goto error; - } - config_string_set(&config->consumerd64_cmd_unix_sock_path, str); - str = NULL; - - /* kconsumerd consumerd path setup */ - ret = asprintf(&str, DEFAULT_KCONSUMERD_PATH, - config->rundir.value); - if (ret < 0) { - ERR("Failed to set kernel consumer path"); - goto error; - } - config_string_set(&config->kconsumerd_path, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_KCONSUMERD_ERR_SOCK_PATH, - config->rundir.value); - if (ret < 0) { - ERR("Failed to set kernel consumer error socket path"); - goto error; - } - config_string_set(&config->kconsumerd_err_unix_sock_path, str); - str = NULL; - - ret = asprintf(&str, DEFAULT_KCONSUMERD_CMD_SOCK_PATH, - config->rundir.value); - if (ret < 0) { - ERR("Failed to set kernel consumer command socket path"); - goto error; - } - config_string_set(&config->kconsumerd_cmd_unix_sock_path, str); - str = NULL; - - ret = asprintf(&str, "%s/%s", config->rundir.value, - DEFAULT_LTTNG_SESSIOND_PIDFILE); - if (ret < 0) { - ERR("Failed to set PID file path"); - goto error; - } - config_string_set(&config->pid_file_path, str); - str = NULL; - - ret = asprintf(&str, "%s/%s", config->rundir.value, - DEFAULT_LTTNG_SESSIOND_LOCKFILE); - if (ret < 0) { - ERR("Failed to set lock file path"); - goto error; - } - config_string_set(&config->lock_file_path, str); - str = NULL; - - ret = asprintf(&str, "%s/%s", config->rundir.value, - DEFAULT_LTTNG_SESSIOND_AGENTPORT_FILE); - if (ret < 0) { - ERR("Failed to set agent port file path"); - goto error; - } - config_string_set(&config->agent_port_file_path, str); - str = NULL; - - /* - * Allow INSTALL_BIN_PATH to be used as a target path for the - * native architecture size consumer if CONFIG_CONSUMER*_PATH - * has not been defined. - */ -#if (CAA_BITS_PER_LONG == 32) - config_string_set_static(&config->consumerd32_bin_path, - INSTALL_BIN_PATH "/" DEFAULT_CONSUMERD_FILE); - config_string_set_static(&config->consumerd32_lib_dir, - INSTALL_LIB_PATH); -#elif (CAA_BITS_PER_LONG == 64) - config_string_set_static(&config->consumerd64_bin_path, - INSTALL_BIN_PATH "/" DEFAULT_CONSUMERD_FILE); - config_string_set_static(&config->consumerd64_lib_dir, - INSTALL_LIB_PATH); -#else -#error "Unknown bitness" -#endif - ret = 0; - return ret; -error: - sessiond_config_fini(config); - return ret; -} - -void sessiond_config_fini(struct sessiond_config *config) -{ - config_string_fini(&config->tracing_group_name); - config_string_fini(&config->kmod_probes_list); - config_string_fini(&config->kmod_extra_probes_list); - config_string_fini(&config->rundir); - config_string_fini(&config->apps_unix_sock_path); - config_string_fini(&config->client_unix_sock_path); - config_string_fini(&config->wait_shm_path); - config_string_fini(&config->health_unix_sock_path); - config_string_fini(&config->lttng_ust_clock_plugin); - config_string_fini(&config->pid_file_path); - config_string_fini(&config->lock_file_path); - config_string_fini(&config->load_session_path); - config_string_fini(&config->agent_port_file_path); - config_string_fini(&config->consumerd32_path); - config_string_fini(&config->consumerd32_bin_path); - config_string_fini(&config->consumerd32_lib_dir); - config_string_fini(&config->consumerd32_err_unix_sock_path); - config_string_fini(&config->consumerd32_cmd_unix_sock_path); - config_string_fini(&config->consumerd64_path); - config_string_fini(&config->consumerd64_bin_path); - config_string_fini(&config->consumerd64_lib_dir); - config_string_fini(&config->consumerd64_err_unix_sock_path); - config_string_fini(&config->consumerd64_cmd_unix_sock_path); - config_string_fini(&config->kconsumerd_path); - config_string_fini(&config->kconsumerd_err_unix_sock_path); - config_string_fini(&config->kconsumerd_cmd_unix_sock_path); -} - -static -int resolve_path(struct config_string *path) -{ - int ret = 0; - char *absolute_path; - - if (!path->value || path->value[0] == '/') { - goto end; - } - - absolute_path = utils_expand_path(path->value); - if (!absolute_path) { - ret = -1; - goto end; - } - - config_string_set(path, absolute_path); -end: - return ret; -} - -#define RESOLVE_CHECK(path_config_str) \ - if (resolve_path(path_config_str)) \ - return -1 - -int sessiond_config_resolve_paths(struct sessiond_config *config) -{ - RESOLVE_CHECK(&config->apps_unix_sock_path); - RESOLVE_CHECK(&config->client_unix_sock_path); - RESOLVE_CHECK(&config->wait_shm_path); - RESOLVE_CHECK(&config->health_unix_sock_path); - RESOLVE_CHECK(&config->lttng_ust_clock_plugin); - RESOLVE_CHECK(&config->pid_file_path); - RESOLVE_CHECK(&config->lock_file_path); - RESOLVE_CHECK(&config->load_session_path); - RESOLVE_CHECK(&config->agent_port_file_path); - RESOLVE_CHECK(&config->consumerd32_path); - RESOLVE_CHECK(&config->consumerd32_bin_path); - RESOLVE_CHECK(&config->consumerd32_lib_dir); - RESOLVE_CHECK(&config->consumerd32_err_unix_sock_path); - RESOLVE_CHECK(&config->consumerd32_cmd_unix_sock_path); - RESOLVE_CHECK(&config->consumerd64_path); - RESOLVE_CHECK(&config->consumerd64_bin_path); - RESOLVE_CHECK(&config->consumerd64_lib_dir); - RESOLVE_CHECK(&config->consumerd64_err_unix_sock_path); - RESOLVE_CHECK(&config->consumerd64_cmd_unix_sock_path); - RESOLVE_CHECK(&config->kconsumerd_path); - RESOLVE_CHECK(&config->kconsumerd_err_unix_sock_path); - RESOLVE_CHECK(&config->kconsumerd_cmd_unix_sock_path); - return 0; -} - -void sessiond_config_log(struct sessiond_config *config) -{ - DBG_NO_LOC("[sessiond configuration]"); - DBG_NO_LOC("\tversion %s", VERSION); - if (GIT_VERSION[0] != '\0') { - DBG_NO_LOC("\tgit version %s", GIT_VERSION); - } - if (EXTRA_VERSION_NAME[0] != '\0') { - DBG_NO_LOC("\textra version name %s", EXTRA_VERSION_NAME); - } - if (EXTRA_VERSION_DESCRIPTION[0] != '\0') { - DBG_NO_LOC("\textra version description:\n\t%s", EXTRA_VERSION_DESCRIPTION); - } - if (EXTRA_VERSION_PATCHES[0] != '\0') { - DBG_NO_LOC("\textra version patches:\n\t%s", EXTRA_VERSION_PATCHES); - } - DBG_NO_LOC("\tverbose: %i", config->verbose); - DBG_NO_LOC("\tverbose consumer: %i", config->verbose_consumer); - DBG_NO_LOC("\tquiet mode: %s", config->quiet ? "True" : "False"); - if (config->agent_tcp_port.begin == config->agent_tcp_port.end) { - DBG_NO_LOC("\tagent_tcp_port: %i", config->agent_tcp_port.begin); - } else { - DBG_NO_LOC("\tagent_tcp_port: [%i, %i]", - config->agent_tcp_port.begin, - config->agent_tcp_port.end); - } - DBG_NO_LOC("\tapplication socket timeout: %i", config->app_socket_timeout); - DBG_NO_LOC("\tno-kernel: %s", config->no_kernel ? "True" : "False"); - DBG_NO_LOC("\tbackground: %s", config->background ? "True" : "False"); - DBG_NO_LOC("\tdaemonize: %s", config->daemonize ? "True" : "False"); - DBG_NO_LOC("\tsignal parent on start: %s", config->sig_parent ? "True" : "False"); - DBG_NO_LOC("\ttracing group name: %s", config->tracing_group_name.value ? : "Unknown"); - DBG_NO_LOC("\tkmod_probe_list: %s", config->kmod_probes_list.value ? : "None"); - DBG_NO_LOC("\tkmod_extra_probe_list: %s", config->kmod_extra_probes_list.value ? : "None"); - DBG_NO_LOC("\trundir: %s", config->rundir.value ? : "Unknown"); - DBG_NO_LOC("\tapplication socket path: %s", config->apps_unix_sock_path.value ? : "Unknown"); - DBG_NO_LOC("\tclient socket path: %s", config->client_unix_sock_path.value ? : "Unknown"); - DBG_NO_LOC("\twait shm path: %s", config->wait_shm_path.value ? : "Unknown"); - DBG_NO_LOC("\thealth socket path: %s", config->health_unix_sock_path.value ? : "Unknown"); - DBG_NO_LOC("\tLTTNG_UST_CLOCK_PLUGIN: %s", config->lttng_ust_clock_plugin.value ? : "None"); - DBG_NO_LOC("\tpid file path: %s", config->pid_file_path.value ? : "Unknown"); - DBG_NO_LOC("\tlock file path: %s", config->lock_file_path.value ? : "Unknown"); - DBG_NO_LOC("\tsession load path: %s", config->load_session_path.value ? : "None"); - DBG_NO_LOC("\tagent port file path: %s", config->agent_port_file_path.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd32 path: %s", config->consumerd32_path.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd32 bin path: %s", config->consumerd32_bin_path.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd32 lib dir: %s", config->consumerd32_lib_dir.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd32 err unix sock path:%s", config->consumerd32_err_unix_sock_path.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd32 cmd unix sock path:%s", config->consumerd32_cmd_unix_sock_path.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd64 path: %s", config->consumerd64_path.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd64 bin path: %s", config->consumerd64_bin_path.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd64 lib dir: %s", config->consumerd64_lib_dir.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd64 err unix sock path:%s", config->consumerd64_err_unix_sock_path.value ? : "Unknown"); - DBG_NO_LOC("\tconsumerd64 cmd unix sock path:%s", config->consumerd64_cmd_unix_sock_path.value ? : "Unknown"); - DBG_NO_LOC("\tkconsumerd path: %s", config->kconsumerd_path.value ? : "Unknown"); - DBG_NO_LOC("\tkconsumerd err unix sock path: %s", config->kconsumerd_err_unix_sock_path.value ? : "Unknown"); - DBG_NO_LOC("\tkconsumerd cmd unix sock path: %s", config->kconsumerd_cmd_unix_sock_path.value ? : "Unknown"); -} diff --git a/src/bin/lttng-sessiond/sessiond-config.cpp b/src/bin/lttng-sessiond/sessiond-config.cpp new file mode 100644 index 000000000..05b77e69c --- /dev/null +++ b/src/bin/lttng-sessiond/sessiond-config.cpp @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2017 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "version.h" +#include "sessiond-config.h" +#include "lttng-ust-ctl.h" +#include +#include +#include +#include +#include +#include +#include + +static +struct sessiond_config sessiond_config_build_defaults = { + .verbose = 0, + .verbose_consumer = 0, + .agent_tcp_port = { .begin = DEFAULT_AGENT_TCP_PORT_RANGE_BEGIN, .end = DEFAULT_AGENT_TCP_PORT_RANGE_END }, + + .event_notifier_buffer_size_kernel = DEFAULT_EVENT_NOTIFIER_ERROR_COUNT_MAP_SIZE, + .event_notifier_buffer_size_userspace = DEFAULT_EVENT_NOTIFIER_ERROR_COUNT_MAP_SIZE, + .app_socket_timeout = DEFAULT_APP_SOCKET_RW_TIMEOUT, + + .quiet = false, + + + .no_kernel = false, + .background = false, + .daemonize = false, + .sig_parent = false, + + .tracing_group_name = { (char *) DEFAULT_TRACING_GROUP }, + .kmod_probes_list = { nullptr }, + .kmod_extra_probes_list = { nullptr }, + + .rundir = { nullptr }, + + .apps_unix_sock_path = { nullptr }, + .client_unix_sock_path = { nullptr }, + .wait_shm_path = { nullptr }, + .health_unix_sock_path = { nullptr }, + .lttng_ust_clock_plugin = { nullptr }, + .pid_file_path = { nullptr }, + .lock_file_path = { nullptr }, + .load_session_path = { nullptr }, + .agent_port_file_path = { nullptr }, + + .consumerd32_path = { nullptr }, + .consumerd32_bin_path = { nullptr }, + .consumerd32_lib_dir = { nullptr }, + .consumerd32_err_unix_sock_path = { nullptr }, + .consumerd32_cmd_unix_sock_path = { nullptr }, + + .consumerd64_path = { nullptr }, + .consumerd64_bin_path = { nullptr }, + .consumerd64_lib_dir = { nullptr }, + .consumerd64_err_unix_sock_path = { nullptr }, + .consumerd64_cmd_unix_sock_path = { nullptr }, + + .kconsumerd_path = { nullptr }, + .kconsumerd_err_unix_sock_path = { nullptr }, + .kconsumerd_cmd_unix_sock_path = { nullptr }, +}; + +static +void config_string_fini(struct config_string *str) +{ + config_string_set(str, NULL); +} + +static +void config_string_set_static(struct config_string *config_str, + const char *value) +{ + config_string_set(config_str, (char *) value); + config_str->should_free = false; +} + +/* Only use for dynamically-allocated strings. */ +void config_string_set(struct config_string *config_str, char *value) +{ + LTTNG_ASSERT(config_str); + if (config_str->should_free) { + free(config_str->value); + config_str->should_free = false; + } + + config_str->should_free = !!value; + config_str->value = value; +} + +int sessiond_config_apply_env_config(struct sessiond_config *config) +{ + int ret = 0; + const char *env_value; + + env_value = getenv(DEFAULT_APP_SOCKET_TIMEOUT_ENV); + if (env_value) { + char *endptr; + long int_val; + + errno = 0; + int_val = strtoul(env_value, &endptr, 0); + if (errno != 0 || int_val > INT_MAX || + (int_val < 0 && int_val != -1)) { + ERR("Invalid value \"%s\" used for \"%s\" environment variable", + env_value, DEFAULT_APP_SOCKET_TIMEOUT_ENV); + ret = -1; + goto end; + } + + config->app_socket_timeout = int_val; + } + + env_value = lttng_secure_getenv("LTTNG_CONSUMERD32_BIN"); + if (env_value) { + config_string_set_static(&config->consumerd32_bin_path, + env_value); + } + env_value = lttng_secure_getenv("LTTNG_CONSUMERD64_BIN"); + if (env_value) { + config_string_set_static(&config->consumerd64_bin_path, + env_value); + } + + env_value = lttng_secure_getenv("LTTNG_CONSUMERD32_LIBDIR"); + if (env_value) { + config_string_set_static(&config->consumerd32_lib_dir, + env_value); + } + env_value = lttng_secure_getenv("LTTNG_CONSUMERD64_LIBDIR"); + if (env_value) { + config_string_set_static(&config->consumerd64_lib_dir, + env_value); + } + + env_value = lttng_secure_getenv("LTTNG_UST_CLOCK_PLUGIN"); + if (env_value) { + config_string_set_static(&config->lttng_ust_clock_plugin, + env_value); + } + + env_value = lttng_secure_getenv(DEFAULT_LTTNG_KMOD_PROBES); + if (env_value) { + config_string_set_static(&config->kmod_probes_list, + env_value); + } + + env_value = lttng_secure_getenv(DEFAULT_LTTNG_EXTRA_KMOD_PROBES); + if (env_value) { + config_string_set_static(&config->kmod_extra_probes_list, + env_value); + } +end: + return ret; +} + +static +int config_set_paths_root(struct sessiond_config *config) +{ + int ret = 0; + + config_string_set(&config->rundir, strdup(DEFAULT_LTTNG_RUNDIR)); + if (!config->rundir.value) { + ERR("Failed to set rundir"); + ret = -1; + goto end; + } + + config_string_set_static(&config->apps_unix_sock_path, + DEFAULT_GLOBAL_APPS_UNIX_SOCK); + config_string_set_static(&config->client_unix_sock_path, + DEFAULT_GLOBAL_CLIENT_UNIX_SOCK); + config_string_set_static(&config->wait_shm_path, + DEFAULT_GLOBAL_APPS_WAIT_SHM_PATH); + config_string_set_static(&config->health_unix_sock_path, + DEFAULT_GLOBAL_HEALTH_UNIX_SOCK); +end: + return ret; +} + +static +int config_set_paths_non_root(struct sessiond_config *config) +{ + int ret = 0; + const char *home_path = utils_get_home_dir(); + char *str; + + if (home_path == NULL) { + ERR("Can't get HOME directory for sockets creation."); + ret = -1; + goto end; + } + + /* + * Create rundir from home path. This will create something like + * $HOME/.lttng + */ + ret = asprintf(&str, DEFAULT_LTTNG_HOME_RUNDIR, home_path); + if (ret < 0) { + ERR("Failed to set rundir"); + goto end; + } + config_string_set(&config->rundir, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_HOME_APPS_UNIX_SOCK, home_path); + if (ret < 0) { + ERR("Failed to set default home apps unix socket path"); + goto end; + } + config_string_set(&config->apps_unix_sock_path, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_HOME_CLIENT_UNIX_SOCK, home_path); + if (ret < 0) { + ERR("Failed to set default home client unix socket path"); + goto end; + } + config_string_set(&config->client_unix_sock_path, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_HOME_APPS_WAIT_SHM_PATH, getuid()); + if (ret < 0) { + ERR("Failed to set default home apps wait shm path"); + goto end; + } + config_string_set(&config->wait_shm_path, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_HOME_HEALTH_UNIX_SOCK, home_path); + if (ret < 0) { + ERR("Failed to set default home health UNIX socket path"); + goto end; + } + config_string_set(&config->health_unix_sock_path, str); + str = NULL; + + ret = 0; +end: + return ret; +} + +int sessiond_config_init(struct sessiond_config *config) +{ + int ret; + bool is_root = (getuid() == 0); + char *str; + + LTTNG_ASSERT(config); + memcpy(config, &sessiond_config_build_defaults, sizeof(*config)); + + if (is_root) { + ret = config_set_paths_root(config); + } else { + ret = config_set_paths_non_root(config); + } + if (ret < 0) { + goto error; + } + + /* 32 bits consumerd path setup */ + ret = asprintf(&str, DEFAULT_USTCONSUMERD32_PATH, + config->rundir.value); + if (ret < 0) { + ERR("Failed to set 32-bit consumer path"); + goto error; + } + config_string_set(&config->consumerd32_path, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_USTCONSUMERD32_ERR_SOCK_PATH, + config->rundir.value); + if (ret < 0) { + ERR("Failed to set 32-bit consumer error socket path"); + goto error; + } + config_string_set(&config->consumerd32_err_unix_sock_path, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_USTCONSUMERD32_CMD_SOCK_PATH, + config->rundir.value); + if (ret < 0) { + ERR("Failed to set 32-bit consumer command socket path"); + goto error; + } + config_string_set(&config->consumerd32_cmd_unix_sock_path, str); + str = NULL; + + /* 64 bits consumerd path setup */ + ret = asprintf(&str, DEFAULT_USTCONSUMERD64_PATH, + config->rundir.value); + if (ret < 0) { + ERR("Failed to set 64-bit consumer path"); + goto error; + } + config_string_set(&config->consumerd64_path, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_USTCONSUMERD64_ERR_SOCK_PATH, + config->rundir.value); + if (ret < 0) { + ERR("Failed to set 64-bit consumer error socket path"); + goto error; + } + config_string_set(&config->consumerd64_err_unix_sock_path, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_USTCONSUMERD64_CMD_SOCK_PATH, + config->rundir.value); + if (ret < 0) { + ERR("Failed to set 64-bit consumer command socket path"); + goto error; + } + config_string_set(&config->consumerd64_cmd_unix_sock_path, str); + str = NULL; + + /* kconsumerd consumerd path setup */ + ret = asprintf(&str, DEFAULT_KCONSUMERD_PATH, + config->rundir.value); + if (ret < 0) { + ERR("Failed to set kernel consumer path"); + goto error; + } + config_string_set(&config->kconsumerd_path, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_KCONSUMERD_ERR_SOCK_PATH, + config->rundir.value); + if (ret < 0) { + ERR("Failed to set kernel consumer error socket path"); + goto error; + } + config_string_set(&config->kconsumerd_err_unix_sock_path, str); + str = NULL; + + ret = asprintf(&str, DEFAULT_KCONSUMERD_CMD_SOCK_PATH, + config->rundir.value); + if (ret < 0) { + ERR("Failed to set kernel consumer command socket path"); + goto error; + } + config_string_set(&config->kconsumerd_cmd_unix_sock_path, str); + str = NULL; + + ret = asprintf(&str, "%s/%s", config->rundir.value, + DEFAULT_LTTNG_SESSIOND_PIDFILE); + if (ret < 0) { + ERR("Failed to set PID file path"); + goto error; + } + config_string_set(&config->pid_file_path, str); + str = NULL; + + ret = asprintf(&str, "%s/%s", config->rundir.value, + DEFAULT_LTTNG_SESSIOND_LOCKFILE); + if (ret < 0) { + ERR("Failed to set lock file path"); + goto error; + } + config_string_set(&config->lock_file_path, str); + str = NULL; + + ret = asprintf(&str, "%s/%s", config->rundir.value, + DEFAULT_LTTNG_SESSIOND_AGENTPORT_FILE); + if (ret < 0) { + ERR("Failed to set agent port file path"); + goto error; + } + config_string_set(&config->agent_port_file_path, str); + str = NULL; + + /* + * Allow INSTALL_BIN_PATH to be used as a target path for the + * native architecture size consumer if CONFIG_CONSUMER*_PATH + * has not been defined. + */ +#if (CAA_BITS_PER_LONG == 32) + config_string_set_static(&config->consumerd32_bin_path, + INSTALL_BIN_PATH "/" DEFAULT_CONSUMERD_FILE); + config_string_set_static(&config->consumerd32_lib_dir, + INSTALL_LIB_PATH); +#elif (CAA_BITS_PER_LONG == 64) + config_string_set_static(&config->consumerd64_bin_path, + INSTALL_BIN_PATH "/" DEFAULT_CONSUMERD_FILE); + config_string_set_static(&config->consumerd64_lib_dir, + INSTALL_LIB_PATH); +#else +#error "Unknown bitness" +#endif + ret = 0; + return ret; +error: + sessiond_config_fini(config); + return ret; +} + +void sessiond_config_fini(struct sessiond_config *config) +{ + config_string_fini(&config->tracing_group_name); + config_string_fini(&config->kmod_probes_list); + config_string_fini(&config->kmod_extra_probes_list); + config_string_fini(&config->rundir); + config_string_fini(&config->apps_unix_sock_path); + config_string_fini(&config->client_unix_sock_path); + config_string_fini(&config->wait_shm_path); + config_string_fini(&config->health_unix_sock_path); + config_string_fini(&config->lttng_ust_clock_plugin); + config_string_fini(&config->pid_file_path); + config_string_fini(&config->lock_file_path); + config_string_fini(&config->load_session_path); + config_string_fini(&config->agent_port_file_path); + config_string_fini(&config->consumerd32_path); + config_string_fini(&config->consumerd32_bin_path); + config_string_fini(&config->consumerd32_lib_dir); + config_string_fini(&config->consumerd32_err_unix_sock_path); + config_string_fini(&config->consumerd32_cmd_unix_sock_path); + config_string_fini(&config->consumerd64_path); + config_string_fini(&config->consumerd64_bin_path); + config_string_fini(&config->consumerd64_lib_dir); + config_string_fini(&config->consumerd64_err_unix_sock_path); + config_string_fini(&config->consumerd64_cmd_unix_sock_path); + config_string_fini(&config->kconsumerd_path); + config_string_fini(&config->kconsumerd_err_unix_sock_path); + config_string_fini(&config->kconsumerd_cmd_unix_sock_path); +} + +static +int resolve_path(struct config_string *path) +{ + int ret = 0; + char *absolute_path; + + if (!path->value || path->value[0] == '/') { + goto end; + } + + absolute_path = utils_expand_path(path->value); + if (!absolute_path) { + ret = -1; + goto end; + } + + config_string_set(path, absolute_path); +end: + return ret; +} + +#define RESOLVE_CHECK(path_config_str) \ + if (resolve_path(path_config_str)) \ + return -1 + +int sessiond_config_resolve_paths(struct sessiond_config *config) +{ + RESOLVE_CHECK(&config->apps_unix_sock_path); + RESOLVE_CHECK(&config->client_unix_sock_path); + RESOLVE_CHECK(&config->wait_shm_path); + RESOLVE_CHECK(&config->health_unix_sock_path); + RESOLVE_CHECK(&config->lttng_ust_clock_plugin); + RESOLVE_CHECK(&config->pid_file_path); + RESOLVE_CHECK(&config->lock_file_path); + RESOLVE_CHECK(&config->load_session_path); + RESOLVE_CHECK(&config->agent_port_file_path); + RESOLVE_CHECK(&config->consumerd32_path); + RESOLVE_CHECK(&config->consumerd32_bin_path); + RESOLVE_CHECK(&config->consumerd32_lib_dir); + RESOLVE_CHECK(&config->consumerd32_err_unix_sock_path); + RESOLVE_CHECK(&config->consumerd32_cmd_unix_sock_path); + RESOLVE_CHECK(&config->consumerd64_path); + RESOLVE_CHECK(&config->consumerd64_bin_path); + RESOLVE_CHECK(&config->consumerd64_lib_dir); + RESOLVE_CHECK(&config->consumerd64_err_unix_sock_path); + RESOLVE_CHECK(&config->consumerd64_cmd_unix_sock_path); + RESOLVE_CHECK(&config->kconsumerd_path); + RESOLVE_CHECK(&config->kconsumerd_err_unix_sock_path); + RESOLVE_CHECK(&config->kconsumerd_cmd_unix_sock_path); + return 0; +} + +void sessiond_config_log(struct sessiond_config *config) +{ + DBG_NO_LOC("[sessiond configuration]"); + DBG_NO_LOC("\tversion %s", VERSION); + if (GIT_VERSION[0] != '\0') { + DBG_NO_LOC("\tgit version %s", GIT_VERSION); + } + if (EXTRA_VERSION_NAME[0] != '\0') { + DBG_NO_LOC("\textra version name %s", EXTRA_VERSION_NAME); + } + if (EXTRA_VERSION_DESCRIPTION[0] != '\0') { + DBG_NO_LOC("\textra version description:\n\t%s", EXTRA_VERSION_DESCRIPTION); + } + if (EXTRA_VERSION_PATCHES[0] != '\0') { + DBG_NO_LOC("\textra version patches:\n\t%s", EXTRA_VERSION_PATCHES); + } + DBG_NO_LOC("\tverbose: %i", config->verbose); + DBG_NO_LOC("\tverbose consumer: %i", config->verbose_consumer); + DBG_NO_LOC("\tquiet mode: %s", config->quiet ? "True" : "False"); + if (config->agent_tcp_port.begin == config->agent_tcp_port.end) { + DBG_NO_LOC("\tagent_tcp_port: %i", config->agent_tcp_port.begin); + } else { + DBG_NO_LOC("\tagent_tcp_port: [%i, %i]", + config->agent_tcp_port.begin, + config->agent_tcp_port.end); + } + DBG_NO_LOC("\tapplication socket timeout: %i", config->app_socket_timeout); + DBG_NO_LOC("\tno-kernel: %s", config->no_kernel ? "True" : "False"); + DBG_NO_LOC("\tbackground: %s", config->background ? "True" : "False"); + DBG_NO_LOC("\tdaemonize: %s", config->daemonize ? "True" : "False"); + DBG_NO_LOC("\tsignal parent on start: %s", config->sig_parent ? "True" : "False"); + DBG_NO_LOC("\ttracing group name: %s", config->tracing_group_name.value ? : "Unknown"); + DBG_NO_LOC("\tkmod_probe_list: %s", config->kmod_probes_list.value ? : "None"); + DBG_NO_LOC("\tkmod_extra_probe_list: %s", config->kmod_extra_probes_list.value ? : "None"); + DBG_NO_LOC("\trundir: %s", config->rundir.value ? : "Unknown"); + DBG_NO_LOC("\tapplication socket path: %s", config->apps_unix_sock_path.value ? : "Unknown"); + DBG_NO_LOC("\tclient socket path: %s", config->client_unix_sock_path.value ? : "Unknown"); + DBG_NO_LOC("\twait shm path: %s", config->wait_shm_path.value ? : "Unknown"); + DBG_NO_LOC("\thealth socket path: %s", config->health_unix_sock_path.value ? : "Unknown"); + DBG_NO_LOC("\tLTTNG_UST_CLOCK_PLUGIN: %s", config->lttng_ust_clock_plugin.value ? : "None"); + DBG_NO_LOC("\tpid file path: %s", config->pid_file_path.value ? : "Unknown"); + DBG_NO_LOC("\tlock file path: %s", config->lock_file_path.value ? : "Unknown"); + DBG_NO_LOC("\tsession load path: %s", config->load_session_path.value ? : "None"); + DBG_NO_LOC("\tagent port file path: %s", config->agent_port_file_path.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd32 path: %s", config->consumerd32_path.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd32 bin path: %s", config->consumerd32_bin_path.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd32 lib dir: %s", config->consumerd32_lib_dir.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd32 err unix sock path:%s", config->consumerd32_err_unix_sock_path.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd32 cmd unix sock path:%s", config->consumerd32_cmd_unix_sock_path.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd64 path: %s", config->consumerd64_path.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd64 bin path: %s", config->consumerd64_bin_path.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd64 lib dir: %s", config->consumerd64_lib_dir.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd64 err unix sock path:%s", config->consumerd64_err_unix_sock_path.value ? : "Unknown"); + DBG_NO_LOC("\tconsumerd64 cmd unix sock path:%s", config->consumerd64_cmd_unix_sock_path.value ? : "Unknown"); + DBG_NO_LOC("\tkconsumerd path: %s", config->kconsumerd_path.value ? : "Unknown"); + DBG_NO_LOC("\tkconsumerd err unix sock path: %s", config->kconsumerd_err_unix_sock_path.value ? : "Unknown"); + DBG_NO_LOC("\tkconsumerd cmd unix sock path: %s", config->kconsumerd_cmd_unix_sock_path.value ? : "Unknown"); +} diff --git a/src/bin/lttng-sessiond/snapshot.c b/src/bin/lttng-sessiond/snapshot.c deleted file mode 100644 index 6e4a0c3f1..000000000 --- a/src/bin/lttng-sessiond/snapshot.c +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (C) 2013 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include - -#include - -#include "snapshot.h" -#include "utils.h" - -/* - * Return the atomically incremented value of next_output_id. - */ -static inline unsigned long get_next_output_id(struct snapshot *snapshot) -{ - return uatomic_add_return(&snapshot->next_output_id, 1); -} - -/* - * Initialized snapshot output with the given values. - * - * Return 0 on success or else a negative value. - */ -static int output_init(const struct ltt_session *session, - uint64_t max_size, const char *name, - struct lttng_uri *uris, size_t nb_uri, - struct consumer_output *consumer, struct snapshot_output *output, - struct snapshot *snapshot) -{ - int ret = 0, i; - - memset(output, 0, sizeof(struct snapshot_output)); - - /* - * max_size of -1ULL means unset. Set to default (unlimited). - */ - if (max_size == (uint64_t) -1ULL) { - max_size = 0; - } - output->max_size = max_size; - - if (snapshot) { - output->id = get_next_output_id(snapshot); - } - lttng_ht_node_init_ulong(&output->node, (unsigned long) output->id); - - if (name && name[0] != '\0') { - if (lttng_strncpy(output->name, name, sizeof(output->name))) { - ret = -LTTNG_ERR_INVALID; - goto error; - } - } else { - /* Set default name. */ - ret = snprintf(output->name, sizeof(output->name), "%s-%" PRIu32, - DEFAULT_SNAPSHOT_NAME, output->id); - if (ret < 0) { - ret = -ENOMEM; - goto error; - } - } - - if (!consumer) { - goto end; - } - - output->consumer = consumer_copy_output(consumer); - if (!output->consumer) { - ret = -ENOMEM; - goto error; - } - output->consumer->snapshot = 1; - - /* No URL given. */ - if (nb_uri == 0) { - ret = 0; - goto end; - } - - if (uris[0].dtype == LTTNG_DST_PATH) { - memset(output->consumer->dst.session_root_path, 0, - sizeof(output->consumer->dst.session_root_path)); - if (lttng_strncpy(output->consumer->dst.session_root_path, - uris[0].dst.path, - sizeof(output->consumer->dst.session_root_path))) { - ret = -LTTNG_ERR_INVALID; - goto error; - } - output->consumer->type = CONSUMER_DST_LOCAL; - ret = 0; - goto end; - } - - if (nb_uri != 2) { - /* Absolutely needs two URIs for network. */ - ret = -LTTNG_ERR_INVALID; - goto error; - } - - for (i = 0; i < nb_uri; i ++) { - /* Network URIs */ - ret = consumer_set_network_uri(session, output->consumer, - &uris[i]); - if (ret < 0) { - goto error; - } - } - -error: -end: - return ret; -} - -/* - * Initialize a snapshot output object using the given parameters and URI(s). - * The name value and uris can be NULL. - * - * Return 0 on success or else a negative value. - */ -int snapshot_output_init_with_uri(const struct ltt_session *session, - uint64_t max_size, const char *name, - struct lttng_uri *uris, size_t nb_uri, - struct consumer_output *consumer, struct snapshot_output *output, - struct snapshot *snapshot) -{ - return output_init(session, max_size, name, uris, nb_uri, consumer, - output, snapshot); -} - -/* - * Initialize a snapshot output object using the given parameters. The name - * value and url can be NULL. - * - * Return 0 on success or else a negative value. - */ -int snapshot_output_init(const struct ltt_session *session, - uint64_t max_size, const char *name, - const char *ctrl_url, const char *data_url, - struct consumer_output *consumer, struct snapshot_output *output, - struct snapshot *snapshot) -{ - int ret = 0, nb_uri; - struct lttng_uri *uris = NULL; - - /* Create an array of URIs from URLs. */ - nb_uri = uri_parse_str_urls(ctrl_url, data_url, &uris); - if (nb_uri < 0) { - ret = nb_uri; - goto error; - } - - ret = output_init(session, max_size, name, uris, nb_uri, consumer, - output, snapshot); - -error: - free(uris); - return ret; -} - -struct snapshot_output *snapshot_output_alloc(void) -{ - return zmalloc(sizeof(struct snapshot_output)); -} - -/* - * Delete output from the snapshot object. - */ -void snapshot_delete_output(struct snapshot *snapshot, - struct snapshot_output *output) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(snapshot); - LTTNG_ASSERT(snapshot->output_ht); - LTTNG_ASSERT(output); - - iter.iter.node = &output->node.node; - rcu_read_lock(); - ret = lttng_ht_del(snapshot->output_ht, &iter); - rcu_read_unlock(); - LTTNG_ASSERT(!ret); - /* - * This is safe because the ownership of a snapshot object is in a session - * for which the session lock need to be acquired to read and modify it. - */ - snapshot->nb_output--; -} - -/* - * Add output object to the snapshot. - */ -void snapshot_add_output(struct snapshot *snapshot, - struct snapshot_output *output) -{ - LTTNG_ASSERT(snapshot); - LTTNG_ASSERT(snapshot->output_ht); - LTTNG_ASSERT(output); - - rcu_read_lock(); - lttng_ht_add_unique_ulong(snapshot->output_ht, &output->node); - rcu_read_unlock(); - /* - * This is safe because the ownership of a snapshot object is in a session - * for which the session lock need to be acquired to read and modify it. - */ - snapshot->nb_output++; -} - -/* - * Destroy and free a snapshot output object. - */ -void snapshot_output_destroy(struct snapshot_output *obj) -{ - LTTNG_ASSERT(obj); - - if (obj->consumer) { - consumer_output_send_destroy_relayd(obj->consumer); - consumer_output_put(obj->consumer); - } - free(obj); -} - -/* - * RCU read side lock MUST be acquired before calling this since the returned - * pointer is in a RCU hash table. - * - * Return the reference on success or else NULL. - */ -struct snapshot_output *snapshot_find_output_by_name(const char *name, - struct snapshot *snapshot) -{ - struct lttng_ht_iter iter; - struct snapshot_output *output = NULL; - - LTTNG_ASSERT(snapshot); - LTTNG_ASSERT(name); - - cds_lfht_for_each_entry(snapshot->output_ht->ht, &iter.iter, output, - node.node) { - if (!strncmp(output->name, name, strlen(name))) { - return output; - } - } - - /* Not found */ - return NULL; -} - -/* - * RCU read side lock MUST be acquired before calling this since the returned - * pointer is in a RCU hash table. - * - * Return the reference on success or else NULL. - */ -struct snapshot_output *snapshot_find_output_by_id(uint32_t id, - struct snapshot *snapshot) -{ - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter iter; - struct snapshot_output *output = NULL; - - LTTNG_ASSERT(snapshot); - - lttng_ht_lookup(snapshot->output_ht, (void *)((unsigned long) id), &iter); - node = lttng_ht_iter_get_node_ulong(&iter); - if (!node) { - DBG3("Snapshot output not found with id %" PRId32, id); - goto error; - } - output = caa_container_of(node, struct snapshot_output, node); - -error: - return output; -} - -/* - * Initialized a snapshot object that was already allocated. - * - * Return 0 on success or else a negative errno value. - */ -int snapshot_init(struct snapshot *obj) -{ - int ret; - - LTTNG_ASSERT(obj); - - memset(obj, 0, sizeof(struct snapshot)); - - obj->output_ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); - if (!obj->output_ht) { - ret = -ENOMEM; - goto error; - } - - ret = 0; - -error: - return ret; -} - -/* - * Destroy snapshot object but the pointer is not freed so it's safe to pass a - * static reference. - */ -void snapshot_destroy(struct snapshot *obj) -{ - struct lttng_ht_iter iter; - struct snapshot_output *output; - - if (!obj->output_ht) { - return; - } - - rcu_read_lock(); - cds_lfht_for_each_entry(obj->output_ht->ht, &iter.iter, output, - node.node) { - snapshot_delete_output(obj, output); - snapshot_output_destroy(output); - } - rcu_read_unlock(); - ht_cleanup_push(obj->output_ht); -} diff --git a/src/bin/lttng-sessiond/snapshot.cpp b/src/bin/lttng-sessiond/snapshot.cpp new file mode 100644 index 000000000..03472201a --- /dev/null +++ b/src/bin/lttng-sessiond/snapshot.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2013 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include + +#include + +#include "snapshot.h" +#include "utils.h" + +/* + * Return the atomically incremented value of next_output_id. + */ +static inline unsigned long get_next_output_id(struct snapshot *snapshot) +{ + return uatomic_add_return(&snapshot->next_output_id, 1); +} + +/* + * Initialized snapshot output with the given values. + * + * Return 0 on success or else a negative value. + */ +static int output_init(const struct ltt_session *session, + uint64_t max_size, const char *name, + struct lttng_uri *uris, size_t nb_uri, + struct consumer_output *consumer, struct snapshot_output *output, + struct snapshot *snapshot) +{ + int ret = 0, i; + + memset(output, 0, sizeof(struct snapshot_output)); + + /* + * max_size of -1ULL means unset. Set to default (unlimited). + */ + if (max_size == (uint64_t) -1ULL) { + max_size = 0; + } + output->max_size = max_size; + + if (snapshot) { + output->id = get_next_output_id(snapshot); + } + lttng_ht_node_init_ulong(&output->node, (unsigned long) output->id); + + if (name && name[0] != '\0') { + if (lttng_strncpy(output->name, name, sizeof(output->name))) { + ret = -LTTNG_ERR_INVALID; + goto error; + } + } else { + /* Set default name. */ + ret = snprintf(output->name, sizeof(output->name), "%s-%" PRIu32, + DEFAULT_SNAPSHOT_NAME, output->id); + if (ret < 0) { + ret = -ENOMEM; + goto error; + } + } + + if (!consumer) { + goto end; + } + + output->consumer = consumer_copy_output(consumer); + if (!output->consumer) { + ret = -ENOMEM; + goto error; + } + output->consumer->snapshot = 1; + + /* No URL given. */ + if (nb_uri == 0) { + ret = 0; + goto end; + } + + if (uris[0].dtype == LTTNG_DST_PATH) { + memset(output->consumer->dst.session_root_path, 0, + sizeof(output->consumer->dst.session_root_path)); + if (lttng_strncpy(output->consumer->dst.session_root_path, + uris[0].dst.path, + sizeof(output->consumer->dst.session_root_path))) { + ret = -LTTNG_ERR_INVALID; + goto error; + } + output->consumer->type = CONSUMER_DST_LOCAL; + ret = 0; + goto end; + } + + if (nb_uri != 2) { + /* Absolutely needs two URIs for network. */ + ret = -LTTNG_ERR_INVALID; + goto error; + } + + for (i = 0; i < nb_uri; i ++) { + /* Network URIs */ + ret = consumer_set_network_uri(session, output->consumer, + &uris[i]); + if (ret < 0) { + goto error; + } + } + +error: +end: + return ret; +} + +/* + * Initialize a snapshot output object using the given parameters and URI(s). + * The name value and uris can be NULL. + * + * Return 0 on success or else a negative value. + */ +int snapshot_output_init_with_uri(const struct ltt_session *session, + uint64_t max_size, const char *name, + struct lttng_uri *uris, size_t nb_uri, + struct consumer_output *consumer, struct snapshot_output *output, + struct snapshot *snapshot) +{ + return output_init(session, max_size, name, uris, nb_uri, consumer, + output, snapshot); +} + +/* + * Initialize a snapshot output object using the given parameters. The name + * value and url can be NULL. + * + * Return 0 on success or else a negative value. + */ +int snapshot_output_init(const struct ltt_session *session, + uint64_t max_size, const char *name, + const char *ctrl_url, const char *data_url, + struct consumer_output *consumer, struct snapshot_output *output, + struct snapshot *snapshot) +{ + int ret = 0, nb_uri; + struct lttng_uri *uris = NULL; + + /* Create an array of URIs from URLs. */ + nb_uri = uri_parse_str_urls(ctrl_url, data_url, &uris); + if (nb_uri < 0) { + ret = nb_uri; + goto error; + } + + ret = output_init(session, max_size, name, uris, nb_uri, consumer, + output, snapshot); + +error: + free(uris); + return ret; +} + +struct snapshot_output *snapshot_output_alloc(void) +{ + return (snapshot_output *) zmalloc(sizeof(struct snapshot_output)); +} + +/* + * Delete output from the snapshot object. + */ +void snapshot_delete_output(struct snapshot *snapshot, + struct snapshot_output *output) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(snapshot); + LTTNG_ASSERT(snapshot->output_ht); + LTTNG_ASSERT(output); + + iter.iter.node = &output->node.node; + rcu_read_lock(); + ret = lttng_ht_del(snapshot->output_ht, &iter); + rcu_read_unlock(); + LTTNG_ASSERT(!ret); + /* + * This is safe because the ownership of a snapshot object is in a session + * for which the session lock need to be acquired to read and modify it. + */ + snapshot->nb_output--; +} + +/* + * Add output object to the snapshot. + */ +void snapshot_add_output(struct snapshot *snapshot, + struct snapshot_output *output) +{ + LTTNG_ASSERT(snapshot); + LTTNG_ASSERT(snapshot->output_ht); + LTTNG_ASSERT(output); + + rcu_read_lock(); + lttng_ht_add_unique_ulong(snapshot->output_ht, &output->node); + rcu_read_unlock(); + /* + * This is safe because the ownership of a snapshot object is in a session + * for which the session lock need to be acquired to read and modify it. + */ + snapshot->nb_output++; +} + +/* + * Destroy and free a snapshot output object. + */ +void snapshot_output_destroy(struct snapshot_output *obj) +{ + LTTNG_ASSERT(obj); + + if (obj->consumer) { + consumer_output_send_destroy_relayd(obj->consumer); + consumer_output_put(obj->consumer); + } + free(obj); +} + +/* + * RCU read side lock MUST be acquired before calling this since the returned + * pointer is in a RCU hash table. + * + * Return the reference on success or else NULL. + */ +struct snapshot_output *snapshot_find_output_by_name(const char *name, + struct snapshot *snapshot) +{ + struct lttng_ht_iter iter; + struct snapshot_output *output = NULL; + + LTTNG_ASSERT(snapshot); + LTTNG_ASSERT(name); + + cds_lfht_for_each_entry(snapshot->output_ht->ht, &iter.iter, output, + node.node) { + if (!strncmp(output->name, name, strlen(name))) { + return output; + } + } + + /* Not found */ + return NULL; +} + +/* + * RCU read side lock MUST be acquired before calling this since the returned + * pointer is in a RCU hash table. + * + * Return the reference on success or else NULL. + */ +struct snapshot_output *snapshot_find_output_by_id(uint32_t id, + struct snapshot *snapshot) +{ + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter iter; + struct snapshot_output *output = NULL; + + LTTNG_ASSERT(snapshot); + + lttng_ht_lookup(snapshot->output_ht, (void *)((unsigned long) id), &iter); + node = lttng_ht_iter_get_node_ulong(&iter); + if (!node) { + DBG3("Snapshot output not found with id %" PRId32, id); + goto error; + } + output = caa_container_of(node, struct snapshot_output, node); + +error: + return output; +} + +/* + * Initialized a snapshot object that was already allocated. + * + * Return 0 on success or else a negative errno value. + */ +int snapshot_init(struct snapshot *obj) +{ + int ret; + + LTTNG_ASSERT(obj); + + memset(obj, 0, sizeof(struct snapshot)); + + obj->output_ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); + if (!obj->output_ht) { + ret = -ENOMEM; + goto error; + } + + ret = 0; + +error: + return ret; +} + +/* + * Destroy snapshot object but the pointer is not freed so it's safe to pass a + * static reference. + */ +void snapshot_destroy(struct snapshot *obj) +{ + struct lttng_ht_iter iter; + struct snapshot_output *output; + + if (!obj->output_ht) { + return; + } + + rcu_read_lock(); + cds_lfht_for_each_entry(obj->output_ht->ht, &iter.iter, output, + node.node) { + snapshot_delete_output(obj, output); + snapshot_output_destroy(output); + } + rcu_read_unlock(); + ht_cleanup_push(obj->output_ht); +} diff --git a/src/bin/lttng-sessiond/thread-utils.c b/src/bin/lttng-sessiond/thread-utils.c deleted file mode 100644 index 1eb25eabe..000000000 --- a/src/bin/lttng-sessiond/thread-utils.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * Copyright (C) 2013 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "lttng-sessiond.h" -#include "utils.h" -#include -#include - -/* - * Quit pipe for all threads. This permits a single cancellation point - * for all threads when receiving an event on the pipe. - */ -static int thread_quit_pipe[2] = { -1, -1 }; - -/* - * Init thread quit pipe. - * - * Return -1 on error or 0 if all pipes are created. - */ -static int __init_thread_quit_pipe(int *a_pipe) -{ - int ret, i; - - ret = pipe(a_pipe); - if (ret < 0) { - PERROR("thread quit pipe"); - goto error; - } - - for (i = 0; i < 2; i++) { - ret = fcntl(a_pipe[i], F_SETFD, FD_CLOEXEC); - if (ret < 0) { - PERROR("fcntl"); - goto error; - } - } - -error: - return ret; -} - -int sessiond_init_thread_quit_pipe(void) -{ - return __init_thread_quit_pipe(thread_quit_pipe); -} - -int sessiond_check_thread_quit_pipe(int fd, uint32_t events) -{ - return (fd == thread_quit_pipe[0] && (events & LPOLLIN)); -} - -/* - * Wait for a notification on the quit pipe (with a timeout). - * - * A timeout value of -1U means no timeout. - * - * Returns 1 if the caller should quit, 0 if the timeout was reached, and - * -1 if an error was encountered. - */ -int sessiond_wait_for_quit_pipe(int timeout_ms) -{ - int ret; - struct lttng_poll_event events; - - ret = lttng_poll_create(&events, 1, LTTNG_CLOEXEC); - if (ret < 0) { - PERROR("Failed to initialize poll/epoll set"); - ret = -1; - goto end; - } - ret = lttng_poll_add(&events, thread_quit_pipe[0], LPOLLIN | LPOLLERR); - if (ret < 0) { - PERROR("Failed to add file descriptor to poll/epoll set"); - ret = -1; - goto end_clean_poll; - } - ret = lttng_poll_wait(&events, timeout_ms); - if (ret > 0) { - /* Should quit. */ - ret = 1; - } else if (ret < 0 && errno != EINTR) { - /* Unknown error. */ - PERROR("Failed to epoll()/poll() thread quit pipe"); - ret = -1; - } else { - /* Timeout reached. */ - ret = 0; - } -end_clean_poll: - lttng_poll_clean(&events); -end: - return ret; -} - -int sessiond_notify_quit_pipe(void) -{ - return notify_thread_pipe(thread_quit_pipe[1]); -} - -void sessiond_close_quit_pipe(void) -{ - utils_close_pipe(thread_quit_pipe); -} - -static -int __sessiond_set_thread_pollset(struct lttng_poll_event *events, size_t size, - int *a_pipe) -{ - int ret; - - LTTNG_ASSERT(events); - - ret = lttng_poll_create(events, size, LTTNG_CLOEXEC); - if (ret < 0) { - goto error; - } - - /* Add quit pipe */ - ret = lttng_poll_add(events, a_pipe[0], LPOLLIN | LPOLLERR); - if (ret < 0) { - goto error; - } - - return 0; - -error: - return ret; -} - -/* - * Create a poll set with O_CLOEXEC and add the thread quit pipe to the set. - */ -int sessiond_set_thread_pollset(struct lttng_poll_event *events, size_t size) -{ - return __sessiond_set_thread_pollset(events, size, thread_quit_pipe); -} diff --git a/src/bin/lttng-sessiond/thread-utils.cpp b/src/bin/lttng-sessiond/thread-utils.cpp new file mode 100644 index 000000000..1eb25eabe --- /dev/null +++ b/src/bin/lttng-sessiond/thread-utils.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * Copyright (C) 2013 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "lttng-sessiond.h" +#include "utils.h" +#include +#include + +/* + * Quit pipe for all threads. This permits a single cancellation point + * for all threads when receiving an event on the pipe. + */ +static int thread_quit_pipe[2] = { -1, -1 }; + +/* + * Init thread quit pipe. + * + * Return -1 on error or 0 if all pipes are created. + */ +static int __init_thread_quit_pipe(int *a_pipe) +{ + int ret, i; + + ret = pipe(a_pipe); + if (ret < 0) { + PERROR("thread quit pipe"); + goto error; + } + + for (i = 0; i < 2; i++) { + ret = fcntl(a_pipe[i], F_SETFD, FD_CLOEXEC); + if (ret < 0) { + PERROR("fcntl"); + goto error; + } + } + +error: + return ret; +} + +int sessiond_init_thread_quit_pipe(void) +{ + return __init_thread_quit_pipe(thread_quit_pipe); +} + +int sessiond_check_thread_quit_pipe(int fd, uint32_t events) +{ + return (fd == thread_quit_pipe[0] && (events & LPOLLIN)); +} + +/* + * Wait for a notification on the quit pipe (with a timeout). + * + * A timeout value of -1U means no timeout. + * + * Returns 1 if the caller should quit, 0 if the timeout was reached, and + * -1 if an error was encountered. + */ +int sessiond_wait_for_quit_pipe(int timeout_ms) +{ + int ret; + struct lttng_poll_event events; + + ret = lttng_poll_create(&events, 1, LTTNG_CLOEXEC); + if (ret < 0) { + PERROR("Failed to initialize poll/epoll set"); + ret = -1; + goto end; + } + ret = lttng_poll_add(&events, thread_quit_pipe[0], LPOLLIN | LPOLLERR); + if (ret < 0) { + PERROR("Failed to add file descriptor to poll/epoll set"); + ret = -1; + goto end_clean_poll; + } + ret = lttng_poll_wait(&events, timeout_ms); + if (ret > 0) { + /* Should quit. */ + ret = 1; + } else if (ret < 0 && errno != EINTR) { + /* Unknown error. */ + PERROR("Failed to epoll()/poll() thread quit pipe"); + ret = -1; + } else { + /* Timeout reached. */ + ret = 0; + } +end_clean_poll: + lttng_poll_clean(&events); +end: + return ret; +} + +int sessiond_notify_quit_pipe(void) +{ + return notify_thread_pipe(thread_quit_pipe[1]); +} + +void sessiond_close_quit_pipe(void) +{ + utils_close_pipe(thread_quit_pipe); +} + +static +int __sessiond_set_thread_pollset(struct lttng_poll_event *events, size_t size, + int *a_pipe) +{ + int ret; + + LTTNG_ASSERT(events); + + ret = lttng_poll_create(events, size, LTTNG_CLOEXEC); + if (ret < 0) { + goto error; + } + + /* Add quit pipe */ + ret = lttng_poll_add(events, a_pipe[0], LPOLLIN | LPOLLERR); + if (ret < 0) { + goto error; + } + + return 0; + +error: + return ret; +} + +/* + * Create a poll set with O_CLOEXEC and add the thread quit pipe to the set. + */ +int sessiond_set_thread_pollset(struct lttng_poll_event *events, size_t size) +{ + return __sessiond_set_thread_pollset(events, size, thread_quit_pipe); +} diff --git a/src/bin/lttng-sessiond/thread.c b/src/bin/lttng-sessiond/thread.c deleted file mode 100644 index a4e3cb86e..000000000 --- a/src/bin/lttng-sessiond/thread.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2018 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "thread.h" -#include -#include -#include -#include -#include -#include - -static struct thread_list { - struct cds_list_head head; - pthread_mutex_t lock; -} thread_list = { - .head = CDS_LIST_HEAD_INIT(thread_list.head), - .lock = PTHREAD_MUTEX_INITIALIZER, -}; - -struct lttng_thread { - struct urcu_ref ref; - struct cds_list_head node; - pthread_t thread; - const char *name; - /* Main thread function */ - lttng_thread_entry_point entry; - /* - * Thread-specific shutdown method. Allows threads to implement their - * own shutdown mechanism as some of them use a structured message - * passed through a command queue and some rely on a dedicated "quit" - * pipe. - */ - lttng_thread_shutdown_cb shutdown; - lttng_thread_cleanup_cb cleanup; - /* Thread implementation-specific data. */ - void *data; -}; - -static -void lttng_thread_destroy(struct lttng_thread *thread) -{ - if (thread->cleanup) { - thread->cleanup(thread->data); - } - free(thread); -} - -static -void lttng_thread_release(struct urcu_ref *ref) -{ - lttng_thread_destroy(container_of(ref, struct lttng_thread, ref)); -} - -static -void *launch_thread(void *data) -{ - void *ret; - struct lttng_thread *thread = (struct lttng_thread *) data; - - logger_set_thread_name(thread->name, true); - DBG("Entering thread entry point"); - ret = thread->entry(thread->data); - DBG("Thread entry point has returned"); - return ret; -} - -struct lttng_thread *lttng_thread_create(const char *name, - lttng_thread_entry_point entry, - lttng_thread_shutdown_cb shutdown, - lttng_thread_cleanup_cb cleanup, - void *thread_data) -{ - int ret; - struct lttng_thread *thread; - - thread = zmalloc(sizeof(*thread)); - if (!thread) { - goto error_alloc; - } - - urcu_ref_init(&thread->ref); - CDS_INIT_LIST_HEAD(&thread->node); - /* - * Thread names are assumed to be statically allocated strings. - * It is unnecessary to copy this attribute. - */ - thread->name = name; - thread->entry = entry; - thread->shutdown = shutdown; - thread->cleanup = cleanup; - thread->data = thread_data; - - pthread_mutex_lock(&thread_list.lock); - /* - * Add the thread at the head of the list to shutdown threads in the - * opposite order of their creation. A reference is taken for the - * thread list which will be released on shutdown of the thread. - */ - cds_list_add(&thread->node, &thread_list.head); - (void) lttng_thread_get(thread); - - ret = pthread_create(&thread->thread, default_pthread_attr(), - launch_thread, thread); - if (ret) { - PERROR("Failed to create \"%s\" thread", thread->name); - goto error_pthread_create; - } - - pthread_mutex_unlock(&thread_list.lock); - return thread; - -error_pthread_create: - cds_list_del(&thread->node); - /* Release list reference. */ - lttng_thread_put(thread); - pthread_mutex_unlock(&thread_list.lock); - /* Release initial reference. */ - lttng_thread_put(thread); -error_alloc: - return NULL; -} - -bool lttng_thread_get(struct lttng_thread *thread) -{ - return urcu_ref_get_unless_zero(&thread->ref); -} - -void lttng_thread_put(struct lttng_thread *thread) -{ - if (!thread) { - return; - } - LTTNG_ASSERT(thread->ref.refcount); - urcu_ref_put(&thread->ref, lttng_thread_release); -} - -const char *lttng_thread_get_name(const struct lttng_thread *thread) -{ - return thread->name; -} - -static -bool _lttng_thread_shutdown(struct lttng_thread *thread) -{ - int ret; - void *status; - bool result = true; - - DBG("Shutting down \"%s\" thread", thread->name); - if (thread->shutdown) { - result = thread->shutdown(thread->data); - if (!result) { - result = false; - goto end; - } - } - - ret = pthread_join(thread->thread, &status); - if (ret) { - PERROR("Failed to join \"%s\" thread", thread->name); - result = false; - goto end; - } - DBG("Joined thread \"%s\"", thread->name); -end: - return result; -} - -bool lttng_thread_shutdown(struct lttng_thread *thread) -{ - 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; -} - -void lttng_thread_list_shutdown_orphans(void) -{ - struct lttng_thread *thread, *tmp; - - pthread_mutex_lock(&thread_list.lock); - cds_list_for_each_entry_safe(thread, tmp, &thread_list.head, node) { - bool result; - const long ref = uatomic_read(&thread->ref.refcount); - - if (ref != 1) { - /* - * Other external references to the thread exist, skip. - */ - continue; - } - - result = _lttng_thread_shutdown(thread); - if (!result) { - ERR("Failed to shutdown thread \"%s\"", thread->name); - } - } - pthread_mutex_unlock(&thread_list.lock); -} diff --git a/src/bin/lttng-sessiond/thread.cpp b/src/bin/lttng-sessiond/thread.cpp new file mode 100644 index 000000000..446a8c9d2 --- /dev/null +++ b/src/bin/lttng-sessiond/thread.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2018 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "thread.h" +#include +#include +#include +#include +#include +#include + +static struct thread_list { + struct cds_list_head head; + pthread_mutex_t lock; +} thread_list = { + .head = CDS_LIST_HEAD_INIT(thread_list.head), + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +struct lttng_thread { + struct urcu_ref ref; + struct cds_list_head node; + pthread_t thread; + const char *name; + /* Main thread function */ + lttng_thread_entry_point entry; + /* + * Thread-specific shutdown method. Allows threads to implement their + * own shutdown mechanism as some of them use a structured message + * passed through a command queue and some rely on a dedicated "quit" + * pipe. + */ + lttng_thread_shutdown_cb shutdown; + lttng_thread_cleanup_cb cleanup; + /* Thread implementation-specific data. */ + void *data; +}; + +static +void lttng_thread_destroy(struct lttng_thread *thread) +{ + if (thread->cleanup) { + thread->cleanup(thread->data); + } + free(thread); +} + +static +void lttng_thread_release(struct urcu_ref *ref) +{ + lttng_thread_destroy(container_of(ref, struct lttng_thread, ref)); +} + +static +void *launch_thread(void *data) +{ + void *ret; + struct lttng_thread *thread = (struct lttng_thread *) data; + + logger_set_thread_name(thread->name, true); + DBG("Entering thread entry point"); + ret = thread->entry(thread->data); + DBG("Thread entry point has returned"); + return ret; +} + +struct lttng_thread *lttng_thread_create(const char *name, + lttng_thread_entry_point entry, + lttng_thread_shutdown_cb shutdown, + lttng_thread_cleanup_cb cleanup, + void *thread_data) +{ + int ret; + struct lttng_thread *thread; + + thread = (lttng_thread *) zmalloc(sizeof(*thread)); + if (!thread) { + goto error_alloc; + } + + urcu_ref_init(&thread->ref); + CDS_INIT_LIST_HEAD(&thread->node); + /* + * Thread names are assumed to be statically allocated strings. + * It is unnecessary to copy this attribute. + */ + thread->name = name; + thread->entry = entry; + thread->shutdown = shutdown; + thread->cleanup = cleanup; + thread->data = thread_data; + + pthread_mutex_lock(&thread_list.lock); + /* + * Add the thread at the head of the list to shutdown threads in the + * opposite order of their creation. A reference is taken for the + * thread list which will be released on shutdown of the thread. + */ + cds_list_add(&thread->node, &thread_list.head); + (void) lttng_thread_get(thread); + + ret = pthread_create(&thread->thread, default_pthread_attr(), + launch_thread, thread); + if (ret) { + PERROR("Failed to create \"%s\" thread", thread->name); + goto error_pthread_create; + } + + pthread_mutex_unlock(&thread_list.lock); + return thread; + +error_pthread_create: + cds_list_del(&thread->node); + /* Release list reference. */ + lttng_thread_put(thread); + pthread_mutex_unlock(&thread_list.lock); + /* Release initial reference. */ + lttng_thread_put(thread); +error_alloc: + return NULL; +} + +bool lttng_thread_get(struct lttng_thread *thread) +{ + return urcu_ref_get_unless_zero(&thread->ref); +} + +void lttng_thread_put(struct lttng_thread *thread) +{ + if (!thread) { + return; + } + LTTNG_ASSERT(thread->ref.refcount); + urcu_ref_put(&thread->ref, lttng_thread_release); +} + +const char *lttng_thread_get_name(const struct lttng_thread *thread) +{ + return thread->name; +} + +static +bool _lttng_thread_shutdown(struct lttng_thread *thread) +{ + int ret; + void *status; + bool result = true; + + DBG("Shutting down \"%s\" thread", thread->name); + if (thread->shutdown) { + result = thread->shutdown(thread->data); + if (!result) { + result = false; + goto end; + } + } + + ret = pthread_join(thread->thread, &status); + if (ret) { + PERROR("Failed to join \"%s\" thread", thread->name); + result = false; + goto end; + } + DBG("Joined thread \"%s\"", thread->name); +end: + return result; +} + +bool lttng_thread_shutdown(struct lttng_thread *thread) +{ + 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; +} + +void lttng_thread_list_shutdown_orphans(void) +{ + struct lttng_thread *thread, *tmp; + + pthread_mutex_lock(&thread_list.lock); + cds_list_for_each_entry_safe(thread, tmp, &thread_list.head, node) { + bool result; + const long ref = uatomic_read(&thread->ref.refcount); + + if (ref != 1) { + /* + * Other external references to the thread exist, skip. + */ + continue; + } + + result = _lttng_thread_shutdown(thread); + if (!result) { + ERR("Failed to shutdown thread \"%s\"", thread->name); + } + } + pthread_mutex_unlock(&thread_list.lock); +} diff --git a/src/bin/lttng-sessiond/timer.c b/src/bin/lttng-sessiond/timer.c deleted file mode 100644 index 4e522a7fc..000000000 --- a/src/bin/lttng-sessiond/timer.c +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (C) 2017 Julien Desfossez - * Copyright (C) 2018 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include - -#include "timer.h" -#include "health-sessiond.h" -#include "rotation-thread.h" -#include "thread.h" - -#define LTTNG_SESSIOND_SIG_QS SIGRTMIN + 10 -#define LTTNG_SESSIOND_SIG_EXIT SIGRTMIN + 11 -#define LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK SIGRTMIN + 12 -#define LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION SIGRTMIN + 13 - -#define UINT_TO_PTR(value) \ - ({ \ - LTTNG_ASSERT(value <= UINTPTR_MAX); \ - (void *) (uintptr_t) value; \ - }) -#define PTR_TO_UINT(ptr) ((uintptr_t) ptr) - -/* - * Handle timer teardown race wrt memory free of private data by sessiond - * signals are handled by a single thread, which permits a synchronization - * point between handling of each signal. Internal lock ensures mutual - * exclusion. - */ -static -struct timer_signal_data { - /* Thread managing signals. */ - pthread_t tid; - int qs_done; - pthread_mutex_t lock; -} timer_signal = { - .tid = 0, - .qs_done = 0, - .lock = PTHREAD_MUTEX_INITIALIZER, -}; - -/* - * Set custom signal mask to current thread. - */ -static -void setmask(sigset_t *mask) -{ - int ret; - - ret = sigemptyset(mask); - if (ret) { - PERROR("sigemptyset"); - } - ret = sigaddset(mask, LTTNG_SESSIOND_SIG_QS); - if (ret) { - PERROR("sigaddset teardown"); - } - ret = sigaddset(mask, LTTNG_SESSIOND_SIG_EXIT); - if (ret) { - PERROR("sigaddset exit"); - } - ret = sigaddset(mask, LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK); - if (ret) { - PERROR("sigaddset pending rotation check"); - } - ret = sigaddset(mask, LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION); - if (ret) { - PERROR("sigaddset scheduled rotation"); - } -} - -/* - * This is the same function as timer_signal_thread_qs, when it - * returns, it means that no timer signr is currently pending or being handled - * by the timer thread. This cannot be called from the timer thread. - */ -static -void timer_signal_thread_qs(unsigned int signr) -{ - sigset_t pending_set; - int ret; - - /* - * We need to be the only thread interacting with the thread - * that manages signals for teardown synchronization. - */ - pthread_mutex_lock(&timer_signal.lock); - - /* Ensure we don't have any signal queued for this session. */ - for (;;) { - ret = sigemptyset(&pending_set); - if (ret == -1) { - PERROR("sigemptyset"); - } - ret = sigpending(&pending_set); - if (ret == -1) { - PERROR("sigpending"); - } - if (!sigismember(&pending_set, signr)) { - break; - } - caa_cpu_relax(); - } - - /* - * From this point, no new signal handler will be fired that would try to - * access "session". However, we still need to wait for any currently - * executing handler to complete. - */ - cmm_smp_mb(); - CMM_STORE_SHARED(timer_signal.qs_done, 0); - cmm_smp_mb(); - - /* - * Kill with LTTNG_SESSIOND_SIG_QS, so signal management thread - * wakes up. - */ - kill(getpid(), LTTNG_SESSIOND_SIG_QS); - - while (!CMM_LOAD_SHARED(timer_signal.qs_done)) { - caa_cpu_relax(); - } - cmm_smp_mb(); - - pthread_mutex_unlock(&timer_signal.lock); -} - -/* - * Start a timer on a session that will fire at a given interval - * (timer_interval_us) and fire a given signal (signal). - * - * Returns a negative value on error, 0 if a timer was created, and - * a positive value if no timer was created (not an error). - */ -static -int timer_start(timer_t *timer_id, struct ltt_session *session, - unsigned int timer_interval_us, int signal, bool one_shot) -{ - int ret = 0, delete_ret; - struct sigevent sev = {}; - struct itimerspec its; - - sev.sigev_notify = SIGEV_SIGNAL; - sev.sigev_signo = signal; - sev.sigev_value.sival_ptr = session; - ret = timer_create(CLOCK_MONOTONIC, &sev, timer_id); - if (ret == -1) { - PERROR("timer_create"); - goto end; - } - - its.it_value.tv_sec = timer_interval_us / 1000000; - its.it_value.tv_nsec = (timer_interval_us % 1000000) * 1000; - if (one_shot) { - its.it_interval.tv_sec = 0; - its.it_interval.tv_nsec = 0; - } else { - its.it_interval.tv_sec = its.it_value.tv_sec; - its.it_interval.tv_nsec = its.it_value.tv_nsec; - } - - ret = timer_settime(*timer_id, 0, &its, NULL); - if (ret == -1) { - PERROR("timer_settime"); - goto error_destroy_timer; - } - goto end; - -error_destroy_timer: - delete_ret = timer_delete(*timer_id); - if (delete_ret == -1) { - PERROR("timer_delete"); - } - -end: - return ret; -} - -static -int timer_stop(timer_t *timer_id, int signal) -{ - int ret = 0; - - ret = timer_delete(*timer_id); - if (ret == -1) { - PERROR("timer_delete"); - goto end; - } - - timer_signal_thread_qs(signal); - *timer_id = 0; -end: - return ret; -} - -int timer_session_rotation_pending_check_start(struct ltt_session *session, - unsigned int interval_us) -{ - int ret; - - if (!session_get(session)) { - ret = -1; - goto end; - } - DBG("Enabling session rotation pending check timer on session %" PRIu64, - session->id); - /* - * We arm this timer in a one-shot mode so we don't have to disable it - * explicitly (which could deadlock if the timer thread is blocked - * writing in the rotation_timer_pipe). - * - * Instead, we re-arm it if needed after the rotation_pending check as - * returned. Also, this timer is usually only needed once, so there is - * no need to go through the whole signal teardown scheme everytime. - */ - ret = timer_start(&session->rotation_pending_check_timer, - session, interval_us, - LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK, - /* one-shot */ true); - if (ret == 0) { - session->rotation_pending_check_timer_enabled = true; - } -end: - return ret; -} - -/* - * Call with session and session_list locks held. - */ -int timer_session_rotation_pending_check_stop(struct ltt_session *session) -{ - int ret; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(session->rotation_pending_check_timer_enabled); - - DBG("Disabling session rotation pending check timer on session %" PRIu64, - session->id); - ret = timer_stop(&session->rotation_pending_check_timer, - LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK); - if (ret == -1) { - ERR("Failed to stop rotate_pending_check timer"); - } else { - session->rotation_pending_check_timer_enabled = false; - /* - * The timer's reference to the session can be released safely. - */ - session_put(session); - } - return ret; -} - -/* - * Call with session and session_list locks held. - */ -int timer_session_rotation_schedule_timer_start(struct ltt_session *session, - unsigned int interval_us) -{ - int ret; - - if (!session_get(session)) { - ret = -1; - goto end; - } - DBG("Enabling scheduled rotation timer on session \"%s\" (%ui %s)", session->name, - interval_us, USEC_UNIT); - ret = timer_start(&session->rotation_schedule_timer, session, - interval_us, LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION, - /* one-shot */ false); - if (ret < 0) { - goto end; - } - session->rotation_schedule_timer_enabled = true; -end: - return ret; -} - -/* - * Call with session and session_list locks held. - */ -int timer_session_rotation_schedule_timer_stop(struct ltt_session *session) -{ - int ret = 0; - - LTTNG_ASSERT(session); - - if (!session->rotation_schedule_timer_enabled) { - goto end; - } - - DBG("Disabling scheduled rotation timer on session %s", session->name); - ret = timer_stop(&session->rotation_schedule_timer, - LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION); - if (ret < 0) { - ERR("Failed to stop scheduled rotation timer of session \"%s\"", - session->name); - goto end; - } - - session->rotation_schedule_timer_enabled = false; - /* The timer's reference to the session can be released safely. */ - session_put(session); - ret = 0; -end: - return ret; -} - -/* - * Block the RT signals for the entire process. It must be called from the - * sessiond main before creating the threads - */ -int timer_signal_init(void) -{ - int ret; - sigset_t mask; - - /* Block signal for entire process, so only our thread processes it. */ - setmask(&mask); - ret = pthread_sigmask(SIG_BLOCK, &mask, NULL); - if (ret) { - errno = ret; - PERROR("pthread_sigmask"); - return -1; - } - return 0; -} - -/* - * This thread is the sighandler for the timer signals. - */ -static -void *thread_timer(void *data) -{ - int signr; - sigset_t mask; - siginfo_t info; - struct timer_thread_parameters *ctx = data; - - rcu_register_thread(); - rcu_thread_online(); - - health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_TIMER); - health_code_update(); - - /* Only self thread will receive signal mask. */ - setmask(&mask); - CMM_STORE_SHARED(timer_signal.tid, pthread_self()); - - while (1) { - health_code_update(); - - health_poll_entry(); - signr = sigwaitinfo(&mask, &info); - health_poll_exit(); - - /* - * NOTE: cascading conditions are used instead of a switch case - * since the use of SIGRTMIN in the definition of the signals' - * values prevents the reduction to an integer constant. - */ - if (signr == -1) { - if (errno != EINTR) { - PERROR("sigwaitinfo"); - } - continue; - } else if (signr == LTTNG_SESSIOND_SIG_QS) { - cmm_smp_mb(); - CMM_STORE_SHARED(timer_signal.qs_done, 1); - cmm_smp_mb(); - } else if (signr == LTTNG_SESSIOND_SIG_EXIT) { - goto end; - } else if (signr == LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK) { - struct ltt_session *session = - (struct ltt_session *) info.si_value.sival_ptr; - - rotation_thread_enqueue_job(ctx->rotation_thread_job_queue, - ROTATION_THREAD_JOB_TYPE_CHECK_PENDING_ROTATION, - session); - } else if (signr == LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION) { - rotation_thread_enqueue_job(ctx->rotation_thread_job_queue, - ROTATION_THREAD_JOB_TYPE_SCHEDULED_ROTATION, - (struct ltt_session *) info.si_value.sival_ptr); - /* - * The scheduled periodic rotation timer is not in - * "one-shot" mode. The reference to the session is not - * released since the timer is still enabled and can - * still fire. - */ - } else { - ERR("Unexpected signal %d", info.si_signo); - } - } - -end: - DBG("Thread exit"); - health_unregister(the_health_sessiond); - rcu_thread_offline(); - rcu_unregister_thread(); - return NULL; -} - -static -bool shutdown_timer_thread(void *data) -{ - return kill(getpid(), LTTNG_SESSIOND_SIG_EXIT) == 0; -} - -bool launch_timer_thread( - struct timer_thread_parameters *timer_thread_parameters) -{ - struct lttng_thread *thread; - - thread = lttng_thread_create("Timer", - thread_timer, - shutdown_timer_thread, - NULL, - timer_thread_parameters); - if (!thread) { - goto error; - } - lttng_thread_put(thread); - return true; -error: - return false; -} diff --git a/src/bin/lttng-sessiond/timer.cpp b/src/bin/lttng-sessiond/timer.cpp new file mode 100644 index 000000000..5a1f35dfa --- /dev/null +++ b/src/bin/lttng-sessiond/timer.cpp @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2017 Julien Desfossez + * Copyright (C) 2018 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include + +#include "timer.h" +#include "health-sessiond.h" +#include "rotation-thread.h" +#include "thread.h" + +#define LTTNG_SESSIOND_SIG_QS SIGRTMIN + 10 +#define LTTNG_SESSIOND_SIG_EXIT SIGRTMIN + 11 +#define LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK SIGRTMIN + 12 +#define LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION SIGRTMIN + 13 + +#define UINT_TO_PTR(value) \ + ({ \ + LTTNG_ASSERT(value <= UINTPTR_MAX); \ + (void *) (uintptr_t) value; \ + }) +#define PTR_TO_UINT(ptr) ((uintptr_t) ptr) + +/* + * Handle timer teardown race wrt memory free of private data by sessiond + * signals are handled by a single thread, which permits a synchronization + * point between handling of each signal. Internal lock ensures mutual + * exclusion. + */ +static +struct timer_signal_data { + /* Thread managing signals. */ + pthread_t tid; + int qs_done; + pthread_mutex_t lock; +} timer_signal = { + .tid = 0, + .qs_done = 0, + .lock = PTHREAD_MUTEX_INITIALIZER, +}; + +/* + * Set custom signal mask to current thread. + */ +static +void setmask(sigset_t *mask) +{ + int ret; + + ret = sigemptyset(mask); + if (ret) { + PERROR("sigemptyset"); + } + ret = sigaddset(mask, LTTNG_SESSIOND_SIG_QS); + if (ret) { + PERROR("sigaddset teardown"); + } + ret = sigaddset(mask, LTTNG_SESSIOND_SIG_EXIT); + if (ret) { + PERROR("sigaddset exit"); + } + ret = sigaddset(mask, LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK); + if (ret) { + PERROR("sigaddset pending rotation check"); + } + ret = sigaddset(mask, LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION); + if (ret) { + PERROR("sigaddset scheduled rotation"); + } +} + +/* + * This is the same function as timer_signal_thread_qs, when it + * returns, it means that no timer signr is currently pending or being handled + * by the timer thread. This cannot be called from the timer thread. + */ +static +void timer_signal_thread_qs(unsigned int signr) +{ + sigset_t pending_set; + int ret; + + /* + * We need to be the only thread interacting with the thread + * that manages signals for teardown synchronization. + */ + pthread_mutex_lock(&timer_signal.lock); + + /* Ensure we don't have any signal queued for this session. */ + for (;;) { + ret = sigemptyset(&pending_set); + if (ret == -1) { + PERROR("sigemptyset"); + } + ret = sigpending(&pending_set); + if (ret == -1) { + PERROR("sigpending"); + } + if (!sigismember(&pending_set, signr)) { + break; + } + caa_cpu_relax(); + } + + /* + * From this point, no new signal handler will be fired that would try to + * access "session". However, we still need to wait for any currently + * executing handler to complete. + */ + cmm_smp_mb(); + CMM_STORE_SHARED(timer_signal.qs_done, 0); + cmm_smp_mb(); + + /* + * Kill with LTTNG_SESSIOND_SIG_QS, so signal management thread + * wakes up. + */ + kill(getpid(), LTTNG_SESSIOND_SIG_QS); + + while (!CMM_LOAD_SHARED(timer_signal.qs_done)) { + caa_cpu_relax(); + } + cmm_smp_mb(); + + pthread_mutex_unlock(&timer_signal.lock); +} + +/* + * Start a timer on a session that will fire at a given interval + * (timer_interval_us) and fire a given signal (signal). + * + * Returns a negative value on error, 0 if a timer was created, and + * a positive value if no timer was created (not an error). + */ +static +int timer_start(timer_t *timer_id, struct ltt_session *session, + unsigned int timer_interval_us, int signal, bool one_shot) +{ + int ret = 0, delete_ret; + struct sigevent sev = {}; + struct itimerspec its; + + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = signal; + sev.sigev_value.sival_ptr = session; + ret = timer_create(CLOCK_MONOTONIC, &sev, timer_id); + if (ret == -1) { + PERROR("timer_create"); + goto end; + } + + its.it_value.tv_sec = timer_interval_us / 1000000; + its.it_value.tv_nsec = (timer_interval_us % 1000000) * 1000; + if (one_shot) { + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + } else { + its.it_interval.tv_sec = its.it_value.tv_sec; + its.it_interval.tv_nsec = its.it_value.tv_nsec; + } + + ret = timer_settime(*timer_id, 0, &its, NULL); + if (ret == -1) { + PERROR("timer_settime"); + goto error_destroy_timer; + } + goto end; + +error_destroy_timer: + delete_ret = timer_delete(*timer_id); + if (delete_ret == -1) { + PERROR("timer_delete"); + } + +end: + return ret; +} + +static +int timer_stop(timer_t *timer_id, int signal) +{ + int ret = 0; + + ret = timer_delete(*timer_id); + if (ret == -1) { + PERROR("timer_delete"); + goto end; + } + + timer_signal_thread_qs(signal); + *timer_id = 0; +end: + return ret; +} + +int timer_session_rotation_pending_check_start(struct ltt_session *session, + unsigned int interval_us) +{ + int ret; + + if (!session_get(session)) { + ret = -1; + goto end; + } + DBG("Enabling session rotation pending check timer on session %" PRIu64, + session->id); + /* + * We arm this timer in a one-shot mode so we don't have to disable it + * explicitly (which could deadlock if the timer thread is blocked + * writing in the rotation_timer_pipe). + * + * Instead, we re-arm it if needed after the rotation_pending check as + * returned. Also, this timer is usually only needed once, so there is + * no need to go through the whole signal teardown scheme everytime. + */ + ret = timer_start(&session->rotation_pending_check_timer, + session, interval_us, + LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK, + /* one-shot */ true); + if (ret == 0) { + session->rotation_pending_check_timer_enabled = true; + } +end: + return ret; +} + +/* + * Call with session and session_list locks held. + */ +int timer_session_rotation_pending_check_stop(struct ltt_session *session) +{ + int ret; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(session->rotation_pending_check_timer_enabled); + + DBG("Disabling session rotation pending check timer on session %" PRIu64, + session->id); + ret = timer_stop(&session->rotation_pending_check_timer, + LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK); + if (ret == -1) { + ERR("Failed to stop rotate_pending_check timer"); + } else { + session->rotation_pending_check_timer_enabled = false; + /* + * The timer's reference to the session can be released safely. + */ + session_put(session); + } + return ret; +} + +/* + * Call with session and session_list locks held. + */ +int timer_session_rotation_schedule_timer_start(struct ltt_session *session, + unsigned int interval_us) +{ + int ret; + + if (!session_get(session)) { + ret = -1; + goto end; + } + DBG("Enabling scheduled rotation timer on session \"%s\" (%ui %s)", session->name, + interval_us, USEC_UNIT); + ret = timer_start(&session->rotation_schedule_timer, session, + interval_us, LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION, + /* one-shot */ false); + if (ret < 0) { + goto end; + } + session->rotation_schedule_timer_enabled = true; +end: + return ret; +} + +/* + * Call with session and session_list locks held. + */ +int timer_session_rotation_schedule_timer_stop(struct ltt_session *session) +{ + int ret = 0; + + LTTNG_ASSERT(session); + + if (!session->rotation_schedule_timer_enabled) { + goto end; + } + + DBG("Disabling scheduled rotation timer on session %s", session->name); + ret = timer_stop(&session->rotation_schedule_timer, + LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION); + if (ret < 0) { + ERR("Failed to stop scheduled rotation timer of session \"%s\"", + session->name); + goto end; + } + + session->rotation_schedule_timer_enabled = false; + /* The timer's reference to the session can be released safely. */ + session_put(session); + ret = 0; +end: + return ret; +} + +/* + * Block the RT signals for the entire process. It must be called from the + * sessiond main before creating the threads + */ +int timer_signal_init(void) +{ + int ret; + sigset_t mask; + + /* Block signal for entire process, so only our thread processes it. */ + setmask(&mask); + ret = pthread_sigmask(SIG_BLOCK, &mask, NULL); + if (ret) { + errno = ret; + PERROR("pthread_sigmask"); + return -1; + } + return 0; +} + +/* + * This thread is the sighandler for the timer signals. + */ +static +void *thread_timer(void *data) +{ + int signr; + sigset_t mask; + siginfo_t info; + struct timer_thread_parameters *ctx = (timer_thread_parameters *) data; + + rcu_register_thread(); + rcu_thread_online(); + + health_register(the_health_sessiond, HEALTH_SESSIOND_TYPE_TIMER); + health_code_update(); + + /* Only self thread will receive signal mask. */ + setmask(&mask); + CMM_STORE_SHARED(timer_signal.tid, pthread_self()); + + while (1) { + health_code_update(); + + health_poll_entry(); + signr = sigwaitinfo(&mask, &info); + health_poll_exit(); + + /* + * NOTE: cascading conditions are used instead of a switch case + * since the use of SIGRTMIN in the definition of the signals' + * values prevents the reduction to an integer constant. + */ + if (signr == -1) { + if (errno != EINTR) { + PERROR("sigwaitinfo"); + } + continue; + } else if (signr == LTTNG_SESSIOND_SIG_QS) { + cmm_smp_mb(); + CMM_STORE_SHARED(timer_signal.qs_done, 1); + cmm_smp_mb(); + } else if (signr == LTTNG_SESSIOND_SIG_EXIT) { + goto end; + } else if (signr == LTTNG_SESSIOND_SIG_PENDING_ROTATION_CHECK) { + struct ltt_session *session = + (struct ltt_session *) info.si_value.sival_ptr; + + rotation_thread_enqueue_job(ctx->rotation_thread_job_queue, + ROTATION_THREAD_JOB_TYPE_CHECK_PENDING_ROTATION, + session); + } else if (signr == LTTNG_SESSIOND_SIG_SCHEDULED_ROTATION) { + rotation_thread_enqueue_job(ctx->rotation_thread_job_queue, + ROTATION_THREAD_JOB_TYPE_SCHEDULED_ROTATION, + (struct ltt_session *) info.si_value.sival_ptr); + /* + * The scheduled periodic rotation timer is not in + * "one-shot" mode. The reference to the session is not + * released since the timer is still enabled and can + * still fire. + */ + } else { + ERR("Unexpected signal %d", info.si_signo); + } + } + +end: + DBG("Thread exit"); + health_unregister(the_health_sessiond); + rcu_thread_offline(); + rcu_unregister_thread(); + return NULL; +} + +static +bool shutdown_timer_thread(void *data) +{ + return kill(getpid(), LTTNG_SESSIOND_SIG_EXIT) == 0; +} + +bool launch_timer_thread( + struct timer_thread_parameters *timer_thread_parameters) +{ + struct lttng_thread *thread; + + thread = lttng_thread_create("Timer", + thread_timer, + shutdown_timer_thread, + NULL, + timer_thread_parameters); + if (!thread) { + goto error; + } + lttng_thread_put(thread); + return true; +error: + return false; +} diff --git a/src/bin/lttng-sessiond/trace-kernel.c b/src/bin/lttng-sessiond/trace-kernel.c deleted file mode 100644 index cbafd12bd..000000000 --- a/src/bin/lttng-sessiond/trace-kernel.c +++ /dev/null @@ -1,1052 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "consumer.h" -#include "trace-kernel.h" -#include "lttng-sessiond.h" -#include "notification-thread-commands.h" - -/* - * Find the channel name for the given kernel session. - */ -struct ltt_kernel_channel *trace_kernel_get_channel_by_name( - const char *name, struct ltt_kernel_session *session) -{ - struct ltt_kernel_channel *chan; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(name); - - /* - * If we receive an empty string for channel name, it means the - * default channel name is requested. - */ - if (name[0] == '\0') - name = DEFAULT_CHANNEL_NAME; - - DBG("Trying to find channel %s", name); - - cds_list_for_each_entry(chan, &session->channel_list.head, list) { - if (strcmp(name, chan->channel->name) == 0) { - DBG("Found channel by name %s", name); - return chan; - } - } - - return NULL; -} - -/* - * Find the event for the given channel. - */ -struct ltt_kernel_event *trace_kernel_find_event( - char *name, struct ltt_kernel_channel *channel, - enum lttng_event_type type, - struct lttng_bytecode *filter) -{ - struct ltt_kernel_event *ev; - int found = 0; - - LTTNG_ASSERT(name); - LTTNG_ASSERT(channel); - - cds_list_for_each_entry(ev, &channel->events_list.head, list) { - if (type != LTTNG_EVENT_ALL && ev->type != type) { - continue; - } - if (strcmp(name, ev->event->name)) { - continue; - } - if ((ev->filter && !filter) || (!ev->filter && filter)) { - continue; - } - if (ev->filter && filter) { - if (ev->filter->len != filter->len || - memcmp(ev->filter->data, filter->data, - filter->len) != 0) { - continue; - } - } - found = 1; - break; - } - if (found) { - DBG("Found event %s for channel %s", name, - channel->channel->name); - return ev; - } else { - return NULL; - } -} - -/* - * Find the event name for the given channel. - */ -struct ltt_kernel_event *trace_kernel_get_event_by_name( - char *name, struct ltt_kernel_channel *channel, - enum lttng_event_type type) -{ - struct ltt_kernel_event *ev; - int found = 0; - - LTTNG_ASSERT(name); - LTTNG_ASSERT(channel); - - cds_list_for_each_entry(ev, &channel->events_list.head, list) { - if (type != LTTNG_EVENT_ALL && ev->type != type) { - continue; - } - if (strcmp(name, ev->event->name)) { - continue; - } - found = 1; - break; - } - if (found) { - DBG("Found event %s for channel %s", name, - channel->channel->name); - return ev; - } else { - return NULL; - } -} - -/* - * Allocate and initialize a kernel session data structure. - * - * Return pointer to structure or NULL. - */ -struct ltt_kernel_session *trace_kernel_create_session(void) -{ - struct ltt_kernel_session *lks = NULL; - - /* Allocate a new ltt kernel session */ - lks = zmalloc(sizeof(struct ltt_kernel_session)); - if (lks == NULL) { - PERROR("create kernel session zmalloc"); - goto alloc_error; - } - - /* Init data structure */ - lks->fd = -1; - lks->metadata_stream_fd = -1; - lks->channel_count = 0; - lks->stream_count_global = 0; - lks->metadata = NULL; - CDS_INIT_LIST_HEAD(&lks->channel_list.head); - - lks->tracker_pid = process_attr_tracker_create(); - if (!lks->tracker_pid) { - goto error; - } - lks->tracker_vpid = process_attr_tracker_create(); - if (!lks->tracker_vpid) { - goto error; - } - lks->tracker_uid = process_attr_tracker_create(); - if (!lks->tracker_uid) { - goto error; - } - lks->tracker_vuid = process_attr_tracker_create(); - if (!lks->tracker_vuid) { - goto error; - } - lks->tracker_gid = process_attr_tracker_create(); - if (!lks->tracker_gid) { - goto error; - } - lks->tracker_vgid = process_attr_tracker_create(); - if (!lks->tracker_vgid) { - goto error; - } - lks->consumer = consumer_create_output(CONSUMER_DST_LOCAL); - if (lks->consumer == NULL) { - goto error; - } - - return lks; - -error: - process_attr_tracker_destroy(lks->tracker_pid); - process_attr_tracker_destroy(lks->tracker_vpid); - process_attr_tracker_destroy(lks->tracker_uid); - process_attr_tracker_destroy(lks->tracker_vuid); - process_attr_tracker_destroy(lks->tracker_gid); - process_attr_tracker_destroy(lks->tracker_vgid); - free(lks); - -alloc_error: - return NULL; -} - -/* - * Allocate and initialize a kernel channel data structure. - * - * Return pointer to structure or NULL. - */ -struct ltt_kernel_channel *trace_kernel_create_channel( - struct lttng_channel *chan) -{ - struct ltt_kernel_channel *lkc; - struct lttng_channel_extended *extended = NULL; - - LTTNG_ASSERT(chan); - - lkc = zmalloc(sizeof(struct ltt_kernel_channel)); - if (lkc == NULL) { - PERROR("ltt_kernel_channel zmalloc"); - goto error; - } - - lkc->channel = zmalloc(sizeof(struct lttng_channel)); - if (lkc->channel == NULL) { - PERROR("lttng_channel zmalloc"); - goto error; - } - - extended = zmalloc(sizeof(struct lttng_channel_extended)); - if (!extended) { - PERROR("lttng_channel_channel zmalloc"); - goto error; - } - memcpy(lkc->channel, chan, sizeof(struct lttng_channel)); - memcpy(extended, chan->attr.extended.ptr, sizeof(struct lttng_channel_extended)); - lkc->channel->attr.extended.ptr = extended; - extended = NULL; - - /* - * If we receive an empty string for channel name, it means the - * default channel name is requested. - */ - if (chan->name[0] == '\0') { - strncpy(lkc->channel->name, DEFAULT_CHANNEL_NAME, - sizeof(lkc->channel->name)); - } - lkc->channel->name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; - - lkc->fd = -1; - lkc->stream_count = 0; - lkc->event_count = 0; - lkc->enabled = 1; - lkc->published_to_notification_thread = false; - /* Init linked list */ - CDS_INIT_LIST_HEAD(&lkc->events_list.head); - CDS_INIT_LIST_HEAD(&lkc->stream_list.head); - CDS_INIT_LIST_HEAD(&lkc->ctx_list); - - return lkc; - -error: - if (lkc) { - free(lkc->channel); - } - free(extended); - free(lkc); - return NULL; -} - -/* - * Allocate and init a kernel context object. - * - * Return the allocated object or NULL on error. - */ -struct ltt_kernel_context *trace_kernel_create_context( - struct lttng_kernel_abi_context *ctx) -{ - struct ltt_kernel_context *kctx; - - kctx = zmalloc(sizeof(*kctx)); - if (!kctx) { - PERROR("zmalloc kernel context"); - goto error; - } - - if (ctx) { - memcpy(&kctx->ctx, ctx, sizeof(kctx->ctx)); - } -error: - return kctx; -} - -/* - * Allocate and init a kernel context object from an existing kernel context - * object. - * - * Return the allocated object or NULL on error. - */ -struct ltt_kernel_context *trace_kernel_copy_context( - struct ltt_kernel_context *kctx) -{ - struct ltt_kernel_context *kctx_copy; - - LTTNG_ASSERT(kctx); - kctx_copy = zmalloc(sizeof(*kctx_copy)); - if (!kctx_copy) { - PERROR("zmalloc ltt_kernel_context"); - goto error; - } - - memcpy(kctx_copy, kctx, sizeof(*kctx_copy)); - memset(&kctx_copy->list, 0, sizeof(kctx_copy->list)); - -error: - return kctx_copy; -} - -/* - * Allocate and initialize a kernel event. Set name and event type. - * We own filter_expression, and filter. - * - * Return pointer to structure or NULL. - */ -enum lttng_error_code trace_kernel_create_event( - struct lttng_event *ev, char *filter_expression, - struct lttng_bytecode *filter, - struct ltt_kernel_event **kernel_event) -{ - enum lttng_error_code ret; - struct lttng_kernel_abi_event *attr; - struct ltt_kernel_event *local_kernel_event; - struct lttng_userspace_probe_location *userspace_probe_location = NULL; - - LTTNG_ASSERT(ev); - - local_kernel_event = zmalloc(sizeof(struct ltt_kernel_event)); - attr = zmalloc(sizeof(struct lttng_kernel_abi_event)); - if (local_kernel_event == NULL || attr == NULL) { - PERROR("kernel event zmalloc"); - ret = LTTNG_ERR_NOMEM; - goto error; - } - - switch (ev->type) { - case LTTNG_EVENT_PROBE: - attr->instrumentation = LTTNG_KERNEL_ABI_KPROBE; - attr->u.kprobe.addr = ev->attr.probe.addr; - attr->u.kprobe.offset = ev->attr.probe.offset; - strncpy(attr->u.kprobe.symbol_name, - ev->attr.probe.symbol_name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); - attr->u.kprobe.symbol_name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; - break; - case LTTNG_EVENT_USERSPACE_PROBE: - { - const struct lttng_userspace_probe_location* location = NULL; - const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; - - location = lttng_event_get_userspace_probe_location(ev); - if (!location) { - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - - /* - * From this point on, the specific term 'uprobe' is used - * instead of the generic 'userspace probe' because it's the - * technology used at the moment for this instrumentation. - * LTTng currently implements userspace probes using uprobes. - * In the interactions with the kernel tracer, we use the - * uprobe term. - */ - attr->instrumentation = LTTNG_KERNEL_ABI_UPROBE; - - 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. */ - attr->u.uprobe.fd = - lttng_userspace_probe_location_function_get_binary_fd(location); - - /* - * Save a reference to the probe location used during - * the listing of events. - */ - userspace_probe_location = - lttng_userspace_probe_location_copy(location); - break; - case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: - /* Get the file descriptor on the target binary. */ - attr->u.uprobe.fd = - lttng_userspace_probe_location_tracepoint_get_binary_fd(location); - - /* - * Save a reference to the probe location used during the listing of - * events. - */ - userspace_probe_location = - lttng_userspace_probe_location_copy(location); - break; - default: - DBG("Unsupported lookup method type"); - ret = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - break; - } - case LTTNG_EVENT_FUNCTION: - attr->instrumentation = LTTNG_KERNEL_ABI_KRETPROBE; - attr->u.kretprobe.addr = ev->attr.probe.addr; - attr->u.kretprobe.offset = ev->attr.probe.offset; - strncpy(attr->u.kretprobe.symbol_name, - ev->attr.probe.symbol_name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); - attr->u.kretprobe.symbol_name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; - break; - case LTTNG_EVENT_FUNCTION_ENTRY: - attr->instrumentation = LTTNG_KERNEL_ABI_FUNCTION; - strncpy(attr->u.ftrace.symbol_name, - ev->attr.ftrace.symbol_name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); - attr->u.ftrace.symbol_name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; - break; - case LTTNG_EVENT_TRACEPOINT: - attr->instrumentation = LTTNG_KERNEL_ABI_TRACEPOINT; - break; - case LTTNG_EVENT_SYSCALL: - attr->instrumentation = LTTNG_KERNEL_ABI_SYSCALL; - attr->u.syscall.abi = LTTNG_KERNEL_ABI_SYSCALL_ABI_ALL; - attr->u.syscall.entryexit = LTTNG_KERNEL_ABI_SYSCALL_ENTRYEXIT; - attr->u.syscall.match = LTTNG_KERNEL_ABI_SYSCALL_MATCH_NAME; - break; - case LTTNG_EVENT_ALL: - attr->instrumentation = LTTNG_KERNEL_ABI_ALL; - break; - default: - ERR("Unknown kernel instrumentation type (%d)", ev->type); - ret = LTTNG_ERR_INVALID; - goto error; - } - - /* Copy event name */ - strncpy(attr->name, ev->name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); - attr->name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; - - /* Setting up a kernel event */ - local_kernel_event->fd = -1; - local_kernel_event->event = attr; - local_kernel_event->enabled = 1; - local_kernel_event->filter_expression = filter_expression; - local_kernel_event->filter = filter; - local_kernel_event->userspace_probe_location = userspace_probe_location; - - *kernel_event = local_kernel_event; - - return LTTNG_OK; - -error: - free(filter_expression); - free(filter); - free(local_kernel_event); - free(attr); - return ret; -} - -/* - * Allocate and initialize a kernel token event rule. - * - * Return pointer to structure or NULL. - */ -enum lttng_error_code trace_kernel_create_event_notifier_rule( - struct lttng_trigger *trigger, - uint64_t token, - uint64_t error_counter_index, - struct ltt_kernel_event_notifier_rule **event_notifier_rule) -{ - enum lttng_error_code ret = LTTNG_OK; - enum lttng_condition_type condition_type; - enum lttng_event_rule_type event_rule_type; - enum lttng_condition_status condition_status; - struct ltt_kernel_event_notifier_rule *local_kernel_token_event_rule; - const struct lttng_condition *condition = NULL; - const struct lttng_event_rule *event_rule = NULL; - - LTTNG_ASSERT(event_notifier_rule); - - condition = lttng_trigger_get_const_condition(trigger); - LTTNG_ASSERT(condition); - - condition_type = lttng_condition_get_type(condition); - LTTNG_ASSERT(condition_type == LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); - - condition_status = lttng_condition_event_rule_matches_get_rule( - condition, &event_rule); - LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); - LTTNG_ASSERT(event_rule); - - event_rule_type = lttng_event_rule_get_type(event_rule); - LTTNG_ASSERT(event_rule_type != LTTNG_EVENT_RULE_TYPE_UNKNOWN); - - local_kernel_token_event_rule = - zmalloc(sizeof(struct ltt_kernel_event_notifier_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; - local_kernel_token_event_rule->error_counter_index = error_counter_index; - - /* Get the reference of the event rule. */ - lttng_trigger_get(trigger); - - local_kernel_token_event_rule->trigger = trigger; - /* The event rule still owns the filter and bytecode. */ - local_kernel_token_event_rule->filter = - lttng_event_rule_get_filter_bytecode(event_rule); - - DBG3("Created kernel event notifier rule: token = %" PRIu64, - local_kernel_token_event_rule->token); -error: - *event_notifier_rule = local_kernel_token_event_rule; - return ret; -} - -/* - * Initialize a kernel trigger from an event rule. - */ -enum lttng_error_code trace_kernel_init_event_notifier_from_event_rule( - const struct lttng_event_rule *rule, - struct lttng_kernel_abi_event_notifier *kernel_event_notifier) -{ - enum lttng_error_code ret_code; - const char *name; - int strncpy_ret; - - switch (lttng_event_rule_get_type(rule)) { - case LTTNG_EVENT_RULE_TYPE_KERNEL_KPROBE: - { - uint64_t address = 0, offset = 0; - const char *symbol_name = NULL; - const struct lttng_kernel_probe_location *location = NULL; - enum lttng_kernel_probe_location_status k_status; - enum lttng_event_rule_status status; - - status = lttng_event_rule_kernel_kprobe_get_location(rule, &location); - if (status != LTTNG_EVENT_RULE_STATUS_OK) { - ret_code = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - - switch (lttng_kernel_probe_location_get_type(location)) { - case LTTNG_KERNEL_PROBE_LOCATION_TYPE_ADDRESS: - { - k_status = lttng_kernel_probe_location_address_get_address( - location, &address); - LTTNG_ASSERT(k_status == LTTNG_KERNEL_PROBE_LOCATION_STATUS_OK); - break; - } - case LTTNG_KERNEL_PROBE_LOCATION_TYPE_SYMBOL_OFFSET: - { - k_status = lttng_kernel_probe_location_symbol_get_offset( - location, &offset); - LTTNG_ASSERT(k_status == LTTNG_KERNEL_PROBE_LOCATION_STATUS_OK); - symbol_name = lttng_kernel_probe_location_symbol_get_name( - location); - break; - } - default: - abort(); - } - - kernel_event_notifier->event.instrumentation = LTTNG_KERNEL_ABI_KPROBE; - kernel_event_notifier->event.u.kprobe.addr = address; - kernel_event_notifier->event.u.kprobe.offset = offset; - if (symbol_name) { - strncpy_ret = lttng_strncpy( - kernel_event_notifier->event.u.kprobe.symbol_name, - symbol_name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); - - if (strncpy_ret) { - ret_code = LTTNG_ERR_INVALID; - goto error; - } - } - - kernel_event_notifier->event.u.kprobe.symbol_name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; - - status = lttng_event_rule_kernel_kprobe_get_event_name(rule, &name); - LTTNG_ASSERT(status == LTTNG_EVENT_RULE_STATUS_OK); - ret_code = LTTNG_OK; - break; - } - case LTTNG_EVENT_RULE_TYPE_KERNEL_UPROBE: - { - const struct lttng_userspace_probe_location* location = NULL; - const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; - enum lttng_event_rule_status status; - - status = lttng_event_rule_kernel_uprobe_get_location(rule, &location); - if (status != LTTNG_EVENT_RULE_STATUS_OK) { - ret_code = LTTNG_ERR_PROBE_LOCATION_INVAL; - goto error; - } - - kernel_event_notifier->event.instrumentation = LTTNG_KERNEL_ABI_UPROBE; - - lookup = lttng_userspace_probe_location_get_lookup_method( - location); - if (!lookup) { - ret_code = 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_event_notifier->event.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_event_notifier->event.u.uprobe.fd = - lttng_userspace_probe_location_tracepoint_get_binary_fd(location); - break; - default: - abort(); - } - - status = lttng_event_rule_kernel_uprobe_get_event_name( - rule, &name); - LTTNG_ASSERT(status == LTTNG_EVENT_RULE_STATUS_OK); - ret_code = LTTNG_OK; - break; - } - case LTTNG_EVENT_RULE_TYPE_KERNEL_TRACEPOINT: - { - const enum lttng_event_rule_status status = - lttng_event_rule_kernel_tracepoint_get_name_pattern( - rule, &name); - - LTTNG_ASSERT(status == LTTNG_EVENT_RULE_STATUS_OK); - kernel_event_notifier->event.instrumentation = - LTTNG_KERNEL_ABI_TRACEPOINT; - - ret_code = LTTNG_OK; - break; - } - case LTTNG_EVENT_RULE_TYPE_KERNEL_SYSCALL: - { - const enum lttng_event_rule_status status = - lttng_event_rule_kernel_syscall_get_name_pattern( - rule, &name); - const enum lttng_event_rule_kernel_syscall_emission_site - emission_site = - lttng_event_rule_kernel_syscall_get_emission_site(rule); - enum lttng_kernel_abi_syscall_entryexit entryexit; - - LTTNG_ASSERT(status == LTTNG_EVENT_RULE_STATUS_OK); - LTTNG_ASSERT(emission_site != LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_UNKNOWN); - - switch(emission_site) { - case LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_ENTRY: - entryexit = LTTNG_KERNEL_ABI_SYSCALL_ENTRY; - break; - case LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_EXIT: - entryexit = LTTNG_KERNEL_ABI_SYSCALL_EXIT; - break; - case LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_ENTRY_EXIT: - entryexit = LTTNG_KERNEL_ABI_SYSCALL_ENTRYEXIT; - break; - default: - abort(); - break; - } - - kernel_event_notifier->event.instrumentation = - LTTNG_KERNEL_ABI_SYSCALL; - kernel_event_notifier->event.u.syscall.abi = - LTTNG_KERNEL_ABI_SYSCALL_ABI_ALL; - kernel_event_notifier->event.u.syscall.entryexit = - entryexit; - kernel_event_notifier->event.u.syscall.match = - LTTNG_KERNEL_ABI_SYSCALL_MATCH_NAME; - ret_code = LTTNG_OK; - break; - } - default: - abort(); - break; - } - - strncpy_ret = lttng_strncpy(kernel_event_notifier->event.name, name, - LTTNG_KERNEL_ABI_SYM_NAME_LEN); - if (strncpy_ret) { - ret_code = LTTNG_ERR_INVALID; - goto error; - } - -error: - return ret_code; -} -/* - * Allocate and initialize a kernel metadata. - * - * Return pointer to structure or NULL. - */ -struct ltt_kernel_metadata *trace_kernel_create_metadata(void) -{ - int ret; - struct ltt_kernel_metadata *lkm; - struct lttng_channel *chan; - - lkm = zmalloc(sizeof(struct ltt_kernel_metadata)); - chan = zmalloc(sizeof(struct lttng_channel)); - if (lkm == NULL || chan == NULL) { - PERROR("kernel metadata zmalloc"); - goto error; - } - - ret = lttng_strncpy( - chan->name, DEFAULT_METADATA_NAME, sizeof(chan->name)); - if (ret) { - ERR("Failed to initialize metadata channel name to `%s`", - DEFAULT_METADATA_NAME); - goto error; - } - - /* Set default attributes */ - chan->attr.overwrite = DEFAULT_METADATA_OVERWRITE; - chan->attr.subbuf_size = default_get_metadata_subbuf_size(); - chan->attr.num_subbuf = DEFAULT_METADATA_SUBBUF_NUM; - chan->attr.switch_timer_interval = DEFAULT_METADATA_SWITCH_TIMER; - chan->attr.read_timer_interval = DEFAULT_METADATA_READ_TIMER;; - - - /* - * The metadata channel of kernel sessions must use the "mmap" - * back-end since the consumer daemon accumulates complete - * metadata units before sending them to the relay daemon in - * live mode. The consumer daemon also needs to extract the contents - * of the metadata cache when computing a rotation position. - * - * In both cases, it is not possible to rely on the splice - * back-end as the consumer daemon may need to accumulate more - * content than can be backed by the ring buffer's underlying - * pages. - */ - chan->attr.output = LTTNG_EVENT_MMAP; - chan->attr.tracefile_size = 0; - chan->attr.tracefile_count = 0; - chan->attr.live_timer_interval = 0; - - /* Init metadata */ - lkm->fd = -1; - lkm->conf = chan; - - return lkm; - -error: - free(lkm); - free(chan); - return NULL; -} - -/* - * Allocate and initialize a kernel stream. The stream is set to ACTIVE_FD by - * default. - * - * Return pointer to structure or NULL. - */ -struct ltt_kernel_stream *trace_kernel_create_stream(const char *name, - unsigned int count) -{ - int ret; - struct ltt_kernel_stream *lks; - - LTTNG_ASSERT(name); - - lks = zmalloc(sizeof(struct ltt_kernel_stream)); - if (lks == NULL) { - PERROR("kernel stream zmalloc"); - goto error; - } - - /* Set name */ - ret = snprintf(lks->name, sizeof(lks->name), "%s_%u", name, count); - if (ret < 0) { - PERROR("snprintf stream name"); - goto error; - } - lks->name[sizeof(lks->name) - 1] = '\0'; - - /* Init stream */ - lks->fd = -1; - lks->state = 0; - lks->cpu = count; - - return lks; - -error: - return NULL; -} - -/* - * Cleanup kernel stream structure. - */ -void trace_kernel_destroy_stream(struct ltt_kernel_stream *stream) -{ - LTTNG_ASSERT(stream); - - DBG("[trace] Closing stream fd %d", stream->fd); - /* Close kernel fd */ - if (stream->fd >= 0) { - int ret; - - ret = close(stream->fd); - if (ret) { - PERROR("close"); - } - } - /* Remove from stream list */ - cds_list_del(&stream->list); - - free(stream); -} - -/* - * Cleanup kernel event structure. - */ -void trace_kernel_destroy_event(struct ltt_kernel_event *event) -{ - LTTNG_ASSERT(event); - - if (event->fd >= 0) { - int ret; - - DBG("[trace] Closing event fd %d", event->fd); - /* Close kernel fd */ - ret = close(event->fd); - if (ret) { - PERROR("close"); - } - } else { - DBG("[trace] Tearing down event (no associated file descriptor)"); - } - - /* Remove from event list */ - cds_list_del(&event->list); - - free(event->filter_expression); - free(event->filter); - - free(event->event); - free(event); -} - -/* - * Cleanup kernel event structure. - */ -static void free_token_event_rule_rcu(struct rcu_head *rcu_node) -{ - struct ltt_kernel_event_notifier_rule *rule = caa_container_of(rcu_node, - struct ltt_kernel_event_notifier_rule, rcu_node); - - free(rule); -} - -void trace_kernel_destroy_event_notifier_rule( - struct ltt_kernel_event_notifier_rule *event) -{ - LTTNG_ASSERT(event); - - if (event->fd >= 0) { - const int ret = close(event->fd); - - DBG("Closing kernel event notifier rule file descriptor: fd = %d", - event->fd); - if (ret) { - PERROR("Failed to close kernel event notifier file descriptor: fd = %d", - event->fd); - } - } else { - DBG("Destroying kernel event notifier rule (no associated file descriptor)"); - } - - lttng_trigger_put(event->trigger); - call_rcu(&event->rcu_node, free_token_event_rule_rcu); -} -/* - * Cleanup kernel context structure. - */ -void trace_kernel_destroy_context(struct ltt_kernel_context *ctx) -{ - LTTNG_ASSERT(ctx); - - if (ctx->in_list) { - cds_list_del(&ctx->list); - } - free(ctx); -} - -/* - * Cleanup kernel channel structure. - */ -void trace_kernel_destroy_channel(struct ltt_kernel_channel *channel) -{ - struct ltt_kernel_stream *stream, *stmp; - struct ltt_kernel_event *event, *etmp; - struct ltt_kernel_context *ctx, *ctmp; - int ret; - enum lttng_error_code status; - - LTTNG_ASSERT(channel); - - DBG("[trace] Closing channel fd %d", channel->fd); - /* Close kernel fd */ - if (channel->fd >= 0) { - ret = close(channel->fd); - if (ret) { - PERROR("close"); - } - } - - /* For each stream in the channel list */ - cds_list_for_each_entry_safe(stream, stmp, &channel->stream_list.head, list) { - trace_kernel_destroy_stream(stream); - } - - /* For each event in the channel list */ - cds_list_for_each_entry_safe(event, etmp, &channel->events_list.head, list) { - trace_kernel_destroy_event(event); - } - - /* For each context in the channel list */ - cds_list_for_each_entry_safe(ctx, ctmp, &channel->ctx_list, list) { - trace_kernel_destroy_context(ctx); - } - - /* Remove from channel list */ - cds_list_del(&channel->list); - - if (the_notification_thread_handle && - channel->published_to_notification_thread) { - status = notification_thread_command_remove_channel( - the_notification_thread_handle, channel->key, - LTTNG_DOMAIN_KERNEL); - LTTNG_ASSERT(status == LTTNG_OK); - } - free(channel->channel->attr.extended.ptr); - free(channel->channel); - free(channel); -} - -/* - * Cleanup kernel metadata structure. - */ -void trace_kernel_destroy_metadata(struct ltt_kernel_metadata *metadata) -{ - LTTNG_ASSERT(metadata); - - DBG("[trace] Closing metadata fd %d", metadata->fd); - /* Close kernel fd */ - if (metadata->fd >= 0) { - int ret; - - ret = close(metadata->fd); - if (ret) { - PERROR("close"); - } - } - - free(metadata->conf); - free(metadata); -} - -/* - * Cleanup kernel session structure - * - * Should *NOT* be called with RCU read-side lock held. - */ -void trace_kernel_destroy_session(struct ltt_kernel_session *session) -{ - struct ltt_kernel_channel *channel, *ctmp; - int ret; - - LTTNG_ASSERT(session); - - DBG("[trace] Closing session fd %d", session->fd); - /* Close kernel fds */ - if (session->fd >= 0) { - ret = close(session->fd); - if (ret) { - PERROR("close"); - } - } - - if (session->metadata_stream_fd >= 0) { - DBG("[trace] Closing metadata stream fd %d", session->metadata_stream_fd); - ret = close(session->metadata_stream_fd); - if (ret) { - PERROR("close"); - } - } - - if (session->metadata != NULL) { - trace_kernel_destroy_metadata(session->metadata); - } - - cds_list_for_each_entry_safe(channel, ctmp, &session->channel_list.head, list) { - trace_kernel_destroy_channel(channel); - } -} - -/* Free elements needed by destroy notifiers. */ -void trace_kernel_free_session(struct ltt_kernel_session *session) -{ - /* Wipe consumer output object */ - consumer_output_put(session->consumer); - - process_attr_tracker_destroy(session->tracker_pid); - process_attr_tracker_destroy(session->tracker_vpid); - process_attr_tracker_destroy(session->tracker_uid); - process_attr_tracker_destroy(session->tracker_vuid); - process_attr_tracker_destroy(session->tracker_gid); - process_attr_tracker_destroy(session->tracker_vgid); - - free(session); -} diff --git a/src/bin/lttng-sessiond/trace-kernel.cpp b/src/bin/lttng-sessiond/trace-kernel.cpp new file mode 100644 index 000000000..cf1455ee6 --- /dev/null +++ b/src/bin/lttng-sessiond/trace-kernel.cpp @@ -0,0 +1,1052 @@ +/* + * Copyright (C) 2011 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "consumer.h" +#include "trace-kernel.h" +#include "lttng-sessiond.h" +#include "notification-thread-commands.h" + +/* + * Find the channel name for the given kernel session. + */ +struct ltt_kernel_channel *trace_kernel_get_channel_by_name( + const char *name, struct ltt_kernel_session *session) +{ + struct ltt_kernel_channel *chan; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(name); + + /* + * If we receive an empty string for channel name, it means the + * default channel name is requested. + */ + if (name[0] == '\0') + name = DEFAULT_CHANNEL_NAME; + + DBG("Trying to find channel %s", name); + + cds_list_for_each_entry(chan, &session->channel_list.head, list) { + if (strcmp(name, chan->channel->name) == 0) { + DBG("Found channel by name %s", name); + return chan; + } + } + + return NULL; +} + +/* + * Find the event for the given channel. + */ +struct ltt_kernel_event *trace_kernel_find_event( + char *name, struct ltt_kernel_channel *channel, + enum lttng_event_type type, + struct lttng_bytecode *filter) +{ + struct ltt_kernel_event *ev; + int found = 0; + + LTTNG_ASSERT(name); + LTTNG_ASSERT(channel); + + cds_list_for_each_entry(ev, &channel->events_list.head, list) { + if (type != LTTNG_EVENT_ALL && ev->type != type) { + continue; + } + if (strcmp(name, ev->event->name)) { + continue; + } + if ((ev->filter && !filter) || (!ev->filter && filter)) { + continue; + } + if (ev->filter && filter) { + if (ev->filter->len != filter->len || + memcmp(ev->filter->data, filter->data, + filter->len) != 0) { + continue; + } + } + found = 1; + break; + } + if (found) { + DBG("Found event %s for channel %s", name, + channel->channel->name); + return ev; + } else { + return NULL; + } +} + +/* + * Find the event name for the given channel. + */ +struct ltt_kernel_event *trace_kernel_get_event_by_name( + char *name, struct ltt_kernel_channel *channel, + enum lttng_event_type type) +{ + struct ltt_kernel_event *ev; + int found = 0; + + LTTNG_ASSERT(name); + LTTNG_ASSERT(channel); + + cds_list_for_each_entry(ev, &channel->events_list.head, list) { + if (type != LTTNG_EVENT_ALL && ev->type != type) { + continue; + } + if (strcmp(name, ev->event->name)) { + continue; + } + found = 1; + break; + } + if (found) { + DBG("Found event %s for channel %s", name, + channel->channel->name); + return ev; + } else { + return NULL; + } +} + +/* + * Allocate and initialize a kernel session data structure. + * + * Return pointer to structure or NULL. + */ +struct ltt_kernel_session *trace_kernel_create_session(void) +{ + struct ltt_kernel_session *lks = NULL; + + /* Allocate a new ltt kernel session */ + lks = (ltt_kernel_session *) zmalloc(sizeof(struct ltt_kernel_session)); + if (lks == NULL) { + PERROR("create kernel session zmalloc"); + goto alloc_error; + } + + /* Init data structure */ + lks->fd = -1; + lks->metadata_stream_fd = -1; + lks->channel_count = 0; + lks->stream_count_global = 0; + lks->metadata = NULL; + CDS_INIT_LIST_HEAD(&lks->channel_list.head); + + lks->tracker_pid = process_attr_tracker_create(); + if (!lks->tracker_pid) { + goto error; + } + lks->tracker_vpid = process_attr_tracker_create(); + if (!lks->tracker_vpid) { + goto error; + } + lks->tracker_uid = process_attr_tracker_create(); + if (!lks->tracker_uid) { + goto error; + } + lks->tracker_vuid = process_attr_tracker_create(); + if (!lks->tracker_vuid) { + goto error; + } + lks->tracker_gid = process_attr_tracker_create(); + if (!lks->tracker_gid) { + goto error; + } + lks->tracker_vgid = process_attr_tracker_create(); + if (!lks->tracker_vgid) { + goto error; + } + lks->consumer = consumer_create_output(CONSUMER_DST_LOCAL); + if (lks->consumer == NULL) { + goto error; + } + + return lks; + +error: + process_attr_tracker_destroy(lks->tracker_pid); + process_attr_tracker_destroy(lks->tracker_vpid); + process_attr_tracker_destroy(lks->tracker_uid); + process_attr_tracker_destroy(lks->tracker_vuid); + process_attr_tracker_destroy(lks->tracker_gid); + process_attr_tracker_destroy(lks->tracker_vgid); + free(lks); + +alloc_error: + return NULL; +} + +/* + * Allocate and initialize a kernel channel data structure. + * + * Return pointer to structure or NULL. + */ +struct ltt_kernel_channel *trace_kernel_create_channel( + struct lttng_channel *chan) +{ + struct ltt_kernel_channel *lkc; + struct lttng_channel_extended *extended = NULL; + + LTTNG_ASSERT(chan); + + lkc = (ltt_kernel_channel *) zmalloc(sizeof(struct ltt_kernel_channel)); + if (lkc == NULL) { + PERROR("ltt_kernel_channel zmalloc"); + goto error; + } + + lkc->channel = (lttng_channel *) zmalloc(sizeof(struct lttng_channel)); + if (lkc->channel == NULL) { + PERROR("lttng_channel zmalloc"); + goto error; + } + + extended = (lttng_channel_extended *) zmalloc(sizeof(struct lttng_channel_extended)); + if (!extended) { + PERROR("lttng_channel_channel zmalloc"); + goto error; + } + memcpy(lkc->channel, chan, sizeof(struct lttng_channel)); + memcpy(extended, chan->attr.extended.ptr, sizeof(struct lttng_channel_extended)); + lkc->channel->attr.extended.ptr = extended; + extended = NULL; + + /* + * If we receive an empty string for channel name, it means the + * default channel name is requested. + */ + if (chan->name[0] == '\0') { + strncpy(lkc->channel->name, DEFAULT_CHANNEL_NAME, + sizeof(lkc->channel->name)); + } + lkc->channel->name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; + + lkc->fd = -1; + lkc->stream_count = 0; + lkc->event_count = 0; + lkc->enabled = 1; + lkc->published_to_notification_thread = false; + /* Init linked list */ + CDS_INIT_LIST_HEAD(&lkc->events_list.head); + CDS_INIT_LIST_HEAD(&lkc->stream_list.head); + CDS_INIT_LIST_HEAD(&lkc->ctx_list); + + return lkc; + +error: + if (lkc) { + free(lkc->channel); + } + free(extended); + free(lkc); + return NULL; +} + +/* + * Allocate and init a kernel context object. + * + * Return the allocated object or NULL on error. + */ +struct ltt_kernel_context *trace_kernel_create_context( + struct lttng_kernel_abi_context *ctx) +{ + struct ltt_kernel_context *kctx; + + kctx = (ltt_kernel_context *) zmalloc(sizeof(*kctx)); + if (!kctx) { + PERROR("zmalloc kernel context"); + goto error; + } + + if (ctx) { + memcpy(&kctx->ctx, ctx, sizeof(kctx->ctx)); + } +error: + return kctx; +} + +/* + * Allocate and init a kernel context object from an existing kernel context + * object. + * + * Return the allocated object or NULL on error. + */ +struct ltt_kernel_context *trace_kernel_copy_context( + struct ltt_kernel_context *kctx) +{ + struct ltt_kernel_context *kctx_copy; + + LTTNG_ASSERT(kctx); + kctx_copy = (ltt_kernel_context *) zmalloc(sizeof(*kctx_copy)); + if (!kctx_copy) { + PERROR("zmalloc ltt_kernel_context"); + goto error; + } + + memcpy(kctx_copy, kctx, sizeof(*kctx_copy)); + memset(&kctx_copy->list, 0, sizeof(kctx_copy->list)); + +error: + return kctx_copy; +} + +/* + * Allocate and initialize a kernel event. Set name and event type. + * We own filter_expression, and filter. + * + * Return pointer to structure or NULL. + */ +enum lttng_error_code trace_kernel_create_event( + struct lttng_event *ev, char *filter_expression, + struct lttng_bytecode *filter, + struct ltt_kernel_event **kernel_event) +{ + enum lttng_error_code ret; + struct lttng_kernel_abi_event *attr; + struct ltt_kernel_event *local_kernel_event; + struct lttng_userspace_probe_location *userspace_probe_location = NULL; + + LTTNG_ASSERT(ev); + + local_kernel_event = (ltt_kernel_event *) zmalloc(sizeof(struct ltt_kernel_event)); + attr = (lttng_kernel_abi_event *) zmalloc(sizeof(struct lttng_kernel_abi_event)); + if (local_kernel_event == NULL || attr == NULL) { + PERROR("kernel event zmalloc"); + ret = LTTNG_ERR_NOMEM; + goto error; + } + + switch (ev->type) { + case LTTNG_EVENT_PROBE: + attr->instrumentation = LTTNG_KERNEL_ABI_KPROBE; + attr->u.kprobe.addr = ev->attr.probe.addr; + attr->u.kprobe.offset = ev->attr.probe.offset; + strncpy(attr->u.kprobe.symbol_name, + ev->attr.probe.symbol_name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); + attr->u.kprobe.symbol_name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; + break; + case LTTNG_EVENT_USERSPACE_PROBE: + { + const struct lttng_userspace_probe_location* location = NULL; + const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; + + location = lttng_event_get_userspace_probe_location(ev); + if (!location) { + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + + /* + * From this point on, the specific term 'uprobe' is used + * instead of the generic 'userspace probe' because it's the + * technology used at the moment for this instrumentation. + * LTTng currently implements userspace probes using uprobes. + * In the interactions with the kernel tracer, we use the + * uprobe term. + */ + attr->instrumentation = LTTNG_KERNEL_ABI_UPROBE; + + 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. */ + attr->u.uprobe.fd = + lttng_userspace_probe_location_function_get_binary_fd(location); + + /* + * Save a reference to the probe location used during + * the listing of events. + */ + userspace_probe_location = + lttng_userspace_probe_location_copy(location); + break; + case LTTNG_USERSPACE_PROBE_LOCATION_LOOKUP_METHOD_TYPE_TRACEPOINT_SDT: + /* Get the file descriptor on the target binary. */ + attr->u.uprobe.fd = + lttng_userspace_probe_location_tracepoint_get_binary_fd(location); + + /* + * Save a reference to the probe location used during the listing of + * events. + */ + userspace_probe_location = + lttng_userspace_probe_location_copy(location); + break; + default: + DBG("Unsupported lookup method type"); + ret = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + break; + } + case LTTNG_EVENT_FUNCTION: + attr->instrumentation = LTTNG_KERNEL_ABI_KRETPROBE; + attr->u.kretprobe.addr = ev->attr.probe.addr; + attr->u.kretprobe.offset = ev->attr.probe.offset; + strncpy(attr->u.kretprobe.symbol_name, + ev->attr.probe.symbol_name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); + attr->u.kretprobe.symbol_name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; + break; + case LTTNG_EVENT_FUNCTION_ENTRY: + attr->instrumentation = LTTNG_KERNEL_ABI_FUNCTION; + strncpy(attr->u.ftrace.symbol_name, + ev->attr.ftrace.symbol_name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); + attr->u.ftrace.symbol_name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; + break; + case LTTNG_EVENT_TRACEPOINT: + attr->instrumentation = LTTNG_KERNEL_ABI_TRACEPOINT; + break; + case LTTNG_EVENT_SYSCALL: + attr->instrumentation = LTTNG_KERNEL_ABI_SYSCALL; + attr->u.syscall.abi = LTTNG_KERNEL_ABI_SYSCALL_ABI_ALL; + attr->u.syscall.entryexit = LTTNG_KERNEL_ABI_SYSCALL_ENTRYEXIT; + attr->u.syscall.match = LTTNG_KERNEL_ABI_SYSCALL_MATCH_NAME; + break; + case LTTNG_EVENT_ALL: + attr->instrumentation = LTTNG_KERNEL_ABI_ALL; + break; + default: + ERR("Unknown kernel instrumentation type (%d)", ev->type); + ret = LTTNG_ERR_INVALID; + goto error; + } + + /* Copy event name */ + strncpy(attr->name, ev->name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); + attr->name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; + + /* Setting up a kernel event */ + local_kernel_event->fd = -1; + local_kernel_event->event = attr; + local_kernel_event->enabled = 1; + local_kernel_event->filter_expression = filter_expression; + local_kernel_event->filter = filter; + local_kernel_event->userspace_probe_location = userspace_probe_location; + + *kernel_event = local_kernel_event; + + return LTTNG_OK; + +error: + free(filter_expression); + free(filter); + free(local_kernel_event); + free(attr); + return ret; +} + +/* + * Allocate and initialize a kernel token event rule. + * + * Return pointer to structure or NULL. + */ +enum lttng_error_code trace_kernel_create_event_notifier_rule( + struct lttng_trigger *trigger, + uint64_t token, + uint64_t error_counter_index, + struct ltt_kernel_event_notifier_rule **event_notifier_rule) +{ + enum lttng_error_code ret = LTTNG_OK; + enum lttng_condition_type condition_type; + enum lttng_event_rule_type event_rule_type; + enum lttng_condition_status condition_status; + struct ltt_kernel_event_notifier_rule *local_kernel_token_event_rule; + const struct lttng_condition *condition = NULL; + const struct lttng_event_rule *event_rule = NULL; + + LTTNG_ASSERT(event_notifier_rule); + + condition = lttng_trigger_get_const_condition(trigger); + LTTNG_ASSERT(condition); + + condition_type = lttng_condition_get_type(condition); + LTTNG_ASSERT(condition_type == LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); + + condition_status = lttng_condition_event_rule_matches_get_rule( + condition, &event_rule); + LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); + LTTNG_ASSERT(event_rule); + + event_rule_type = lttng_event_rule_get_type(event_rule); + LTTNG_ASSERT(event_rule_type != LTTNG_EVENT_RULE_TYPE_UNKNOWN); + + local_kernel_token_event_rule = + (ltt_kernel_event_notifier_rule *) zmalloc(sizeof(struct ltt_kernel_event_notifier_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; + local_kernel_token_event_rule->error_counter_index = error_counter_index; + + /* Get the reference of the event rule. */ + lttng_trigger_get(trigger); + + local_kernel_token_event_rule->trigger = trigger; + /* The event rule still owns the filter and bytecode. */ + local_kernel_token_event_rule->filter = + lttng_event_rule_get_filter_bytecode(event_rule); + + DBG3("Created kernel event notifier rule: token = %" PRIu64, + local_kernel_token_event_rule->token); +error: + *event_notifier_rule = local_kernel_token_event_rule; + return ret; +} + +/* + * Initialize a kernel trigger from an event rule. + */ +enum lttng_error_code trace_kernel_init_event_notifier_from_event_rule( + const struct lttng_event_rule *rule, + struct lttng_kernel_abi_event_notifier *kernel_event_notifier) +{ + enum lttng_error_code ret_code; + const char *name; + int strncpy_ret; + + switch (lttng_event_rule_get_type(rule)) { + case LTTNG_EVENT_RULE_TYPE_KERNEL_KPROBE: + { + uint64_t address = 0, offset = 0; + const char *symbol_name = NULL; + const struct lttng_kernel_probe_location *location = NULL; + enum lttng_kernel_probe_location_status k_status; + enum lttng_event_rule_status status; + + status = lttng_event_rule_kernel_kprobe_get_location(rule, &location); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret_code = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + + switch (lttng_kernel_probe_location_get_type(location)) { + case LTTNG_KERNEL_PROBE_LOCATION_TYPE_ADDRESS: + { + k_status = lttng_kernel_probe_location_address_get_address( + location, &address); + LTTNG_ASSERT(k_status == LTTNG_KERNEL_PROBE_LOCATION_STATUS_OK); + break; + } + case LTTNG_KERNEL_PROBE_LOCATION_TYPE_SYMBOL_OFFSET: + { + k_status = lttng_kernel_probe_location_symbol_get_offset( + location, &offset); + LTTNG_ASSERT(k_status == LTTNG_KERNEL_PROBE_LOCATION_STATUS_OK); + symbol_name = lttng_kernel_probe_location_symbol_get_name( + location); + break; + } + default: + abort(); + } + + kernel_event_notifier->event.instrumentation = LTTNG_KERNEL_ABI_KPROBE; + kernel_event_notifier->event.u.kprobe.addr = address; + kernel_event_notifier->event.u.kprobe.offset = offset; + if (symbol_name) { + strncpy_ret = lttng_strncpy( + kernel_event_notifier->event.u.kprobe.symbol_name, + symbol_name, LTTNG_KERNEL_ABI_SYM_NAME_LEN); + + if (strncpy_ret) { + ret_code = LTTNG_ERR_INVALID; + goto error; + } + } + + kernel_event_notifier->event.u.kprobe.symbol_name[LTTNG_KERNEL_ABI_SYM_NAME_LEN - 1] = '\0'; + + status = lttng_event_rule_kernel_kprobe_get_event_name(rule, &name); + LTTNG_ASSERT(status == LTTNG_EVENT_RULE_STATUS_OK); + ret_code = LTTNG_OK; + break; + } + case LTTNG_EVENT_RULE_TYPE_KERNEL_UPROBE: + { + const struct lttng_userspace_probe_location* location = NULL; + const struct lttng_userspace_probe_location_lookup_method *lookup = NULL; + enum lttng_event_rule_status status; + + status = lttng_event_rule_kernel_uprobe_get_location(rule, &location); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + ret_code = LTTNG_ERR_PROBE_LOCATION_INVAL; + goto error; + } + + kernel_event_notifier->event.instrumentation = LTTNG_KERNEL_ABI_UPROBE; + + lookup = lttng_userspace_probe_location_get_lookup_method( + location); + if (!lookup) { + ret_code = 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_event_notifier->event.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_event_notifier->event.u.uprobe.fd = + lttng_userspace_probe_location_tracepoint_get_binary_fd(location); + break; + default: + abort(); + } + + status = lttng_event_rule_kernel_uprobe_get_event_name( + rule, &name); + LTTNG_ASSERT(status == LTTNG_EVENT_RULE_STATUS_OK); + ret_code = LTTNG_OK; + break; + } + case LTTNG_EVENT_RULE_TYPE_KERNEL_TRACEPOINT: + { + const enum lttng_event_rule_status status = + lttng_event_rule_kernel_tracepoint_get_name_pattern( + rule, &name); + + LTTNG_ASSERT(status == LTTNG_EVENT_RULE_STATUS_OK); + kernel_event_notifier->event.instrumentation = + LTTNG_KERNEL_ABI_TRACEPOINT; + + ret_code = LTTNG_OK; + break; + } + case LTTNG_EVENT_RULE_TYPE_KERNEL_SYSCALL: + { + const enum lttng_event_rule_status status = + lttng_event_rule_kernel_syscall_get_name_pattern( + rule, &name); + const enum lttng_event_rule_kernel_syscall_emission_site + emission_site = + lttng_event_rule_kernel_syscall_get_emission_site(rule); + enum lttng_kernel_abi_syscall_entryexit entryexit; + + LTTNG_ASSERT(status == LTTNG_EVENT_RULE_STATUS_OK); + LTTNG_ASSERT(emission_site != LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_UNKNOWN); + + switch(emission_site) { + case LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_ENTRY: + entryexit = LTTNG_KERNEL_ABI_SYSCALL_ENTRY; + break; + case LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_EXIT: + entryexit = LTTNG_KERNEL_ABI_SYSCALL_EXIT; + break; + case LTTNG_EVENT_RULE_KERNEL_SYSCALL_EMISSION_SITE_ENTRY_EXIT: + entryexit = LTTNG_KERNEL_ABI_SYSCALL_ENTRYEXIT; + break; + default: + abort(); + break; + } + + kernel_event_notifier->event.instrumentation = + LTTNG_KERNEL_ABI_SYSCALL; + kernel_event_notifier->event.u.syscall.abi = + LTTNG_KERNEL_ABI_SYSCALL_ABI_ALL; + kernel_event_notifier->event.u.syscall.entryexit = + entryexit; + kernel_event_notifier->event.u.syscall.match = + LTTNG_KERNEL_ABI_SYSCALL_MATCH_NAME; + ret_code = LTTNG_OK; + break; + } + default: + abort(); + break; + } + + strncpy_ret = lttng_strncpy(kernel_event_notifier->event.name, name, + LTTNG_KERNEL_ABI_SYM_NAME_LEN); + if (strncpy_ret) { + ret_code = LTTNG_ERR_INVALID; + goto error; + } + +error: + return ret_code; +} +/* + * Allocate and initialize a kernel metadata. + * + * Return pointer to structure or NULL. + */ +struct ltt_kernel_metadata *trace_kernel_create_metadata(void) +{ + int ret; + struct ltt_kernel_metadata *lkm; + struct lttng_channel *chan; + + lkm = (ltt_kernel_metadata *) zmalloc(sizeof(struct ltt_kernel_metadata)); + chan = (lttng_channel *) zmalloc(sizeof(struct lttng_channel)); + if (lkm == NULL || chan == NULL) { + PERROR("kernel metadata zmalloc"); + goto error; + } + + ret = lttng_strncpy( + chan->name, DEFAULT_METADATA_NAME, sizeof(chan->name)); + if (ret) { + ERR("Failed to initialize metadata channel name to `%s`", + DEFAULT_METADATA_NAME); + goto error; + } + + /* Set default attributes */ + chan->attr.overwrite = DEFAULT_METADATA_OVERWRITE; + chan->attr.subbuf_size = default_get_metadata_subbuf_size(); + chan->attr.num_subbuf = DEFAULT_METADATA_SUBBUF_NUM; + chan->attr.switch_timer_interval = DEFAULT_METADATA_SWITCH_TIMER; + chan->attr.read_timer_interval = DEFAULT_METADATA_READ_TIMER;; + + + /* + * The metadata channel of kernel sessions must use the "mmap" + * back-end since the consumer daemon accumulates complete + * metadata units before sending them to the relay daemon in + * live mode. The consumer daemon also needs to extract the contents + * of the metadata cache when computing a rotation position. + * + * In both cases, it is not possible to rely on the splice + * back-end as the consumer daemon may need to accumulate more + * content than can be backed by the ring buffer's underlying + * pages. + */ + chan->attr.output = LTTNG_EVENT_MMAP; + chan->attr.tracefile_size = 0; + chan->attr.tracefile_count = 0; + chan->attr.live_timer_interval = 0; + + /* Init metadata */ + lkm->fd = -1; + lkm->conf = chan; + + return lkm; + +error: + free(lkm); + free(chan); + return NULL; +} + +/* + * Allocate and initialize a kernel stream. The stream is set to ACTIVE_FD by + * default. + * + * Return pointer to structure or NULL. + */ +struct ltt_kernel_stream *trace_kernel_create_stream(const char *name, + unsigned int count) +{ + int ret; + struct ltt_kernel_stream *lks; + + LTTNG_ASSERT(name); + + lks = (ltt_kernel_stream *) zmalloc(sizeof(struct ltt_kernel_stream)); + if (lks == NULL) { + PERROR("kernel stream zmalloc"); + goto error; + } + + /* Set name */ + ret = snprintf(lks->name, sizeof(lks->name), "%s_%u", name, count); + if (ret < 0) { + PERROR("snprintf stream name"); + goto error; + } + lks->name[sizeof(lks->name) - 1] = '\0'; + + /* Init stream */ + lks->fd = -1; + lks->state = 0; + lks->cpu = count; + + return lks; + +error: + return NULL; +} + +/* + * Cleanup kernel stream structure. + */ +void trace_kernel_destroy_stream(struct ltt_kernel_stream *stream) +{ + LTTNG_ASSERT(stream); + + DBG("[trace] Closing stream fd %d", stream->fd); + /* Close kernel fd */ + if (stream->fd >= 0) { + int ret; + + ret = close(stream->fd); + if (ret) { + PERROR("close"); + } + } + /* Remove from stream list */ + cds_list_del(&stream->list); + + free(stream); +} + +/* + * Cleanup kernel event structure. + */ +void trace_kernel_destroy_event(struct ltt_kernel_event *event) +{ + LTTNG_ASSERT(event); + + if (event->fd >= 0) { + int ret; + + DBG("[trace] Closing event fd %d", event->fd); + /* Close kernel fd */ + ret = close(event->fd); + if (ret) { + PERROR("close"); + } + } else { + DBG("[trace] Tearing down event (no associated file descriptor)"); + } + + /* Remove from event list */ + cds_list_del(&event->list); + + free(event->filter_expression); + free(event->filter); + + free(event->event); + free(event); +} + +/* + * Cleanup kernel event structure. + */ +static void free_token_event_rule_rcu(struct rcu_head *rcu_node) +{ + struct ltt_kernel_event_notifier_rule *rule = caa_container_of(rcu_node, + struct ltt_kernel_event_notifier_rule, rcu_node); + + free(rule); +} + +void trace_kernel_destroy_event_notifier_rule( + struct ltt_kernel_event_notifier_rule *event) +{ + LTTNG_ASSERT(event); + + if (event->fd >= 0) { + const int ret = close(event->fd); + + DBG("Closing kernel event notifier rule file descriptor: fd = %d", + event->fd); + if (ret) { + PERROR("Failed to close kernel event notifier file descriptor: fd = %d", + event->fd); + } + } else { + DBG("Destroying kernel event notifier rule (no associated file descriptor)"); + } + + lttng_trigger_put(event->trigger); + call_rcu(&event->rcu_node, free_token_event_rule_rcu); +} +/* + * Cleanup kernel context structure. + */ +void trace_kernel_destroy_context(struct ltt_kernel_context *ctx) +{ + LTTNG_ASSERT(ctx); + + if (ctx->in_list) { + cds_list_del(&ctx->list); + } + free(ctx); +} + +/* + * Cleanup kernel channel structure. + */ +void trace_kernel_destroy_channel(struct ltt_kernel_channel *channel) +{ + struct ltt_kernel_stream *stream, *stmp; + struct ltt_kernel_event *event, *etmp; + struct ltt_kernel_context *ctx, *ctmp; + int ret; + enum lttng_error_code status; + + LTTNG_ASSERT(channel); + + DBG("[trace] Closing channel fd %d", channel->fd); + /* Close kernel fd */ + if (channel->fd >= 0) { + ret = close(channel->fd); + if (ret) { + PERROR("close"); + } + } + + /* For each stream in the channel list */ + cds_list_for_each_entry_safe(stream, stmp, &channel->stream_list.head, list) { + trace_kernel_destroy_stream(stream); + } + + /* For each event in the channel list */ + cds_list_for_each_entry_safe(event, etmp, &channel->events_list.head, list) { + trace_kernel_destroy_event(event); + } + + /* For each context in the channel list */ + cds_list_for_each_entry_safe(ctx, ctmp, &channel->ctx_list, list) { + trace_kernel_destroy_context(ctx); + } + + /* Remove from channel list */ + cds_list_del(&channel->list); + + if (the_notification_thread_handle && + channel->published_to_notification_thread) { + status = notification_thread_command_remove_channel( + the_notification_thread_handle, channel->key, + LTTNG_DOMAIN_KERNEL); + LTTNG_ASSERT(status == LTTNG_OK); + } + free(channel->channel->attr.extended.ptr); + free(channel->channel); + free(channel); +} + +/* + * Cleanup kernel metadata structure. + */ +void trace_kernel_destroy_metadata(struct ltt_kernel_metadata *metadata) +{ + LTTNG_ASSERT(metadata); + + DBG("[trace] Closing metadata fd %d", metadata->fd); + /* Close kernel fd */ + if (metadata->fd >= 0) { + int ret; + + ret = close(metadata->fd); + if (ret) { + PERROR("close"); + } + } + + free(metadata->conf); + free(metadata); +} + +/* + * Cleanup kernel session structure + * + * Should *NOT* be called with RCU read-side lock held. + */ +void trace_kernel_destroy_session(struct ltt_kernel_session *session) +{ + struct ltt_kernel_channel *channel, *ctmp; + int ret; + + LTTNG_ASSERT(session); + + DBG("[trace] Closing session fd %d", session->fd); + /* Close kernel fds */ + if (session->fd >= 0) { + ret = close(session->fd); + if (ret) { + PERROR("close"); + } + } + + if (session->metadata_stream_fd >= 0) { + DBG("[trace] Closing metadata stream fd %d", session->metadata_stream_fd); + ret = close(session->metadata_stream_fd); + if (ret) { + PERROR("close"); + } + } + + if (session->metadata != NULL) { + trace_kernel_destroy_metadata(session->metadata); + } + + cds_list_for_each_entry_safe(channel, ctmp, &session->channel_list.head, list) { + trace_kernel_destroy_channel(channel); + } +} + +/* Free elements needed by destroy notifiers. */ +void trace_kernel_free_session(struct ltt_kernel_session *session) +{ + /* Wipe consumer output object */ + consumer_output_put(session->consumer); + + process_attr_tracker_destroy(session->tracker_pid); + process_attr_tracker_destroy(session->tracker_vpid); + process_attr_tracker_destroy(session->tracker_uid); + process_attr_tracker_destroy(session->tracker_vuid); + process_attr_tracker_destroy(session->tracker_gid); + process_attr_tracker_destroy(session->tracker_vgid); + + free(session); +} diff --git a/src/bin/lttng-sessiond/trace-ust.c b/src/bin/lttng-sessiond/trace-ust.c deleted file mode 100644 index 47e98cc78..000000000 --- a/src/bin/lttng-sessiond/trace-ust.c +++ /dev/null @@ -1,1440 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2016 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "buffer-registry.h" -#include "trace-ust.h" -#include "utils.h" -#include "ust-app.h" -#include "agent.h" - -/* - * Match function for the events hash table lookup. - * - * Matches by name only. Used by the disable command. - */ -int trace_ust_ht_match_event_by_name(struct cds_lfht_node *node, - const void *_key) -{ - struct ltt_ust_event *event; - const char *name; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - event = caa_container_of(node, struct ltt_ust_event, node.node); - name = _key; - - /* Event name */ - if (strncmp(event->attr.name, name, sizeof(event->attr.name)) != 0) { - goto no_match; - } - - /* Match */ - return 1; - -no_match: - return 0; -} - -/* - * Match function for the hash table lookup. - * - * It matches an ust event based on three attributes which are the event name, - * the filter bytecode and the loglevel. - */ -int trace_ust_ht_match_event(struct cds_lfht_node *node, const void *_key) -{ - struct ltt_ust_event *event; - const struct ltt_ust_ht_key *key; - int ev_loglevel_value; - int ll_match; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - event = caa_container_of(node, struct ltt_ust_event, node.node); - key = _key; - ev_loglevel_value = event->attr.loglevel; - - /* Match the 4 elements of the key: name, filter, loglevel, exclusions. */ - - /* Event name */ - if (strncmp(event->attr.name, key->name, sizeof(event->attr.name)) != 0) { - goto no_match; - } - - /* Event loglevel value and type. */ - ll_match = loglevels_match(event->attr.loglevel_type, - ev_loglevel_value, key->loglevel_type, - key->loglevel_value, LTTNG_UST_ABI_LOGLEVEL_ALL); - - if (!ll_match) { - goto no_match; - } - - /* Only one of the filters is NULL, fail. */ - if ((key->filter && !event->filter) || (!key->filter && event->filter)) { - goto no_match; - } - - if (key->filter && event->filter) { - /* Both filters exists, check length followed by the bytecode. */ - if (event->filter->len != key->filter->len || - memcmp(event->filter->data, key->filter->data, - event->filter->len) != 0) { - goto no_match; - } - } - - /* If only one of the exclusions is NULL, fail. */ - if ((key->exclusion && !event->exclusion) || (!key->exclusion && event->exclusion)) { - goto no_match; - } - - if (key->exclusion && event->exclusion) { - size_t i; - - /* Check exclusion counts first. */ - if (event->exclusion->count != key->exclusion->count) { - goto no_match; - } - - /* Compare names individually. */ - for (i = 0; i < event->exclusion->count; ++i) { - size_t j; - bool found = false; - const char *name_ev = - LTTNG_EVENT_EXCLUSION_NAME_AT( - event->exclusion, i); - - /* - * Compare this exclusion name to all the key's - * exclusion names. - */ - for (j = 0; j < key->exclusion->count; ++j) { - const char *name_key = - LTTNG_EVENT_EXCLUSION_NAME_AT( - key->exclusion, j); - - if (!strncmp(name_ev, name_key, - LTTNG_SYMBOL_NAME_LEN)) { - /* Names match! */ - found = true; - break; - } - } - - /* - * If the current exclusion name was not found amongst - * the key's exclusion names, then there's no match. - */ - if (!found) { - goto no_match; - } - } - } - /* Match. */ - return 1; - -no_match: - return 0; -} - -/* - * Find the channel in the hashtable and return channel pointer. RCU read side - * lock MUST be acquired before calling this. - */ -struct ltt_ust_channel *trace_ust_find_channel_by_name(struct lttng_ht *ht, - const char *name) -{ - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - - /* - * If we receive an empty string for channel name, it means the - * default channel name is requested. - */ - if (name[0] == '\0') - name = DEFAULT_CHANNEL_NAME; - - lttng_ht_lookup(ht, (void *)name, &iter); - node = lttng_ht_iter_get_node_str(&iter); - if (node == NULL) { - goto error; - } - - DBG2("Trace UST channel %s found by name", name); - - return caa_container_of(node, struct ltt_ust_channel, node); - -error: - DBG2("Trace UST channel %s not found by name", name); - return NULL; -} - -/* - * Find the event in the hashtable and return event pointer. RCU read side lock - * MUST be acquired before calling this. - */ -struct ltt_ust_event *trace_ust_find_event(struct lttng_ht *ht, - char *name, struct lttng_bytecode *filter, - enum lttng_ust_abi_loglevel_type loglevel_type, int loglevel_value, - struct lttng_event_exclusion *exclusion) -{ - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - struct ltt_ust_ht_key key; - - LTTNG_ASSERT(name); - LTTNG_ASSERT(ht); - - key.name = name; - key.filter = filter; - key.loglevel_type = loglevel_type; - key.loglevel_value = loglevel_value; - key.exclusion = exclusion; - - cds_lfht_lookup(ht->ht, ht->hash_fct((void *) name, lttng_ht_seed), - trace_ust_ht_match_event, &key, &iter.iter); - node = lttng_ht_iter_get_node_str(&iter); - if (node == NULL) { - goto error; - } - - DBG2("Trace UST event %s found", key.name); - - return caa_container_of(node, struct ltt_ust_event, node); - -error: - DBG2("Trace UST event %s NOT found", key.name); - return NULL; -} - -/* - * Lookup an agent in the session agents hash table by domain type and return - * the object if found else NULL. - * - * RCU read side lock must be acquired before calling and only released - * once the agent is no longer in scope or being used. - */ -struct agent *trace_ust_find_agent(struct ltt_ust_session *session, - enum lttng_domain_type domain_type) -{ - struct agent *agt = NULL; - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - uint64_t key; - - LTTNG_ASSERT(session); - - DBG3("Trace ust agent lookup for domain %d", domain_type); - - key = domain_type; - - lttng_ht_lookup(session->agents, &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; -} - -/* - * Allocate and initialize a ust session data structure. - * - * Return pointer to structure or NULL. - */ -struct ltt_ust_session *trace_ust_create_session(uint64_t session_id) -{ - struct ltt_ust_session *lus; - - /* Allocate a new ltt ust session */ - lus = zmalloc(sizeof(struct ltt_ust_session)); - if (lus == NULL) { - PERROR("create ust session zmalloc"); - goto error_alloc; - } - - /* Init data structure */ - lus->id = session_id; - lus->active = 0; - - /* Set default metadata channel attribute. */ - lus->metadata_attr.overwrite = DEFAULT_CHANNEL_OVERWRITE; - lus->metadata_attr.subbuf_size = default_get_metadata_subbuf_size(); - lus->metadata_attr.num_subbuf = DEFAULT_METADATA_SUBBUF_NUM; - lus->metadata_attr.switch_timer_interval = DEFAULT_METADATA_SWITCH_TIMER; - lus->metadata_attr.read_timer_interval = DEFAULT_METADATA_READ_TIMER; - lus->metadata_attr.output = LTTNG_UST_ABI_MMAP; - - /* - * Default buffer type. This can be changed through an enable channel - * requesting a different type. Note that this can only be changed once - * during the session lifetime which is at the first enable channel and - * only before start. The flag buffer_type_changed indicates the status. - */ - lus->buffer_type = LTTNG_BUFFER_PER_UID; - /* Once set to 1, the buffer_type is immutable for the session. */ - lus->buffer_type_changed = 0; - /* Init it in case it get used after allocation. */ - CDS_INIT_LIST_HEAD(&lus->buffer_reg_uid_list); - - /* Alloc UST global domain channels' HT */ - lus->domain_global.channels = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); - /* Alloc agent hash table. */ - lus->agents = lttng_ht_new(0, LTTNG_HT_TYPE_U64); - - lus->tracker_vpid = process_attr_tracker_create(); - if (!lus->tracker_vpid) { - goto error; - } - lus->tracker_vuid = process_attr_tracker_create(); - if (!lus->tracker_vuid) { - goto error; - } - lus->tracker_vgid = process_attr_tracker_create(); - if (!lus->tracker_vgid) { - goto error; - } - lus->consumer = consumer_create_output(CONSUMER_DST_LOCAL); - if (lus->consumer == NULL) { - goto error; - } - - DBG2("UST trace session create successful"); - - return lus; - -error: - process_attr_tracker_destroy(lus->tracker_vpid); - process_attr_tracker_destroy(lus->tracker_vuid); - process_attr_tracker_destroy(lus->tracker_vgid); - ht_cleanup_push(lus->domain_global.channels); - ht_cleanup_push(lus->agents); - free(lus); -error_alloc: - return NULL; -} - -/* - * Allocate and initialize a ust channel data structure. - * - * Return pointer to structure or NULL. - */ -struct ltt_ust_channel *trace_ust_create_channel(struct lttng_channel *chan, - enum lttng_domain_type domain) -{ - struct ltt_ust_channel *luc; - - LTTNG_ASSERT(chan); - - luc = zmalloc(sizeof(struct ltt_ust_channel)); - if (luc == NULL) { - PERROR("ltt_ust_channel zmalloc"); - goto error; - } - - luc->domain = domain; - - /* Copy UST channel attributes */ - luc->attr.overwrite = chan->attr.overwrite; - luc->attr.subbuf_size = chan->attr.subbuf_size; - luc->attr.num_subbuf = chan->attr.num_subbuf; - luc->attr.switch_timer_interval = chan->attr.switch_timer_interval; - luc->attr.read_timer_interval = chan->attr.read_timer_interval; - luc->attr.output = (enum lttng_ust_abi_output) chan->attr.output; - luc->monitor_timer_interval = ((struct lttng_channel_extended *) - chan->attr.extended.ptr)->monitor_timer_interval; - luc->attr.u.s.blocking_timeout = ((struct lttng_channel_extended *) - chan->attr.extended.ptr)->blocking_timeout; - - /* Translate to UST output enum */ - switch (luc->attr.output) { - default: - luc->attr.output = LTTNG_UST_ABI_MMAP; - break; - } - - /* - * If we receive an empty string for channel name, it means the - * default channel name is requested. - */ - if (chan->name[0] == '\0') { - strncpy(luc->name, DEFAULT_CHANNEL_NAME, sizeof(luc->name)); - } else { - /* Copy channel name */ - strncpy(luc->name, chan->name, sizeof(luc->name)); - } - luc->name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; - - /* Init node */ - lttng_ht_node_init_str(&luc->node, luc->name); - CDS_INIT_LIST_HEAD(&luc->ctx_list); - - /* Alloc hash tables */ - luc->events = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); - luc->ctx = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); - - /* On-disk circular buffer parameters */ - luc->tracefile_size = chan->attr.tracefile_size; - luc->tracefile_count = chan->attr.tracefile_count; - - DBG2("Trace UST channel %s created", luc->name); - -error: - return luc; -} - -/* - * Validates an exclusion list. - * - * Returns 0 if valid, negative value if invalid. - */ -static int validate_exclusion(struct lttng_event_exclusion *exclusion) -{ - size_t i; - int ret = 0; - - LTTNG_ASSERT(exclusion); - - for (i = 0; i < exclusion->count; ++i) { - size_t j; - const char *name_a = - LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, i); - - for (j = 0; j < i; ++j) { - const char *name_b = - LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, j); - - if (!strncmp(name_a, name_b, LTTNG_SYMBOL_NAME_LEN)) { - /* Match! */ - ret = -1; - goto end; - } - } - } - -end: - return ret; -} - -/* - * Allocate and initialize a ust event. Set name and event type. - * We own filter_expression, filter, and exclusion. - * - * Return an lttng_error_code - */ -enum lttng_error_code trace_ust_create_event(struct lttng_event *ev, - char *filter_expression, - struct lttng_bytecode *filter, - struct lttng_event_exclusion *exclusion, - bool internal_event, - struct ltt_ust_event **ust_event) -{ - struct ltt_ust_event *local_ust_event; - enum lttng_error_code ret = LTTNG_OK; - - LTTNG_ASSERT(ev); - - if (exclusion && validate_exclusion(exclusion)) { - ret = LTTNG_ERR_INVALID; - goto error; - } - - local_ust_event = zmalloc(sizeof(struct ltt_ust_event)); - if (local_ust_event == NULL) { - PERROR("ust event zmalloc"); - ret = LTTNG_ERR_NOMEM; - goto error; - } - - local_ust_event->internal = internal_event; - - switch (ev->type) { - case LTTNG_EVENT_PROBE: - local_ust_event->attr.instrumentation = LTTNG_UST_ABI_PROBE; - break; - case LTTNG_EVENT_FUNCTION: - local_ust_event->attr.instrumentation = LTTNG_UST_ABI_FUNCTION; - break; - case LTTNG_EVENT_FUNCTION_ENTRY: - local_ust_event->attr.instrumentation = LTTNG_UST_ABI_FUNCTION; - break; - case LTTNG_EVENT_TRACEPOINT: - local_ust_event->attr.instrumentation = LTTNG_UST_ABI_TRACEPOINT; - break; - default: - ERR("Unknown ust instrumentation type (%d)", ev->type); - ret = LTTNG_ERR_INVALID; - goto error_free_event; - } - - /* Copy event name */ - strncpy(local_ust_event->attr.name, ev->name, LTTNG_UST_ABI_SYM_NAME_LEN); - local_ust_event->attr.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; - - switch (ev->loglevel_type) { - case LTTNG_EVENT_LOGLEVEL_ALL: - local_ust_event->attr.loglevel_type = LTTNG_UST_ABI_LOGLEVEL_ALL; - local_ust_event->attr.loglevel = -1; /* Force to -1 */ - break; - case LTTNG_EVENT_LOGLEVEL_RANGE: - local_ust_event->attr.loglevel_type = LTTNG_UST_ABI_LOGLEVEL_RANGE; - local_ust_event->attr.loglevel = ev->loglevel; - break; - case LTTNG_EVENT_LOGLEVEL_SINGLE: - local_ust_event->attr.loglevel_type = LTTNG_UST_ABI_LOGLEVEL_SINGLE; - local_ust_event->attr.loglevel = ev->loglevel; - break; - default: - ERR("Unknown ust loglevel type (%d)", ev->loglevel_type); - ret = LTTNG_ERR_INVALID; - goto error_free_event; - } - - /* Same layout. */ - local_ust_event->filter_expression = filter_expression; - local_ust_event->filter = filter; - local_ust_event->exclusion = exclusion; - - /* Init node */ - lttng_ht_node_init_str(&local_ust_event->node, local_ust_event->attr.name); - - DBG2("Trace UST event %s, loglevel (%d,%d) created", - local_ust_event->attr.name, local_ust_event->attr.loglevel_type, - local_ust_event->attr.loglevel); - - *ust_event = local_ust_event; - - return ret; - -error_free_event: - free(local_ust_event); -error: - free(filter_expression); - free(filter); - free(exclusion); - return ret; -} - -static -int trace_ust_context_type_event_to_ust( - enum lttng_event_context_type type) -{ - int utype; - - switch (type) { - case LTTNG_EVENT_CONTEXT_VTID: - utype = LTTNG_UST_ABI_CONTEXT_VTID; - break; - case LTTNG_EVENT_CONTEXT_VPID: - utype = LTTNG_UST_ABI_CONTEXT_VPID; - break; - case LTTNG_EVENT_CONTEXT_PTHREAD_ID: - utype = LTTNG_UST_ABI_CONTEXT_PTHREAD_ID; - break; - case LTTNG_EVENT_CONTEXT_PROCNAME: - utype = LTTNG_UST_ABI_CONTEXT_PROCNAME; - break; - case LTTNG_EVENT_CONTEXT_IP: - utype = LTTNG_UST_ABI_CONTEXT_IP; - break; - case LTTNG_EVENT_CONTEXT_PERF_THREAD_COUNTER: - if (!lttng_ust_ctl_has_perf_counters()) { - utype = -1; - WARN("Perf counters not implemented in UST"); - } else { - utype = LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER; - } - break; - case LTTNG_EVENT_CONTEXT_APP_CONTEXT: - utype = LTTNG_UST_ABI_CONTEXT_APP_CONTEXT; - break; - case LTTNG_EVENT_CONTEXT_CGROUP_NS: - utype = LTTNG_UST_ABI_CONTEXT_CGROUP_NS; - break; - case LTTNG_EVENT_CONTEXT_IPC_NS: - utype = LTTNG_UST_ABI_CONTEXT_IPC_NS; - break; - case LTTNG_EVENT_CONTEXT_MNT_NS: - utype = LTTNG_UST_ABI_CONTEXT_MNT_NS; - break; - case LTTNG_EVENT_CONTEXT_NET_NS: - utype = LTTNG_UST_ABI_CONTEXT_NET_NS; - break; - case LTTNG_EVENT_CONTEXT_PID_NS: - utype = LTTNG_UST_ABI_CONTEXT_PID_NS; - break; - case LTTNG_EVENT_CONTEXT_TIME_NS: - utype = LTTNG_UST_ABI_CONTEXT_TIME_NS; - break; - case LTTNG_EVENT_CONTEXT_USER_NS: - utype = LTTNG_UST_ABI_CONTEXT_USER_NS; - break; - case LTTNG_EVENT_CONTEXT_UTS_NS: - utype = LTTNG_UST_ABI_CONTEXT_UTS_NS; - break; - case LTTNG_EVENT_CONTEXT_VUID: - utype = LTTNG_UST_ABI_CONTEXT_VUID; - break; - case LTTNG_EVENT_CONTEXT_VEUID: - utype = LTTNG_UST_ABI_CONTEXT_VEUID; - break; - case LTTNG_EVENT_CONTEXT_VSUID: - utype = LTTNG_UST_ABI_CONTEXT_VSUID; - break; - case LTTNG_EVENT_CONTEXT_VGID: - utype = LTTNG_UST_ABI_CONTEXT_VGID; - break; - case LTTNG_EVENT_CONTEXT_VEGID: - utype = LTTNG_UST_ABI_CONTEXT_VEGID; - break; - case LTTNG_EVENT_CONTEXT_VSGID: - utype = LTTNG_UST_ABI_CONTEXT_VSGID; - break; - default: - utype = -1; - break; - } - return utype; -} - -/* - * Return 1 if contexts match, 0 otherwise. - */ -int trace_ust_match_context(const struct ltt_ust_context *uctx, - const struct lttng_event_context *ctx) -{ - int utype; - - utype = trace_ust_context_type_event_to_ust(ctx->ctx); - if (utype < 0) { - return 0; - } - if (uctx->ctx.ctx != utype) { - return 0; - } - switch (utype) { - case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: - if (uctx->ctx.u.perf_counter.type - != ctx->u.perf_counter.type) { - return 0; - } - if (uctx->ctx.u.perf_counter.config - != ctx->u.perf_counter.config) { - return 0; - } - if (strncmp(uctx->ctx.u.perf_counter.name, - ctx->u.perf_counter.name, - LTTNG_UST_ABI_SYM_NAME_LEN)) { - return 0; - } - break; - case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: - LTTNG_ASSERT(uctx->ctx.u.app_ctx.provider_name); - LTTNG_ASSERT(uctx->ctx.u.app_ctx.ctx_name); - if (strcmp(uctx->ctx.u.app_ctx.provider_name, - ctx->u.app_ctx.provider_name) || - strcmp(uctx->ctx.u.app_ctx.ctx_name, - ctx->u.app_ctx.ctx_name)) { - return 0; - } - default: - break; - - } - return 1; -} - -/* - * Allocate and initialize an UST context. - * - * Return pointer to structure or NULL. - */ -struct ltt_ust_context *trace_ust_create_context( - const struct lttng_event_context *ctx) -{ - struct ltt_ust_context *uctx = NULL; - int utype; - - LTTNG_ASSERT(ctx); - - utype = trace_ust_context_type_event_to_ust(ctx->ctx); - if (utype < 0) { - ERR("Invalid UST context"); - goto end; - } - - uctx = zmalloc(sizeof(struct ltt_ust_context)); - if (!uctx) { - PERROR("zmalloc ltt_ust_context"); - goto end; - } - - uctx->ctx.ctx = (enum lttng_ust_abi_context_type) utype; - switch (utype) { - case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: - uctx->ctx.u.perf_counter.type = ctx->u.perf_counter.type; - uctx->ctx.u.perf_counter.config = ctx->u.perf_counter.config; - strncpy(uctx->ctx.u.perf_counter.name, ctx->u.perf_counter.name, - LTTNG_UST_ABI_SYM_NAME_LEN); - uctx->ctx.u.perf_counter.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; - break; - case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: - { - char *provider_name = NULL, *ctx_name = NULL; - - provider_name = strdup(ctx->u.app_ctx.provider_name); - if (!provider_name) { - goto error; - } - uctx->ctx.u.app_ctx.provider_name = provider_name; - - ctx_name = strdup(ctx->u.app_ctx.ctx_name); - if (!ctx_name) { - goto error; - } - uctx->ctx.u.app_ctx.ctx_name = ctx_name; - break; - } - default: - break; - } - lttng_ht_node_init_ulong(&uctx->node, (unsigned long) uctx->ctx.ctx); -end: - return uctx; -error: - trace_ust_destroy_context(uctx); - return NULL; -} - -static void destroy_id_tracker_node_rcu(struct rcu_head *head) -{ - struct ust_id_tracker_node *tracker_node = caa_container_of( - head, struct ust_id_tracker_node, node.head); - free(tracker_node); -} - -static void destroy_id_tracker_node(struct ust_id_tracker_node *tracker_node) -{ - call_rcu(&tracker_node->node.head, destroy_id_tracker_node_rcu); -} - -static int init_id_tracker(struct ust_id_tracker *id_tracker) -{ - int ret = LTTNG_OK; - - id_tracker->ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); - if (!id_tracker->ht) { - ret = LTTNG_ERR_NOMEM; - goto end; - } - -end: - return ret; -} - -/* - * Teardown id tracker content, but don't free id_tracker object. - */ -static void fini_id_tracker(struct ust_id_tracker *id_tracker) -{ - struct ust_id_tracker_node *tracker_node; - struct lttng_ht_iter iter; - - if (!id_tracker->ht) { - return; - } - rcu_read_lock(); - cds_lfht_for_each_entry (id_tracker->ht->ht, &iter.iter, tracker_node, - node.node) { - int ret = lttng_ht_del(id_tracker->ht, &iter); - - LTTNG_ASSERT(!ret); - destroy_id_tracker_node(tracker_node); - } - rcu_read_unlock(); - ht_cleanup_push(id_tracker->ht); - id_tracker->ht = NULL; -} - -static struct ust_id_tracker_node *id_tracker_lookup( - struct ust_id_tracker *id_tracker, - int id, - struct lttng_ht_iter *iter) -{ - unsigned long _id = (unsigned long) id; - struct lttng_ht_node_ulong *node; - - lttng_ht_lookup(id_tracker->ht, (void *) _id, iter); - node = lttng_ht_iter_get_node_ulong(iter); - if (node) { - return caa_container_of(node, struct ust_id_tracker_node, node); - } else { - return NULL; - } -} - -static int id_tracker_add_id(struct ust_id_tracker *id_tracker, int id) -{ - int retval = LTTNG_OK; - struct ust_id_tracker_node *tracker_node; - struct lttng_ht_iter iter; - - if (id < 0) { - retval = LTTNG_ERR_INVALID; - goto end; - } - tracker_node = id_tracker_lookup(id_tracker, id, &iter); - if (tracker_node) { - /* Already exists. */ - retval = LTTNG_ERR_PROCESS_ATTR_EXISTS; - goto end; - } - tracker_node = zmalloc(sizeof(*tracker_node)); - if (!tracker_node) { - retval = LTTNG_ERR_NOMEM; - goto end; - } - lttng_ht_node_init_ulong(&tracker_node->node, (unsigned long) id); - lttng_ht_add_unique_ulong(id_tracker->ht, &tracker_node->node); -end: - return retval; -} - -static int id_tracker_del_id(struct ust_id_tracker *id_tracker, int id) -{ - int retval = LTTNG_OK, ret; - struct ust_id_tracker_node *tracker_node; - struct lttng_ht_iter iter; - - if (id < 0) { - retval = LTTNG_ERR_INVALID; - goto end; - } - tracker_node = id_tracker_lookup(id_tracker, id, &iter); - if (!tracker_node) { - /* Not found */ - retval = LTTNG_ERR_PROCESS_ATTR_MISSING; - goto end; - } - ret = lttng_ht_del(id_tracker->ht, &iter); - LTTNG_ASSERT(!ret); - - destroy_id_tracker_node(tracker_node); -end: - return retval; -} - -static struct ust_id_tracker *get_id_tracker(struct ltt_ust_session *session, - enum lttng_process_attr process_attr) -{ - switch (process_attr) { - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - return &session->vpid_tracker; - case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: - return &session->vuid_tracker; - case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: - return &session->vgid_tracker; - default: - return NULL; - } -} - -static struct process_attr_tracker *_trace_ust_get_process_attr_tracker( - struct ltt_ust_session *session, - enum lttng_process_attr process_attr) -{ - switch (process_attr) { - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - return session->tracker_vpid; - case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: - return session->tracker_vuid; - case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: - return session->tracker_vgid; - default: - return NULL; - } -} - -const struct process_attr_tracker *trace_ust_get_process_attr_tracker( - struct ltt_ust_session *session, - enum lttng_process_attr process_attr) -{ - return (const struct process_attr_tracker *) - _trace_ust_get_process_attr_tracker( - session, process_attr); -} - -/* - * The session lock is held when calling this function. - */ -int trace_ust_id_tracker_lookup(enum lttng_process_attr process_attr, - struct ltt_ust_session *session, - int id) -{ - struct lttng_ht_iter iter; - struct ust_id_tracker *id_tracker; - - id_tracker = get_id_tracker(session, process_attr); - if (!id_tracker) { - abort(); - } - if (!id_tracker->ht) { - return 1; - } - if (id_tracker_lookup(id_tracker, id, &iter)) { - return 1; - } - return 0; -} - -/* - * Called with the session lock held. - */ -enum lttng_error_code trace_ust_process_attr_tracker_set_tracking_policy( - struct ltt_ust_session *session, - enum lttng_process_attr process_attr, - enum lttng_tracking_policy policy) -{ - int ret; - enum lttng_error_code ret_code = LTTNG_OK; - struct ust_id_tracker *id_tracker = - get_id_tracker(session, process_attr); - struct process_attr_tracker *tracker = - _trace_ust_get_process_attr_tracker( - session, process_attr); - bool should_update_apps = false; - enum lttng_tracking_policy previous_policy; - - if (!tracker) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - previous_policy = process_attr_tracker_get_tracking_policy(tracker); - ret = process_attr_tracker_set_tracking_policy(tracker, policy); - if (ret) { - ret_code = LTTNG_ERR_UNK; - goto end; - } - - if (previous_policy == policy) { - goto end; - } - - switch (policy) { - case LTTNG_TRACKING_POLICY_INCLUDE_ALL: - /* Track all values: destroy tracker if exists. */ - if (id_tracker->ht) { - fini_id_tracker(id_tracker); - /* Ensure all apps have session. */ - should_update_apps = true; - } - break; - case LTTNG_TRACKING_POLICY_EXCLUDE_ALL: - case LTTNG_TRACKING_POLICY_INCLUDE_SET: - /* fall-through. */ - fini_id_tracker(id_tracker); - ret_code = init_id_tracker(id_tracker); - if (ret_code != LTTNG_OK) { - ERR("Error initializing ID tracker"); - goto end; - } - /* Remove all apps from session. */ - should_update_apps = true; - break; - default: - abort(); - } - if (should_update_apps && session->active) { - ust_app_global_update_all(session); - } -end: - return ret_code; -} - -/* Called with the session lock held. */ -enum lttng_error_code trace_ust_process_attr_tracker_inclusion_set_add_value( - struct ltt_ust_session *session, - enum lttng_process_attr process_attr, - const struct process_attr_value *value) -{ - enum lttng_error_code ret_code = LTTNG_OK; - bool should_update_apps = false; - struct ust_id_tracker *id_tracker = - get_id_tracker(session, process_attr); - struct process_attr_tracker *tracker; - int integral_value; - enum process_attr_tracker_status status; - struct ust_app *app; - - /* - * Convert process attribute tracker value to the integral - * representation required by the kern-ctl API. - */ - switch (process_attr) { - case LTTNG_PROCESS_ATTR_PROCESS_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - integral_value = (int) value->value.pid; - break; - case LTTNG_PROCESS_ATTR_USER_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: - if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME) { - uid_t uid; - - ret_code = utils_user_id_from_name( - value->value.user_name, &uid); - if (ret_code != LTTNG_OK) { - goto end; - } - integral_value = (int) uid; - } else { - integral_value = (int) value->value.uid; - } - break; - case LTTNG_PROCESS_ATTR_GROUP_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: - if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME) { - gid_t gid; - - ret_code = utils_group_id_from_name( - value->value.group_name, &gid); - if (ret_code != LTTNG_OK) { - goto end; - } - integral_value = (int) gid; - } else { - integral_value = (int) value->value.gid; - } - break; - default: - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - tracker = _trace_ust_get_process_attr_tracker(session, process_attr); - if (!tracker) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - status = process_attr_tracker_inclusion_set_add_value(tracker, value); - if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { - switch (status) { - case PROCESS_ATTR_TRACKER_STATUS_EXISTS: - ret_code = LTTNG_ERR_PROCESS_ATTR_EXISTS; - break; - case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: - ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; - break; - case PROCESS_ATTR_TRACKER_STATUS_ERROR: - default: - ret_code = LTTNG_ERR_UNK; - break; - } - goto end; - } - - DBG("User space track %s %d for session id %" PRIu64, - lttng_process_attr_to_string(process_attr), - integral_value, session->id); - - ret_code = id_tracker_add_id(id_tracker, integral_value); - if (ret_code != LTTNG_OK) { - goto end; - } - /* Add session to application */ - switch (process_attr) { - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - app = ust_app_find_by_pid(integral_value); - if (app) { - should_update_apps = true; - } - break; - default: - should_update_apps = true; - break; - } - if (should_update_apps && session->active) { - ust_app_global_update_all(session); - } -end: - return ret_code; -} - -/* Called with the session lock held. */ -enum lttng_error_code trace_ust_process_attr_tracker_inclusion_set_remove_value( - struct ltt_ust_session *session, - enum lttng_process_attr process_attr, - const struct process_attr_value *value) -{ - enum lttng_error_code ret_code = LTTNG_OK; - bool should_update_apps = false; - struct ust_id_tracker *id_tracker = - get_id_tracker(session, process_attr); - struct process_attr_tracker *tracker; - int integral_value; - enum process_attr_tracker_status status; - struct ust_app *app; - - /* - * Convert process attribute tracker value to the integral - * representation required by the kern-ctl API. - */ - switch (process_attr) { - case LTTNG_PROCESS_ATTR_PROCESS_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - integral_value = (int) value->value.pid; - break; - case LTTNG_PROCESS_ATTR_USER_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: - if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME) { - uid_t uid; - - ret_code = utils_user_id_from_name( - value->value.user_name, &uid); - if (ret_code != LTTNG_OK) { - goto end; - } - integral_value = (int) uid; - } else { - integral_value = (int) value->value.uid; - } - break; - case LTTNG_PROCESS_ATTR_GROUP_ID: - case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: - if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME) { - gid_t gid; - - ret_code = utils_group_id_from_name( - value->value.group_name, &gid); - if (ret_code != LTTNG_OK) { - goto end; - } - integral_value = (int) gid; - } else { - integral_value = (int) value->value.gid; - } - break; - default: - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - tracker = _trace_ust_get_process_attr_tracker(session, process_attr); - if (!tracker) { - ret_code = LTTNG_ERR_INVALID; - goto end; - } - - status = process_attr_tracker_inclusion_set_remove_value( - tracker, value); - if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { - switch (status) { - case PROCESS_ATTR_TRACKER_STATUS_MISSING: - ret_code = LTTNG_ERR_PROCESS_ATTR_MISSING; - break; - case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: - ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; - break; - case PROCESS_ATTR_TRACKER_STATUS_ERROR: - default: - ret_code = LTTNG_ERR_UNK; - break; - } - goto end; - } - - DBG("User space untrack %s %d for session id %" PRIu64, - lttng_process_attr_to_string(process_attr), - integral_value, session->id); - - ret_code = id_tracker_del_id(id_tracker, integral_value); - if (ret_code != LTTNG_OK) { - goto end; - } - /* Add session to application */ - switch (process_attr) { - case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: - app = ust_app_find_by_pid(integral_value); - if (app) { - should_update_apps = true; - } - break; - default: - should_update_apps = true; - break; - } - if (should_update_apps && session->active) { - ust_app_global_update_all(session); - } -end: - return ret_code; -} - -/* - * RCU safe free context structure. - */ -static void destroy_context_rcu(struct rcu_head *head) -{ - struct lttng_ht_node_ulong *node = - caa_container_of(head, struct lttng_ht_node_ulong, head); - struct ltt_ust_context *ctx = - caa_container_of(node, struct ltt_ust_context, node); - - trace_ust_destroy_context(ctx); -} - -/* - * Cleanup UST context hash table. - */ -static void destroy_contexts(struct lttng_ht *ht) -{ - int ret; - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter iter; - struct ltt_ust_context *ctx; - - LTTNG_ASSERT(ht); - - rcu_read_lock(); - cds_lfht_for_each_entry(ht->ht, &iter.iter, node, node) { - /* Remove from ordered list. */ - ctx = caa_container_of(node, struct ltt_ust_context, node); - cds_list_del(&ctx->list); - /* Remove from channel's hash table. */ - ret = lttng_ht_del(ht, &iter); - if (!ret) { - call_rcu(&node->head, destroy_context_rcu); - } - } - rcu_read_unlock(); - - ht_cleanup_push(ht); -} - -/* - * Cleanup ust event structure. - */ -void trace_ust_destroy_event(struct ltt_ust_event *event) -{ - LTTNG_ASSERT(event); - - DBG2("Trace destroy UST event %s", event->attr.name); - free(event->filter_expression); - free(event->filter); - free(event->exclusion); - free(event); -} - -/* - * Cleanup ust context structure. - */ -void trace_ust_destroy_context(struct ltt_ust_context *ctx) -{ - LTTNG_ASSERT(ctx); - - if (ctx->ctx.ctx == LTTNG_UST_ABI_CONTEXT_APP_CONTEXT) { - free(ctx->ctx.u.app_ctx.provider_name); - free(ctx->ctx.u.app_ctx.ctx_name); - } - free(ctx); -} - -/* - * URCU intermediate call to complete destroy event. - */ -static void destroy_event_rcu(struct rcu_head *head) -{ - struct lttng_ht_node_str *node = - caa_container_of(head, struct lttng_ht_node_str, head); - struct ltt_ust_event *event = - caa_container_of(node, struct ltt_ust_event, node); - - trace_ust_destroy_event(event); -} - -/* - * Cleanup UST events hashtable. - */ -static void destroy_events(struct lttng_ht *events) -{ - int ret; - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(events); - - rcu_read_lock(); - cds_lfht_for_each_entry(events->ht, &iter.iter, node, node) { - ret = lttng_ht_del(events, &iter); - LTTNG_ASSERT(!ret); - call_rcu(&node->head, destroy_event_rcu); - } - rcu_read_unlock(); - - ht_cleanup_push(events); -} - -/* - * Cleanup ust channel structure. - * - * Should _NOT_ be called with RCU read lock held. - */ -static void _trace_ust_destroy_channel(struct ltt_ust_channel *channel) -{ - LTTNG_ASSERT(channel); - - DBG2("Trace destroy UST channel %s", channel->name); - - free(channel); -} - -/* - * URCU intermediate call to complete destroy channel. - */ -static void destroy_channel_rcu(struct rcu_head *head) -{ - struct lttng_ht_node_str *node = - caa_container_of(head, struct lttng_ht_node_str, head); - struct ltt_ust_channel *channel = - caa_container_of(node, struct ltt_ust_channel, node); - - _trace_ust_destroy_channel(channel); -} - -void trace_ust_destroy_channel(struct ltt_ust_channel *channel) -{ - /* Destroying all events of the channel */ - destroy_events(channel->events); - /* Destroying all context of the channel */ - destroy_contexts(channel->ctx); - - call_rcu(&channel->node.head, destroy_channel_rcu); -} - -/* - * Remove an UST channel from a channel HT. - */ -void trace_ust_delete_channel(struct lttng_ht *ht, - struct ltt_ust_channel *channel) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(ht); - LTTNG_ASSERT(channel); - - iter.iter.node = &channel->node.node; - ret = lttng_ht_del(ht, &iter); - LTTNG_ASSERT(!ret); -} - -/* - * Iterate over a hash table containing channels and cleanup safely. - */ -static void destroy_channels(struct lttng_ht *channels) -{ - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(channels); - - rcu_read_lock(); - cds_lfht_for_each_entry(channels->ht, &iter.iter, node, node) { - struct ltt_ust_channel *chan = - caa_container_of(node, struct ltt_ust_channel, node); - - trace_ust_delete_channel(channels, chan); - trace_ust_destroy_channel(chan); - } - rcu_read_unlock(); - - ht_cleanup_push(channels); -} - -/* - * Cleanup UST global domain. - */ -static void destroy_domain_global(struct ltt_ust_domain_global *dom) -{ - LTTNG_ASSERT(dom); - - destroy_channels(dom->channels); -} - -/* - * Cleanup ust session structure, keeping data required by - * destroy notifier. - * - * Should *NOT* be called with RCU read-side lock held. - */ -void trace_ust_destroy_session(struct ltt_ust_session *session) -{ - struct agent *agt; - struct buffer_reg_uid *reg, *sreg; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(session); - - DBG2("Trace UST destroy session %" PRIu64, session->id); - - /* Cleaning up UST domain */ - destroy_domain_global(&session->domain_global); - - rcu_read_lock(); - cds_lfht_for_each_entry(session->agents->ht, &iter.iter, agt, node.node) { - int ret = lttng_ht_del(session->agents, &iter); - - LTTNG_ASSERT(!ret); - agent_destroy(agt); - } - rcu_read_unlock(); - - ht_cleanup_push(session->agents); - - /* Cleanup UID buffer registry object(s). */ - cds_list_for_each_entry_safe(reg, sreg, &session->buffer_reg_uid_list, - lnode) { - cds_list_del(®->lnode); - buffer_reg_uid_remove(reg); - buffer_reg_uid_destroy(reg, session->consumer); - } - - process_attr_tracker_destroy(session->tracker_vpid); - process_attr_tracker_destroy(session->tracker_vuid); - process_attr_tracker_destroy(session->tracker_vgid); - - fini_id_tracker(&session->vpid_tracker); - fini_id_tracker(&session->vuid_tracker); - fini_id_tracker(&session->vgid_tracker); - lttng_trace_chunk_put(session->current_trace_chunk); -} - -/* Free elements needed by destroy notifiers. */ -void trace_ust_free_session(struct ltt_ust_session *session) -{ - consumer_output_put(session->consumer); - free(session); -} diff --git a/src/bin/lttng-sessiond/trace-ust.cpp b/src/bin/lttng-sessiond/trace-ust.cpp new file mode 100644 index 000000000..0f303bf03 --- /dev/null +++ b/src/bin/lttng-sessiond/trace-ust.cpp @@ -0,0 +1,1440 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2016 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "buffer-registry.h" +#include "trace-ust.h" +#include "utils.h" +#include "ust-app.h" +#include "agent.h" + +/* + * Match function for the events hash table lookup. + * + * Matches by name only. Used by the disable command. + */ +int trace_ust_ht_match_event_by_name(struct cds_lfht_node *node, + const void *_key) +{ + struct ltt_ust_event *event; + const char *name; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + event = caa_container_of(node, struct ltt_ust_event, node.node); + name = (const char *) _key; + + /* Event name */ + if (strncmp(event->attr.name, name, sizeof(event->attr.name)) != 0) { + goto no_match; + } + + /* Match */ + return 1; + +no_match: + return 0; +} + +/* + * Match function for the hash table lookup. + * + * It matches an ust event based on three attributes which are the event name, + * the filter bytecode and the loglevel. + */ +int trace_ust_ht_match_event(struct cds_lfht_node *node, const void *_key) +{ + struct ltt_ust_event *event; + const struct ltt_ust_ht_key *key; + int ev_loglevel_value; + int ll_match; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + event = caa_container_of(node, struct ltt_ust_event, node.node); + key = (ltt_ust_ht_key *) _key; + ev_loglevel_value = event->attr.loglevel; + + /* Match the 4 elements of the key: name, filter, loglevel, exclusions. */ + + /* Event name */ + if (strncmp(event->attr.name, key->name, sizeof(event->attr.name)) != 0) { + goto no_match; + } + + /* Event loglevel value and type. */ + ll_match = loglevels_match(event->attr.loglevel_type, + ev_loglevel_value, key->loglevel_type, + key->loglevel_value, LTTNG_UST_ABI_LOGLEVEL_ALL); + + if (!ll_match) { + goto no_match; + } + + /* Only one of the filters is NULL, fail. */ + if ((key->filter && !event->filter) || (!key->filter && event->filter)) { + goto no_match; + } + + if (key->filter && event->filter) { + /* Both filters exists, check length followed by the bytecode. */ + if (event->filter->len != key->filter->len || + memcmp(event->filter->data, key->filter->data, + event->filter->len) != 0) { + goto no_match; + } + } + + /* If only one of the exclusions is NULL, fail. */ + if ((key->exclusion && !event->exclusion) || (!key->exclusion && event->exclusion)) { + goto no_match; + } + + if (key->exclusion && event->exclusion) { + size_t i; + + /* Check exclusion counts first. */ + if (event->exclusion->count != key->exclusion->count) { + goto no_match; + } + + /* Compare names individually. */ + for (i = 0; i < event->exclusion->count; ++i) { + size_t j; + bool found = false; + const char *name_ev = + LTTNG_EVENT_EXCLUSION_NAME_AT( + event->exclusion, i); + + /* + * Compare this exclusion name to all the key's + * exclusion names. + */ + for (j = 0; j < key->exclusion->count; ++j) { + const char *name_key = + LTTNG_EVENT_EXCLUSION_NAME_AT( + key->exclusion, j); + + if (!strncmp(name_ev, name_key, + LTTNG_SYMBOL_NAME_LEN)) { + /* Names match! */ + found = true; + break; + } + } + + /* + * If the current exclusion name was not found amongst + * the key's exclusion names, then there's no match. + */ + if (!found) { + goto no_match; + } + } + } + /* Match. */ + return 1; + +no_match: + return 0; +} + +/* + * Find the channel in the hashtable and return channel pointer. RCU read side + * lock MUST be acquired before calling this. + */ +struct ltt_ust_channel *trace_ust_find_channel_by_name(struct lttng_ht *ht, + const char *name) +{ + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + + /* + * If we receive an empty string for channel name, it means the + * default channel name is requested. + */ + if (name[0] == '\0') + name = DEFAULT_CHANNEL_NAME; + + lttng_ht_lookup(ht, (void *)name, &iter); + node = lttng_ht_iter_get_node_str(&iter); + if (node == NULL) { + goto error; + } + + DBG2("Trace UST channel %s found by name", name); + + return caa_container_of(node, struct ltt_ust_channel, node); + +error: + DBG2("Trace UST channel %s not found by name", name); + return NULL; +} + +/* + * Find the event in the hashtable and return event pointer. RCU read side lock + * MUST be acquired before calling this. + */ +struct ltt_ust_event *trace_ust_find_event(struct lttng_ht *ht, + char *name, struct lttng_bytecode *filter, + enum lttng_ust_abi_loglevel_type loglevel_type, int loglevel_value, + struct lttng_event_exclusion *exclusion) +{ + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + struct ltt_ust_ht_key key; + + LTTNG_ASSERT(name); + LTTNG_ASSERT(ht); + + key.name = name; + key.filter = filter; + key.loglevel_type = loglevel_type; + key.loglevel_value = loglevel_value; + key.exclusion = exclusion; + + cds_lfht_lookup(ht->ht, ht->hash_fct((void *) name, lttng_ht_seed), + trace_ust_ht_match_event, &key, &iter.iter); + node = lttng_ht_iter_get_node_str(&iter); + if (node == NULL) { + goto error; + } + + DBG2("Trace UST event %s found", key.name); + + return caa_container_of(node, struct ltt_ust_event, node); + +error: + DBG2("Trace UST event %s NOT found", key.name); + return NULL; +} + +/* + * Lookup an agent in the session agents hash table by domain type and return + * the object if found else NULL. + * + * RCU read side lock must be acquired before calling and only released + * once the agent is no longer in scope or being used. + */ +struct agent *trace_ust_find_agent(struct ltt_ust_session *session, + enum lttng_domain_type domain_type) +{ + struct agent *agt = NULL; + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + uint64_t key; + + LTTNG_ASSERT(session); + + DBG3("Trace ust agent lookup for domain %d", domain_type); + + key = domain_type; + + lttng_ht_lookup(session->agents, &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; +} + +/* + * Allocate and initialize a ust session data structure. + * + * Return pointer to structure or NULL. + */ +struct ltt_ust_session *trace_ust_create_session(uint64_t session_id) +{ + struct ltt_ust_session *lus; + + /* Allocate a new ltt ust session */ + lus = (ltt_ust_session *) zmalloc(sizeof(struct ltt_ust_session)); + if (lus == NULL) { + PERROR("create ust session zmalloc"); + goto error_alloc; + } + + /* Init data structure */ + lus->id = session_id; + lus->active = 0; + + /* Set default metadata channel attribute. */ + lus->metadata_attr.overwrite = DEFAULT_CHANNEL_OVERWRITE; + lus->metadata_attr.subbuf_size = default_get_metadata_subbuf_size(); + lus->metadata_attr.num_subbuf = DEFAULT_METADATA_SUBBUF_NUM; + lus->metadata_attr.switch_timer_interval = DEFAULT_METADATA_SWITCH_TIMER; + lus->metadata_attr.read_timer_interval = DEFAULT_METADATA_READ_TIMER; + lus->metadata_attr.output = LTTNG_UST_ABI_MMAP; + + /* + * Default buffer type. This can be changed through an enable channel + * requesting a different type. Note that this can only be changed once + * during the session lifetime which is at the first enable channel and + * only before start. The flag buffer_type_changed indicates the status. + */ + lus->buffer_type = LTTNG_BUFFER_PER_UID; + /* Once set to 1, the buffer_type is immutable for the session. */ + lus->buffer_type_changed = 0; + /* Init it in case it get used after allocation. */ + CDS_INIT_LIST_HEAD(&lus->buffer_reg_uid_list); + + /* Alloc UST global domain channels' HT */ + lus->domain_global.channels = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); + /* Alloc agent hash table. */ + lus->agents = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + + lus->tracker_vpid = process_attr_tracker_create(); + if (!lus->tracker_vpid) { + goto error; + } + lus->tracker_vuid = process_attr_tracker_create(); + if (!lus->tracker_vuid) { + goto error; + } + lus->tracker_vgid = process_attr_tracker_create(); + if (!lus->tracker_vgid) { + goto error; + } + lus->consumer = consumer_create_output(CONSUMER_DST_LOCAL); + if (lus->consumer == NULL) { + goto error; + } + + DBG2("UST trace session create successful"); + + return lus; + +error: + process_attr_tracker_destroy(lus->tracker_vpid); + process_attr_tracker_destroy(lus->tracker_vuid); + process_attr_tracker_destroy(lus->tracker_vgid); + ht_cleanup_push(lus->domain_global.channels); + ht_cleanup_push(lus->agents); + free(lus); +error_alloc: + return NULL; +} + +/* + * Allocate and initialize a ust channel data structure. + * + * Return pointer to structure or NULL. + */ +struct ltt_ust_channel *trace_ust_create_channel(struct lttng_channel *chan, + enum lttng_domain_type domain) +{ + struct ltt_ust_channel *luc; + + LTTNG_ASSERT(chan); + + luc = (ltt_ust_channel *) zmalloc(sizeof(struct ltt_ust_channel)); + if (luc == NULL) { + PERROR("ltt_ust_channel zmalloc"); + goto error; + } + + luc->domain = domain; + + /* Copy UST channel attributes */ + luc->attr.overwrite = chan->attr.overwrite; + luc->attr.subbuf_size = chan->attr.subbuf_size; + luc->attr.num_subbuf = chan->attr.num_subbuf; + luc->attr.switch_timer_interval = chan->attr.switch_timer_interval; + luc->attr.read_timer_interval = chan->attr.read_timer_interval; + luc->attr.output = (enum lttng_ust_abi_output) chan->attr.output; + luc->monitor_timer_interval = ((struct lttng_channel_extended *) + chan->attr.extended.ptr)->monitor_timer_interval; + luc->attr.u.s.blocking_timeout = ((struct lttng_channel_extended *) + chan->attr.extended.ptr)->blocking_timeout; + + /* Translate to UST output enum */ + switch (luc->attr.output) { + default: + luc->attr.output = LTTNG_UST_ABI_MMAP; + break; + } + + /* + * If we receive an empty string for channel name, it means the + * default channel name is requested. + */ + if (chan->name[0] == '\0') { + strncpy(luc->name, DEFAULT_CHANNEL_NAME, sizeof(luc->name)); + } else { + /* Copy channel name */ + strncpy(luc->name, chan->name, sizeof(luc->name)); + } + luc->name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; + + /* Init node */ + lttng_ht_node_init_str(&luc->node, luc->name); + CDS_INIT_LIST_HEAD(&luc->ctx_list); + + /* Alloc hash tables */ + luc->events = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); + luc->ctx = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); + + /* On-disk circular buffer parameters */ + luc->tracefile_size = chan->attr.tracefile_size; + luc->tracefile_count = chan->attr.tracefile_count; + + DBG2("Trace UST channel %s created", luc->name); + +error: + return luc; +} + +/* + * Validates an exclusion list. + * + * Returns 0 if valid, negative value if invalid. + */ +static int validate_exclusion(struct lttng_event_exclusion *exclusion) +{ + size_t i; + int ret = 0; + + LTTNG_ASSERT(exclusion); + + for (i = 0; i < exclusion->count; ++i) { + size_t j; + const char *name_a = + LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, i); + + for (j = 0; j < i; ++j) { + const char *name_b = + LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, j); + + if (!strncmp(name_a, name_b, LTTNG_SYMBOL_NAME_LEN)) { + /* Match! */ + ret = -1; + goto end; + } + } + } + +end: + return ret; +} + +/* + * Allocate and initialize a ust event. Set name and event type. + * We own filter_expression, filter, and exclusion. + * + * Return an lttng_error_code + */ +enum lttng_error_code trace_ust_create_event(struct lttng_event *ev, + char *filter_expression, + struct lttng_bytecode *filter, + struct lttng_event_exclusion *exclusion, + bool internal_event, + struct ltt_ust_event **ust_event) +{ + struct ltt_ust_event *local_ust_event; + enum lttng_error_code ret = LTTNG_OK; + + LTTNG_ASSERT(ev); + + if (exclusion && validate_exclusion(exclusion)) { + ret = LTTNG_ERR_INVALID; + goto error; + } + + local_ust_event = (ltt_ust_event *) zmalloc(sizeof(struct ltt_ust_event)); + if (local_ust_event == NULL) { + PERROR("ust event zmalloc"); + ret = LTTNG_ERR_NOMEM; + goto error; + } + + local_ust_event->internal = internal_event; + + switch (ev->type) { + case LTTNG_EVENT_PROBE: + local_ust_event->attr.instrumentation = LTTNG_UST_ABI_PROBE; + break; + case LTTNG_EVENT_FUNCTION: + local_ust_event->attr.instrumentation = LTTNG_UST_ABI_FUNCTION; + break; + case LTTNG_EVENT_FUNCTION_ENTRY: + local_ust_event->attr.instrumentation = LTTNG_UST_ABI_FUNCTION; + break; + case LTTNG_EVENT_TRACEPOINT: + local_ust_event->attr.instrumentation = LTTNG_UST_ABI_TRACEPOINT; + break; + default: + ERR("Unknown ust instrumentation type (%d)", ev->type); + ret = LTTNG_ERR_INVALID; + goto error_free_event; + } + + /* Copy event name */ + strncpy(local_ust_event->attr.name, ev->name, LTTNG_UST_ABI_SYM_NAME_LEN); + local_ust_event->attr.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; + + switch (ev->loglevel_type) { + case LTTNG_EVENT_LOGLEVEL_ALL: + local_ust_event->attr.loglevel_type = LTTNG_UST_ABI_LOGLEVEL_ALL; + local_ust_event->attr.loglevel = -1; /* Force to -1 */ + break; + case LTTNG_EVENT_LOGLEVEL_RANGE: + local_ust_event->attr.loglevel_type = LTTNG_UST_ABI_LOGLEVEL_RANGE; + local_ust_event->attr.loglevel = ev->loglevel; + break; + case LTTNG_EVENT_LOGLEVEL_SINGLE: + local_ust_event->attr.loglevel_type = LTTNG_UST_ABI_LOGLEVEL_SINGLE; + local_ust_event->attr.loglevel = ev->loglevel; + break; + default: + ERR("Unknown ust loglevel type (%d)", ev->loglevel_type); + ret = LTTNG_ERR_INVALID; + goto error_free_event; + } + + /* Same layout. */ + local_ust_event->filter_expression = filter_expression; + local_ust_event->filter = filter; + local_ust_event->exclusion = exclusion; + + /* Init node */ + lttng_ht_node_init_str(&local_ust_event->node, local_ust_event->attr.name); + + DBG2("Trace UST event %s, loglevel (%d,%d) created", + local_ust_event->attr.name, local_ust_event->attr.loglevel_type, + local_ust_event->attr.loglevel); + + *ust_event = local_ust_event; + + return ret; + +error_free_event: + free(local_ust_event); +error: + free(filter_expression); + free(filter); + free(exclusion); + return ret; +} + +static +int trace_ust_context_type_event_to_ust( + enum lttng_event_context_type type) +{ + int utype; + + switch (type) { + case LTTNG_EVENT_CONTEXT_VTID: + utype = LTTNG_UST_ABI_CONTEXT_VTID; + break; + case LTTNG_EVENT_CONTEXT_VPID: + utype = LTTNG_UST_ABI_CONTEXT_VPID; + break; + case LTTNG_EVENT_CONTEXT_PTHREAD_ID: + utype = LTTNG_UST_ABI_CONTEXT_PTHREAD_ID; + break; + case LTTNG_EVENT_CONTEXT_PROCNAME: + utype = LTTNG_UST_ABI_CONTEXT_PROCNAME; + break; + case LTTNG_EVENT_CONTEXT_IP: + utype = LTTNG_UST_ABI_CONTEXT_IP; + break; + case LTTNG_EVENT_CONTEXT_PERF_THREAD_COUNTER: + if (!lttng_ust_ctl_has_perf_counters()) { + utype = -1; + WARN("Perf counters not implemented in UST"); + } else { + utype = LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER; + } + break; + case LTTNG_EVENT_CONTEXT_APP_CONTEXT: + utype = LTTNG_UST_ABI_CONTEXT_APP_CONTEXT; + break; + case LTTNG_EVENT_CONTEXT_CGROUP_NS: + utype = LTTNG_UST_ABI_CONTEXT_CGROUP_NS; + break; + case LTTNG_EVENT_CONTEXT_IPC_NS: + utype = LTTNG_UST_ABI_CONTEXT_IPC_NS; + break; + case LTTNG_EVENT_CONTEXT_MNT_NS: + utype = LTTNG_UST_ABI_CONTEXT_MNT_NS; + break; + case LTTNG_EVENT_CONTEXT_NET_NS: + utype = LTTNG_UST_ABI_CONTEXT_NET_NS; + break; + case LTTNG_EVENT_CONTEXT_PID_NS: + utype = LTTNG_UST_ABI_CONTEXT_PID_NS; + break; + case LTTNG_EVENT_CONTEXT_TIME_NS: + utype = LTTNG_UST_ABI_CONTEXT_TIME_NS; + break; + case LTTNG_EVENT_CONTEXT_USER_NS: + utype = LTTNG_UST_ABI_CONTEXT_USER_NS; + break; + case LTTNG_EVENT_CONTEXT_UTS_NS: + utype = LTTNG_UST_ABI_CONTEXT_UTS_NS; + break; + case LTTNG_EVENT_CONTEXT_VUID: + utype = LTTNG_UST_ABI_CONTEXT_VUID; + break; + case LTTNG_EVENT_CONTEXT_VEUID: + utype = LTTNG_UST_ABI_CONTEXT_VEUID; + break; + case LTTNG_EVENT_CONTEXT_VSUID: + utype = LTTNG_UST_ABI_CONTEXT_VSUID; + break; + case LTTNG_EVENT_CONTEXT_VGID: + utype = LTTNG_UST_ABI_CONTEXT_VGID; + break; + case LTTNG_EVENT_CONTEXT_VEGID: + utype = LTTNG_UST_ABI_CONTEXT_VEGID; + break; + case LTTNG_EVENT_CONTEXT_VSGID: + utype = LTTNG_UST_ABI_CONTEXT_VSGID; + break; + default: + utype = -1; + break; + } + return utype; +} + +/* + * Return 1 if contexts match, 0 otherwise. + */ +int trace_ust_match_context(const struct ltt_ust_context *uctx, + const struct lttng_event_context *ctx) +{ + int utype; + + utype = trace_ust_context_type_event_to_ust(ctx->ctx); + if (utype < 0) { + return 0; + } + if (uctx->ctx.ctx != utype) { + return 0; + } + switch (utype) { + case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: + if (uctx->ctx.u.perf_counter.type + != ctx->u.perf_counter.type) { + return 0; + } + if (uctx->ctx.u.perf_counter.config + != ctx->u.perf_counter.config) { + return 0; + } + if (strncmp(uctx->ctx.u.perf_counter.name, + ctx->u.perf_counter.name, + LTTNG_UST_ABI_SYM_NAME_LEN)) { + return 0; + } + break; + case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: + LTTNG_ASSERT(uctx->ctx.u.app_ctx.provider_name); + LTTNG_ASSERT(uctx->ctx.u.app_ctx.ctx_name); + if (strcmp(uctx->ctx.u.app_ctx.provider_name, + ctx->u.app_ctx.provider_name) || + strcmp(uctx->ctx.u.app_ctx.ctx_name, + ctx->u.app_ctx.ctx_name)) { + return 0; + } + default: + break; + + } + return 1; +} + +/* + * Allocate and initialize an UST context. + * + * Return pointer to structure or NULL. + */ +struct ltt_ust_context *trace_ust_create_context( + const struct lttng_event_context *ctx) +{ + struct ltt_ust_context *uctx = NULL; + int utype; + + LTTNG_ASSERT(ctx); + + utype = trace_ust_context_type_event_to_ust(ctx->ctx); + if (utype < 0) { + ERR("Invalid UST context"); + goto end; + } + + uctx = (ltt_ust_context *) zmalloc(sizeof(struct ltt_ust_context)); + if (!uctx) { + PERROR("zmalloc ltt_ust_context"); + goto end; + } + + uctx->ctx.ctx = (enum lttng_ust_abi_context_type) utype; + switch (utype) { + case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: + uctx->ctx.u.perf_counter.type = ctx->u.perf_counter.type; + uctx->ctx.u.perf_counter.config = ctx->u.perf_counter.config; + strncpy(uctx->ctx.u.perf_counter.name, ctx->u.perf_counter.name, + LTTNG_UST_ABI_SYM_NAME_LEN); + uctx->ctx.u.perf_counter.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; + break; + case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: + { + char *provider_name = NULL, *ctx_name = NULL; + + provider_name = strdup(ctx->u.app_ctx.provider_name); + if (!provider_name) { + goto error; + } + uctx->ctx.u.app_ctx.provider_name = provider_name; + + ctx_name = strdup(ctx->u.app_ctx.ctx_name); + if (!ctx_name) { + goto error; + } + uctx->ctx.u.app_ctx.ctx_name = ctx_name; + break; + } + default: + break; + } + lttng_ht_node_init_ulong(&uctx->node, (unsigned long) uctx->ctx.ctx); +end: + return uctx; +error: + trace_ust_destroy_context(uctx); + return NULL; +} + +static void destroy_id_tracker_node_rcu(struct rcu_head *head) +{ + struct ust_id_tracker_node *tracker_node = caa_container_of( + head, struct ust_id_tracker_node, node.head); + free(tracker_node); +} + +static void destroy_id_tracker_node(struct ust_id_tracker_node *tracker_node) +{ + call_rcu(&tracker_node->node.head, destroy_id_tracker_node_rcu); +} + +static int init_id_tracker(struct ust_id_tracker *id_tracker) +{ + int ret = LTTNG_OK; + + id_tracker->ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); + if (!id_tracker->ht) { + ret = LTTNG_ERR_NOMEM; + goto end; + } + +end: + return ret; +} + +/* + * Teardown id tracker content, but don't free id_tracker object. + */ +static void fini_id_tracker(struct ust_id_tracker *id_tracker) +{ + struct ust_id_tracker_node *tracker_node; + struct lttng_ht_iter iter; + + if (!id_tracker->ht) { + return; + } + rcu_read_lock(); + cds_lfht_for_each_entry (id_tracker->ht->ht, &iter.iter, tracker_node, + node.node) { + int ret = lttng_ht_del(id_tracker->ht, &iter); + + LTTNG_ASSERT(!ret); + destroy_id_tracker_node(tracker_node); + } + rcu_read_unlock(); + ht_cleanup_push(id_tracker->ht); + id_tracker->ht = NULL; +} + +static struct ust_id_tracker_node *id_tracker_lookup( + struct ust_id_tracker *id_tracker, + int id, + struct lttng_ht_iter *iter) +{ + unsigned long _id = (unsigned long) id; + struct lttng_ht_node_ulong *node; + + lttng_ht_lookup(id_tracker->ht, (void *) _id, iter); + node = lttng_ht_iter_get_node_ulong(iter); + if (node) { + return caa_container_of(node, struct ust_id_tracker_node, node); + } else { + return NULL; + } +} + +static int id_tracker_add_id(struct ust_id_tracker *id_tracker, int id) +{ + int retval = LTTNG_OK; + struct ust_id_tracker_node *tracker_node; + struct lttng_ht_iter iter; + + if (id < 0) { + retval = LTTNG_ERR_INVALID; + goto end; + } + tracker_node = id_tracker_lookup(id_tracker, id, &iter); + if (tracker_node) { + /* Already exists. */ + retval = LTTNG_ERR_PROCESS_ATTR_EXISTS; + goto end; + } + tracker_node = (ust_id_tracker_node *) zmalloc(sizeof(*tracker_node)); + if (!tracker_node) { + retval = LTTNG_ERR_NOMEM; + goto end; + } + lttng_ht_node_init_ulong(&tracker_node->node, (unsigned long) id); + lttng_ht_add_unique_ulong(id_tracker->ht, &tracker_node->node); +end: + return retval; +} + +static int id_tracker_del_id(struct ust_id_tracker *id_tracker, int id) +{ + int retval = LTTNG_OK, ret; + struct ust_id_tracker_node *tracker_node; + struct lttng_ht_iter iter; + + if (id < 0) { + retval = LTTNG_ERR_INVALID; + goto end; + } + tracker_node = id_tracker_lookup(id_tracker, id, &iter); + if (!tracker_node) { + /* Not found */ + retval = LTTNG_ERR_PROCESS_ATTR_MISSING; + goto end; + } + ret = lttng_ht_del(id_tracker->ht, &iter); + LTTNG_ASSERT(!ret); + + destroy_id_tracker_node(tracker_node); +end: + return retval; +} + +static struct ust_id_tracker *get_id_tracker(struct ltt_ust_session *session, + enum lttng_process_attr process_attr) +{ + switch (process_attr) { + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + return &session->vpid_tracker; + case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: + return &session->vuid_tracker; + case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: + return &session->vgid_tracker; + default: + return NULL; + } +} + +static struct process_attr_tracker *_trace_ust_get_process_attr_tracker( + struct ltt_ust_session *session, + enum lttng_process_attr process_attr) +{ + switch (process_attr) { + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + return session->tracker_vpid; + case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: + return session->tracker_vuid; + case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: + return session->tracker_vgid; + default: + return NULL; + } +} + +const struct process_attr_tracker *trace_ust_get_process_attr_tracker( + struct ltt_ust_session *session, + enum lttng_process_attr process_attr) +{ + return (const struct process_attr_tracker *) + _trace_ust_get_process_attr_tracker( + session, process_attr); +} + +/* + * The session lock is held when calling this function. + */ +int trace_ust_id_tracker_lookup(enum lttng_process_attr process_attr, + struct ltt_ust_session *session, + int id) +{ + struct lttng_ht_iter iter; + struct ust_id_tracker *id_tracker; + + id_tracker = get_id_tracker(session, process_attr); + if (!id_tracker) { + abort(); + } + if (!id_tracker->ht) { + return 1; + } + if (id_tracker_lookup(id_tracker, id, &iter)) { + return 1; + } + return 0; +} + +/* + * Called with the session lock held. + */ +enum lttng_error_code trace_ust_process_attr_tracker_set_tracking_policy( + struct ltt_ust_session *session, + enum lttng_process_attr process_attr, + enum lttng_tracking_policy policy) +{ + int ret; + enum lttng_error_code ret_code = LTTNG_OK; + struct ust_id_tracker *id_tracker = + get_id_tracker(session, process_attr); + struct process_attr_tracker *tracker = + _trace_ust_get_process_attr_tracker( + session, process_attr); + bool should_update_apps = false; + enum lttng_tracking_policy previous_policy; + + if (!tracker) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + previous_policy = process_attr_tracker_get_tracking_policy(tracker); + ret = process_attr_tracker_set_tracking_policy(tracker, policy); + if (ret) { + ret_code = LTTNG_ERR_UNK; + goto end; + } + + if (previous_policy == policy) { + goto end; + } + + switch (policy) { + case LTTNG_TRACKING_POLICY_INCLUDE_ALL: + /* Track all values: destroy tracker if exists. */ + if (id_tracker->ht) { + fini_id_tracker(id_tracker); + /* Ensure all apps have session. */ + should_update_apps = true; + } + break; + case LTTNG_TRACKING_POLICY_EXCLUDE_ALL: + case LTTNG_TRACKING_POLICY_INCLUDE_SET: + /* fall-through. */ + fini_id_tracker(id_tracker); + ret_code = (lttng_error_code) init_id_tracker(id_tracker); + if (ret_code != LTTNG_OK) { + ERR("Error initializing ID tracker"); + goto end; + } + /* Remove all apps from session. */ + should_update_apps = true; + break; + default: + abort(); + } + if (should_update_apps && session->active) { + ust_app_global_update_all(session); + } +end: + return ret_code; +} + +/* Called with the session lock held. */ +enum lttng_error_code trace_ust_process_attr_tracker_inclusion_set_add_value( + struct ltt_ust_session *session, + enum lttng_process_attr process_attr, + const struct process_attr_value *value) +{ + enum lttng_error_code ret_code = LTTNG_OK; + bool should_update_apps = false; + struct ust_id_tracker *id_tracker = + get_id_tracker(session, process_attr); + struct process_attr_tracker *tracker; + int integral_value; + enum process_attr_tracker_status status; + struct ust_app *app; + + /* + * Convert process attribute tracker value to the integral + * representation required by the kern-ctl API. + */ + switch (process_attr) { + case LTTNG_PROCESS_ATTR_PROCESS_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + integral_value = (int) value->value.pid; + break; + case LTTNG_PROCESS_ATTR_USER_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: + if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME) { + uid_t uid; + + ret_code = utils_user_id_from_name( + value->value.user_name, &uid); + if (ret_code != LTTNG_OK) { + goto end; + } + integral_value = (int) uid; + } else { + integral_value = (int) value->value.uid; + } + break; + case LTTNG_PROCESS_ATTR_GROUP_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: + if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME) { + gid_t gid; + + ret_code = utils_group_id_from_name( + value->value.group_name, &gid); + if (ret_code != LTTNG_OK) { + goto end; + } + integral_value = (int) gid; + } else { + integral_value = (int) value->value.gid; + } + break; + default: + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + tracker = _trace_ust_get_process_attr_tracker(session, process_attr); + if (!tracker) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + status = process_attr_tracker_inclusion_set_add_value(tracker, value); + if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { + switch (status) { + case PROCESS_ATTR_TRACKER_STATUS_EXISTS: + ret_code = LTTNG_ERR_PROCESS_ATTR_EXISTS; + break; + case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: + ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; + break; + case PROCESS_ATTR_TRACKER_STATUS_ERROR: + default: + ret_code = LTTNG_ERR_UNK; + break; + } + goto end; + } + + DBG("User space track %s %d for session id %" PRIu64, + lttng_process_attr_to_string(process_attr), + integral_value, session->id); + + ret_code = (lttng_error_code) id_tracker_add_id(id_tracker, integral_value); + if (ret_code != LTTNG_OK) { + goto end; + } + /* Add session to application */ + switch (process_attr) { + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + app = ust_app_find_by_pid(integral_value); + if (app) { + should_update_apps = true; + } + break; + default: + should_update_apps = true; + break; + } + if (should_update_apps && session->active) { + ust_app_global_update_all(session); + } +end: + return ret_code; +} + +/* Called with the session lock held. */ +enum lttng_error_code trace_ust_process_attr_tracker_inclusion_set_remove_value( + struct ltt_ust_session *session, + enum lttng_process_attr process_attr, + const struct process_attr_value *value) +{ + enum lttng_error_code ret_code = LTTNG_OK; + bool should_update_apps = false; + struct ust_id_tracker *id_tracker = + get_id_tracker(session, process_attr); + struct process_attr_tracker *tracker; + int integral_value; + enum process_attr_tracker_status status; + struct ust_app *app; + + /* + * Convert process attribute tracker value to the integral + * representation required by the kern-ctl API. + */ + switch (process_attr) { + case LTTNG_PROCESS_ATTR_PROCESS_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + integral_value = (int) value->value.pid; + break; + case LTTNG_PROCESS_ATTR_USER_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID: + if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_USER_NAME) { + uid_t uid; + + ret_code = utils_user_id_from_name( + value->value.user_name, &uid); + if (ret_code != LTTNG_OK) { + goto end; + } + integral_value = (int) uid; + } else { + integral_value = (int) value->value.uid; + } + break; + case LTTNG_PROCESS_ATTR_GROUP_ID: + case LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID: + if (value->type == LTTNG_PROCESS_ATTR_VALUE_TYPE_GROUP_NAME) { + gid_t gid; + + ret_code = utils_group_id_from_name( + value->value.group_name, &gid); + if (ret_code != LTTNG_OK) { + goto end; + } + integral_value = (int) gid; + } else { + integral_value = (int) value->value.gid; + } + break; + default: + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + tracker = _trace_ust_get_process_attr_tracker(session, process_attr); + if (!tracker) { + ret_code = LTTNG_ERR_INVALID; + goto end; + } + + status = process_attr_tracker_inclusion_set_remove_value( + tracker, value); + if (status != PROCESS_ATTR_TRACKER_STATUS_OK) { + switch (status) { + case PROCESS_ATTR_TRACKER_STATUS_MISSING: + ret_code = LTTNG_ERR_PROCESS_ATTR_MISSING; + break; + case PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY: + ret_code = LTTNG_ERR_PROCESS_ATTR_TRACKER_INVALID_TRACKING_POLICY; + break; + case PROCESS_ATTR_TRACKER_STATUS_ERROR: + default: + ret_code = LTTNG_ERR_UNK; + break; + } + goto end; + } + + DBG("User space untrack %s %d for session id %" PRIu64, + lttng_process_attr_to_string(process_attr), + integral_value, session->id); + + ret_code = (lttng_error_code) id_tracker_del_id(id_tracker, integral_value); + if (ret_code != LTTNG_OK) { + goto end; + } + /* Add session to application */ + switch (process_attr) { + case LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID: + app = ust_app_find_by_pid(integral_value); + if (app) { + should_update_apps = true; + } + break; + default: + should_update_apps = true; + break; + } + if (should_update_apps && session->active) { + ust_app_global_update_all(session); + } +end: + return ret_code; +} + +/* + * RCU safe free context structure. + */ +static void destroy_context_rcu(struct rcu_head *head) +{ + struct lttng_ht_node_ulong *node = + caa_container_of(head, struct lttng_ht_node_ulong, head); + struct ltt_ust_context *ctx = + caa_container_of(node, struct ltt_ust_context, node); + + trace_ust_destroy_context(ctx); +} + +/* + * Cleanup UST context hash table. + */ +static void destroy_contexts(struct lttng_ht *ht) +{ + int ret; + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter iter; + struct ltt_ust_context *ctx; + + LTTNG_ASSERT(ht); + + rcu_read_lock(); + cds_lfht_for_each_entry(ht->ht, &iter.iter, node, node) { + /* Remove from ordered list. */ + ctx = caa_container_of(node, struct ltt_ust_context, node); + cds_list_del(&ctx->list); + /* Remove from channel's hash table. */ + ret = lttng_ht_del(ht, &iter); + if (!ret) { + call_rcu(&node->head, destroy_context_rcu); + } + } + rcu_read_unlock(); + + ht_cleanup_push(ht); +} + +/* + * Cleanup ust event structure. + */ +void trace_ust_destroy_event(struct ltt_ust_event *event) +{ + LTTNG_ASSERT(event); + + DBG2("Trace destroy UST event %s", event->attr.name); + free(event->filter_expression); + free(event->filter); + free(event->exclusion); + free(event); +} + +/* + * Cleanup ust context structure. + */ +void trace_ust_destroy_context(struct ltt_ust_context *ctx) +{ + LTTNG_ASSERT(ctx); + + if (ctx->ctx.ctx == LTTNG_UST_ABI_CONTEXT_APP_CONTEXT) { + free(ctx->ctx.u.app_ctx.provider_name); + free(ctx->ctx.u.app_ctx.ctx_name); + } + free(ctx); +} + +/* + * URCU intermediate call to complete destroy event. + */ +static void destroy_event_rcu(struct rcu_head *head) +{ + struct lttng_ht_node_str *node = + caa_container_of(head, struct lttng_ht_node_str, head); + struct ltt_ust_event *event = + caa_container_of(node, struct ltt_ust_event, node); + + trace_ust_destroy_event(event); +} + +/* + * Cleanup UST events hashtable. + */ +static void destroy_events(struct lttng_ht *events) +{ + int ret; + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(events); + + rcu_read_lock(); + cds_lfht_for_each_entry(events->ht, &iter.iter, node, node) { + ret = lttng_ht_del(events, &iter); + LTTNG_ASSERT(!ret); + call_rcu(&node->head, destroy_event_rcu); + } + rcu_read_unlock(); + + ht_cleanup_push(events); +} + +/* + * Cleanup ust channel structure. + * + * Should _NOT_ be called with RCU read lock held. + */ +static void _trace_ust_destroy_channel(struct ltt_ust_channel *channel) +{ + LTTNG_ASSERT(channel); + + DBG2("Trace destroy UST channel %s", channel->name); + + free(channel); +} + +/* + * URCU intermediate call to complete destroy channel. + */ +static void destroy_channel_rcu(struct rcu_head *head) +{ + struct lttng_ht_node_str *node = + caa_container_of(head, struct lttng_ht_node_str, head); + struct ltt_ust_channel *channel = + caa_container_of(node, struct ltt_ust_channel, node); + + _trace_ust_destroy_channel(channel); +} + +void trace_ust_destroy_channel(struct ltt_ust_channel *channel) +{ + /* Destroying all events of the channel */ + destroy_events(channel->events); + /* Destroying all context of the channel */ + destroy_contexts(channel->ctx); + + call_rcu(&channel->node.head, destroy_channel_rcu); +} + +/* + * Remove an UST channel from a channel HT. + */ +void trace_ust_delete_channel(struct lttng_ht *ht, + struct ltt_ust_channel *channel) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(ht); + LTTNG_ASSERT(channel); + + iter.iter.node = &channel->node.node; + ret = lttng_ht_del(ht, &iter); + LTTNG_ASSERT(!ret); +} + +/* + * Iterate over a hash table containing channels and cleanup safely. + */ +static void destroy_channels(struct lttng_ht *channels) +{ + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(channels); + + rcu_read_lock(); + cds_lfht_for_each_entry(channels->ht, &iter.iter, node, node) { + struct ltt_ust_channel *chan = + caa_container_of(node, struct ltt_ust_channel, node); + + trace_ust_delete_channel(channels, chan); + trace_ust_destroy_channel(chan); + } + rcu_read_unlock(); + + ht_cleanup_push(channels); +} + +/* + * Cleanup UST global domain. + */ +static void destroy_domain_global(struct ltt_ust_domain_global *dom) +{ + LTTNG_ASSERT(dom); + + destroy_channels(dom->channels); +} + +/* + * Cleanup ust session structure, keeping data required by + * destroy notifier. + * + * Should *NOT* be called with RCU read-side lock held. + */ +void trace_ust_destroy_session(struct ltt_ust_session *session) +{ + struct agent *agt; + struct buffer_reg_uid *reg, *sreg; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(session); + + DBG2("Trace UST destroy session %" PRIu64, session->id); + + /* Cleaning up UST domain */ + destroy_domain_global(&session->domain_global); + + rcu_read_lock(); + cds_lfht_for_each_entry(session->agents->ht, &iter.iter, agt, node.node) { + int ret = lttng_ht_del(session->agents, &iter); + + LTTNG_ASSERT(!ret); + agent_destroy(agt); + } + rcu_read_unlock(); + + ht_cleanup_push(session->agents); + + /* Cleanup UID buffer registry object(s). */ + cds_list_for_each_entry_safe(reg, sreg, &session->buffer_reg_uid_list, + lnode) { + cds_list_del(®->lnode); + buffer_reg_uid_remove(reg); + buffer_reg_uid_destroy(reg, session->consumer); + } + + process_attr_tracker_destroy(session->tracker_vpid); + process_attr_tracker_destroy(session->tracker_vuid); + process_attr_tracker_destroy(session->tracker_vgid); + + fini_id_tracker(&session->vpid_tracker); + fini_id_tracker(&session->vuid_tracker); + fini_id_tracker(&session->vgid_tracker); + lttng_trace_chunk_put(session->current_trace_chunk); +} + +/* Free elements needed by destroy notifiers. */ +void trace_ust_free_session(struct ltt_ust_session *session) +{ + consumer_output_put(session->consumer); + free(session); +} diff --git a/src/bin/lttng-sessiond/tracker.c b/src/bin/lttng-sessiond/tracker.c deleted file mode 100644 index 8293904fb..000000000 --- a/src/bin/lttng-sessiond/tracker.c +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2018 Mathieu Desnoyers - * Copyright (C) 2020 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "lttng/tracker.h" -#include "common/dynamic-array.h" -#include "common/macros.h" -#define _LGPL_SOURCE -#include -#include -#include -#include -#include -#include -#include - -#include "tracker.h" -#include -#include -#include -#include -#include -#include - -struct process_attr_tracker_value_node { - struct process_attr_value *value; - struct cds_lfht_node inclusion_set_ht_node; - struct rcu_head rcu_head; -}; - -struct process_attr_tracker { - enum lttng_tracking_policy policy; - struct cds_lfht *inclusion_set_ht; -}; - -static void process_attr_tracker_value_node_rcu_free(struct rcu_head *rcu_head) -{ - struct process_attr_tracker_value_node *node = - container_of(rcu_head, typeof(*node), rcu_head); - - free(node); -} - -struct process_attr_tracker *process_attr_tracker_create(void) -{ - struct process_attr_tracker *tracker; - - tracker = zmalloc(sizeof(*tracker)); - if (!tracker) { - return NULL; - } - - (void) process_attr_tracker_set_tracking_policy( - tracker, LTTNG_TRACKING_POLICY_INCLUDE_ALL); - - tracker->inclusion_set_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, - CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - if (!tracker->inclusion_set_ht) { - goto error; - } - - return tracker; -error: - process_attr_tracker_destroy(tracker); - return NULL; -} - -static void process_attr_tracker_remove_value_node( - struct process_attr_tracker *tracker, - struct process_attr_tracker_value_node *value_node) -{ - cds_lfht_del(tracker->inclusion_set_ht, - &value_node->inclusion_set_ht_node); - process_attr_value_destroy(value_node->value); - call_rcu(&value_node->rcu_head, - process_attr_tracker_value_node_rcu_free); -} - -static void process_attr_tracker_clear_inclusion_set( - struct process_attr_tracker *tracker) -{ - int ret; - struct lttng_ht_iter iter; - struct process_attr_tracker_value_node *value_node; - - if (!tracker->inclusion_set_ht) { - return; - } - - rcu_read_lock(); - cds_lfht_for_each_entry (tracker->inclusion_set_ht, &iter.iter, - value_node, inclusion_set_ht_node) { - process_attr_tracker_remove_value_node(tracker, value_node); - } - rcu_read_unlock(); - ret = cds_lfht_destroy(tracker->inclusion_set_ht, NULL); - LTTNG_ASSERT(ret == 0); - tracker->inclusion_set_ht = NULL; -} - -static int process_attr_tracker_create_inclusion_set( - struct process_attr_tracker *tracker) -{ - LTTNG_ASSERT(!tracker->inclusion_set_ht); - tracker->inclusion_set_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, - CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); - return tracker->inclusion_set_ht ? 0 : -1; -} - -void process_attr_tracker_destroy(struct process_attr_tracker *tracker) -{ - if (!tracker) { - return; - } - - process_attr_tracker_clear_inclusion_set(tracker); - free(tracker); -} - -enum lttng_tracking_policy process_attr_tracker_get_tracking_policy( - const struct process_attr_tracker *tracker) -{ - return tracker->policy; -} - -int process_attr_tracker_set_tracking_policy( - struct process_attr_tracker *tracker, - enum lttng_tracking_policy tracking_policy) -{ - int ret = 0; - - if (tracker->policy == tracking_policy) { - goto end; - } - - process_attr_tracker_clear_inclusion_set(tracker); - ret = process_attr_tracker_create_inclusion_set(tracker); - if (ret) { - goto end; - } - tracker->policy = tracking_policy; -end: - return ret; -} - -static int match_inclusion_set_value( - struct cds_lfht_node *node, const void *key) -{ - const struct process_attr_value *value_key = key; - const struct process_attr_tracker_value_node *value_node = - caa_container_of(node, - struct process_attr_tracker_value_node, - inclusion_set_ht_node); - - return process_attr_tracker_value_equal(value_node->value, value_key); -} - -static struct process_attr_tracker_value_node *process_attr_tracker_lookup( - const struct process_attr_tracker *tracker, - const struct process_attr_value *value) -{ - struct cds_lfht_iter iter; - struct cds_lfht_node *node; - - LTTNG_ASSERT(tracker->policy == LTTNG_TRACKING_POLICY_INCLUDE_SET); - - rcu_read_lock(); - cds_lfht_lookup(tracker->inclusion_set_ht, - process_attr_value_hash(value), - match_inclusion_set_value, value, &iter); - node = cds_lfht_iter_get_node(&iter); - rcu_read_unlock(); - - return node ? container_of(node, struct process_attr_tracker_value_node, - inclusion_set_ht_node) : - NULL; -} - -/* Protected by session mutex held by caller. */ -enum process_attr_tracker_status process_attr_tracker_inclusion_set_add_value( - struct process_attr_tracker *tracker, - const struct process_attr_value *value) -{ - enum process_attr_tracker_status status = - PROCESS_ATTR_TRACKER_STATUS_OK; - struct process_attr_value *value_copy = NULL; - struct process_attr_tracker_value_node *value_node = NULL; - - rcu_read_lock(); - if (tracker->policy != LTTNG_TRACKING_POLICY_INCLUDE_SET) { - status = PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY; - goto end; - } - - if (process_attr_tracker_lookup(tracker, value)) { - status = PROCESS_ATTR_TRACKER_STATUS_EXISTS; - goto end; - } - - value_node = zmalloc(sizeof(*value_node)); - if (!value_node) { - status = PROCESS_ATTR_TRACKER_STATUS_ERROR; - goto end; - } - - value_copy = process_attr_value_copy(value); - if (!value_copy) { - status = PROCESS_ATTR_TRACKER_STATUS_ERROR; - goto end; - } - - value_node->value = value_copy; - cds_lfht_add(tracker->inclusion_set_ht, - process_attr_value_hash(value_copy), - &value_node->inclusion_set_ht_node); - value_copy = NULL; - value_node = NULL; -end: - if (value_copy) { - process_attr_value_destroy(value_copy); - } - if (value_node) { - free(value_node); - } - rcu_read_unlock(); - return status; -} - -/* Protected by session mutex held by caller. */ -enum process_attr_tracker_status -process_attr_tracker_inclusion_set_remove_value( - struct process_attr_tracker *tracker, - const struct process_attr_value *value) -{ - struct process_attr_tracker_value_node *value_node; - enum process_attr_tracker_status status = - PROCESS_ATTR_TRACKER_STATUS_OK; - - rcu_read_lock(); - if (tracker->policy != LTTNG_TRACKING_POLICY_INCLUDE_SET) { - status = PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY; - goto end; - } - - value_node = process_attr_tracker_lookup(tracker, value); - if (!value_node) { - status = PROCESS_ATTR_TRACKER_STATUS_MISSING; - goto end; - } - - process_attr_tracker_remove_value_node(tracker, value_node); -end: - rcu_read_unlock(); - return status; -} - -enum process_attr_tracker_status process_attr_tracker_get_inclusion_set( - const struct process_attr_tracker *tracker, - struct lttng_process_attr_values **_values) -{ - struct lttng_ht_iter iter; - struct process_attr_tracker_value_node *value_node; - enum process_attr_tracker_status status = - PROCESS_ATTR_TRACKER_STATUS_OK; - struct lttng_process_attr_values *values; - struct process_attr_value *new_value = NULL; - - values = lttng_process_attr_values_create(); - if (!values) { - status = PROCESS_ATTR_TRACKER_STATUS_ERROR; - goto error; - } - - if (tracker->policy != LTTNG_TRACKING_POLICY_INCLUDE_SET) { - status = PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY; - goto error; - } - - rcu_read_lock(); - cds_lfht_for_each_entry (tracker->inclusion_set_ht, &iter.iter, - value_node, inclusion_set_ht_node) { - int ret; - - new_value = process_attr_value_copy(value_node->value); - if (!new_value) { - status = PROCESS_ATTR_TRACKER_STATUS_ERROR; - goto error_unlock; - } - - ret = lttng_dynamic_pointer_array_add_pointer( - &values->array, new_value); - if (ret) { - status = PROCESS_ATTR_TRACKER_STATUS_ERROR; - goto error_unlock; - } - - new_value = NULL; - } - rcu_read_unlock(); - *_values = values; - return status; -error_unlock: - rcu_read_unlock(); -error: - lttng_process_attr_values_destroy(values); - process_attr_value_destroy(new_value); - return status; -} diff --git a/src/bin/lttng-sessiond/tracker.cpp b/src/bin/lttng-sessiond/tracker.cpp new file mode 100644 index 000000000..1cae40350 --- /dev/null +++ b/src/bin/lttng-sessiond/tracker.cpp @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2018 Mathieu Desnoyers + * Copyright (C) 2020 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "lttng/tracker.h" +#include "common/dynamic-array.h" +#include "common/macros.h" +#define _LGPL_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "tracker.h" +#include +#include +#include +#include +#include +#include + +struct process_attr_tracker_value_node { + struct process_attr_value *value; + struct cds_lfht_node inclusion_set_ht_node; + struct rcu_head rcu_head; +}; + +struct process_attr_tracker { + enum lttng_tracking_policy policy; + struct cds_lfht *inclusion_set_ht; +}; + +static void process_attr_tracker_value_node_rcu_free(struct rcu_head *rcu_head) +{ + struct process_attr_tracker_value_node *node = + container_of(rcu_head, typeof(*node), rcu_head); + + free(node); +} + +struct process_attr_tracker *process_attr_tracker_create(void) +{ + struct process_attr_tracker *tracker; + + tracker = (process_attr_tracker *) zmalloc(sizeof(*tracker)); + if (!tracker) { + return NULL; + } + + (void) process_attr_tracker_set_tracking_policy( + tracker, LTTNG_TRACKING_POLICY_INCLUDE_ALL); + + tracker->inclusion_set_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!tracker->inclusion_set_ht) { + goto error; + } + + return tracker; +error: + process_attr_tracker_destroy(tracker); + return NULL; +} + +static void process_attr_tracker_remove_value_node( + struct process_attr_tracker *tracker, + struct process_attr_tracker_value_node *value_node) +{ + cds_lfht_del(tracker->inclusion_set_ht, + &value_node->inclusion_set_ht_node); + process_attr_value_destroy(value_node->value); + call_rcu(&value_node->rcu_head, + process_attr_tracker_value_node_rcu_free); +} + +static void process_attr_tracker_clear_inclusion_set( + struct process_attr_tracker *tracker) +{ + int ret; + struct lttng_ht_iter iter; + struct process_attr_tracker_value_node *value_node; + + if (!tracker->inclusion_set_ht) { + return; + } + + rcu_read_lock(); + cds_lfht_for_each_entry (tracker->inclusion_set_ht, &iter.iter, + value_node, inclusion_set_ht_node) { + process_attr_tracker_remove_value_node(tracker, value_node); + } + rcu_read_unlock(); + ret = cds_lfht_destroy(tracker->inclusion_set_ht, NULL); + LTTNG_ASSERT(ret == 0); + tracker->inclusion_set_ht = NULL; +} + +static int process_attr_tracker_create_inclusion_set( + struct process_attr_tracker *tracker) +{ + LTTNG_ASSERT(!tracker->inclusion_set_ht); + tracker->inclusion_set_ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + return tracker->inclusion_set_ht ? 0 : -1; +} + +void process_attr_tracker_destroy(struct process_attr_tracker *tracker) +{ + if (!tracker) { + return; + } + + process_attr_tracker_clear_inclusion_set(tracker); + free(tracker); +} + +enum lttng_tracking_policy process_attr_tracker_get_tracking_policy( + const struct process_attr_tracker *tracker) +{ + return tracker->policy; +} + +int process_attr_tracker_set_tracking_policy( + struct process_attr_tracker *tracker, + enum lttng_tracking_policy tracking_policy) +{ + int ret = 0; + + if (tracker->policy == tracking_policy) { + goto end; + } + + process_attr_tracker_clear_inclusion_set(tracker); + ret = process_attr_tracker_create_inclusion_set(tracker); + if (ret) { + goto end; + } + tracker->policy = tracking_policy; +end: + return ret; +} + +static int match_inclusion_set_value( + struct cds_lfht_node *node, const void *key) +{ + const struct process_attr_value *value_key = (process_attr_value *) key; + const struct process_attr_tracker_value_node *value_node = + caa_container_of(node, + struct process_attr_tracker_value_node, + inclusion_set_ht_node); + + return process_attr_tracker_value_equal(value_node->value, value_key); +} + +static struct process_attr_tracker_value_node *process_attr_tracker_lookup( + const struct process_attr_tracker *tracker, + const struct process_attr_value *value) +{ + struct cds_lfht_iter iter; + struct cds_lfht_node *node; + + LTTNG_ASSERT(tracker->policy == LTTNG_TRACKING_POLICY_INCLUDE_SET); + + rcu_read_lock(); + cds_lfht_lookup(tracker->inclusion_set_ht, + process_attr_value_hash(value), + match_inclusion_set_value, value, &iter); + node = cds_lfht_iter_get_node(&iter); + rcu_read_unlock(); + + return node ? container_of(node, struct process_attr_tracker_value_node, + inclusion_set_ht_node) : + NULL; +} + +/* Protected by session mutex held by caller. */ +enum process_attr_tracker_status process_attr_tracker_inclusion_set_add_value( + struct process_attr_tracker *tracker, + const struct process_attr_value *value) +{ + enum process_attr_tracker_status status = + PROCESS_ATTR_TRACKER_STATUS_OK; + struct process_attr_value *value_copy = NULL; + struct process_attr_tracker_value_node *value_node = NULL; + + rcu_read_lock(); + if (tracker->policy != LTTNG_TRACKING_POLICY_INCLUDE_SET) { + status = PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY; + goto end; + } + + if (process_attr_tracker_lookup(tracker, value)) { + status = PROCESS_ATTR_TRACKER_STATUS_EXISTS; + goto end; + } + + value_node = (process_attr_tracker_value_node *) zmalloc(sizeof(*value_node)); + if (!value_node) { + status = PROCESS_ATTR_TRACKER_STATUS_ERROR; + goto end; + } + + value_copy = process_attr_value_copy(value); + if (!value_copy) { + status = PROCESS_ATTR_TRACKER_STATUS_ERROR; + goto end; + } + + value_node->value = value_copy; + cds_lfht_add(tracker->inclusion_set_ht, + process_attr_value_hash(value_copy), + &value_node->inclusion_set_ht_node); + value_copy = NULL; + value_node = NULL; +end: + if (value_copy) { + process_attr_value_destroy(value_copy); + } + if (value_node) { + free(value_node); + } + rcu_read_unlock(); + return status; +} + +/* Protected by session mutex held by caller. */ +enum process_attr_tracker_status +process_attr_tracker_inclusion_set_remove_value( + struct process_attr_tracker *tracker, + const struct process_attr_value *value) +{ + struct process_attr_tracker_value_node *value_node; + enum process_attr_tracker_status status = + PROCESS_ATTR_TRACKER_STATUS_OK; + + rcu_read_lock(); + if (tracker->policy != LTTNG_TRACKING_POLICY_INCLUDE_SET) { + status = PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY; + goto end; + } + + value_node = process_attr_tracker_lookup(tracker, value); + if (!value_node) { + status = PROCESS_ATTR_TRACKER_STATUS_MISSING; + goto end; + } + + process_attr_tracker_remove_value_node(tracker, value_node); +end: + rcu_read_unlock(); + return status; +} + +enum process_attr_tracker_status process_attr_tracker_get_inclusion_set( + const struct process_attr_tracker *tracker, + struct lttng_process_attr_values **_values) +{ + struct lttng_ht_iter iter; + struct process_attr_tracker_value_node *value_node; + enum process_attr_tracker_status status = + PROCESS_ATTR_TRACKER_STATUS_OK; + struct lttng_process_attr_values *values; + struct process_attr_value *new_value = NULL; + + values = lttng_process_attr_values_create(); + if (!values) { + status = PROCESS_ATTR_TRACKER_STATUS_ERROR; + goto error; + } + + if (tracker->policy != LTTNG_TRACKING_POLICY_INCLUDE_SET) { + status = PROCESS_ATTR_TRACKER_STATUS_INVALID_TRACKING_POLICY; + goto error; + } + + rcu_read_lock(); + cds_lfht_for_each_entry (tracker->inclusion_set_ht, &iter.iter, + value_node, inclusion_set_ht_node) { + int ret; + + new_value = process_attr_value_copy(value_node->value); + if (!new_value) { + status = PROCESS_ATTR_TRACKER_STATUS_ERROR; + goto error_unlock; + } + + ret = lttng_dynamic_pointer_array_add_pointer( + &values->array, new_value); + if (ret) { + status = PROCESS_ATTR_TRACKER_STATUS_ERROR; + goto error_unlock; + } + + new_value = NULL; + } + rcu_read_unlock(); + *_values = values; + return status; +error_unlock: + rcu_read_unlock(); +error: + lttng_process_attr_values_destroy(values); + process_attr_value_destroy(new_value); + return status; +} diff --git a/src/bin/lttng-sessiond/trigger-error-query.c b/src/bin/lttng-sessiond/trigger-error-query.c deleted file mode 100644 index 8c39e1b06..000000000 --- a/src/bin/lttng-sessiond/trigger-error-query.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2021 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include "event-notifier-error-accounting.h" -#include -#include -#include - -enum lttng_trigger_status lttng_trigger_add_error_results( - const struct lttng_trigger *trigger, - struct lttng_error_query_results *results) -{ - return LTTNG_TRIGGER_STATUS_OK; -} - -enum lttng_trigger_status lttng_trigger_condition_add_error_results( - const struct lttng_trigger *trigger, - struct lttng_error_query_results *results) -{ - enum lttng_trigger_status status; - uint64_t discarded_tracer_messages_count; - enum event_notifier_error_accounting_status error_accounting_status; - struct lttng_error_query_result *discarded_tracer_messages_counter = NULL; - const char *trigger_name; - uid_t trigger_owner; - - status = lttng_trigger_get_name(trigger, &trigger_name); - trigger_name = status == LTTNG_TRIGGER_STATUS_OK ? - trigger_name : "(anonymous)"; - status = lttng_trigger_get_owner_uid(trigger, - &trigger_owner); - LTTNG_ASSERT(status == LTTNG_TRIGGER_STATUS_OK); - - /* - * Only add discarded tracer messages count for applicable conditions. - * As of 2.13, only "event rule matches" conditions can generate - * reportable errors hence why this function is very specific to this - * condition type. - */ - if (!lttng_trigger_needs_tracer_notifier(trigger)) { - status = LTTNG_TRIGGER_STATUS_OK; - goto end; - } - - error_accounting_status = event_notifier_error_accounting_get_count( - trigger, &discarded_tracer_messages_count); - if (error_accounting_status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - ERR("Failed to retrieve tracer discarded messages count for trigger: trigger name = '%s', trigger owner uid = %d", - trigger_name, (int) trigger_owner); - status = LTTNG_TRIGGER_STATUS_ERROR; - goto end; - } - - discarded_tracer_messages_counter = lttng_error_query_result_counter_create( - "discarded tracer messages", - "Count of messages discarded by the tracer due to a communication error with the session daemon", - discarded_tracer_messages_count); - if (!discarded_tracer_messages_counter) { - status = LTTNG_TRIGGER_STATUS_ERROR; - goto end; - } - - if (lttng_error_query_results_add_result( - results, discarded_tracer_messages_counter)) { - status = LTTNG_TRIGGER_STATUS_ERROR; - goto end; - } - - /* Ownership transferred to the results. */ - discarded_tracer_messages_counter = NULL; - - status = LTTNG_TRIGGER_STATUS_OK; -end: - lttng_error_query_result_destroy(discarded_tracer_messages_counter); - return status; -} - -enum lttng_trigger_status lttng_trigger_add_action_error_query_results( - struct lttng_trigger *trigger, - struct lttng_error_query_results *results) -{ - enum lttng_trigger_status status; - const char *trigger_name; - uid_t trigger_owner; - enum lttng_action_status action_status; - - status = lttng_trigger_get_name(trigger, &trigger_name); - trigger_name = status == LTTNG_TRIGGER_STATUS_OK ? - trigger_name : "(anonymous)"; - status = lttng_trigger_get_owner_uid(trigger, - &trigger_owner); - LTTNG_ASSERT(status == LTTNG_TRIGGER_STATUS_OK); - - action_status = lttng_action_add_error_query_results( - lttng_trigger_get_action(trigger), results); - switch (action_status) { - case LTTNG_ACTION_STATUS_OK: - break; - default: - status = LTTNG_TRIGGER_STATUS_ERROR; - goto end; - } - - status = LTTNG_TRIGGER_STATUS_OK; -end: - return status; -} diff --git a/src/bin/lttng-sessiond/trigger-error-query.cpp b/src/bin/lttng-sessiond/trigger-error-query.cpp new file mode 100644 index 000000000..8c39e1b06 --- /dev/null +++ b/src/bin/lttng-sessiond/trigger-error-query.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include "event-notifier-error-accounting.h" +#include +#include +#include + +enum lttng_trigger_status lttng_trigger_add_error_results( + const struct lttng_trigger *trigger, + struct lttng_error_query_results *results) +{ + return LTTNG_TRIGGER_STATUS_OK; +} + +enum lttng_trigger_status lttng_trigger_condition_add_error_results( + const struct lttng_trigger *trigger, + struct lttng_error_query_results *results) +{ + enum lttng_trigger_status status; + uint64_t discarded_tracer_messages_count; + enum event_notifier_error_accounting_status error_accounting_status; + struct lttng_error_query_result *discarded_tracer_messages_counter = NULL; + const char *trigger_name; + uid_t trigger_owner; + + status = lttng_trigger_get_name(trigger, &trigger_name); + trigger_name = status == LTTNG_TRIGGER_STATUS_OK ? + trigger_name : "(anonymous)"; + status = lttng_trigger_get_owner_uid(trigger, + &trigger_owner); + LTTNG_ASSERT(status == LTTNG_TRIGGER_STATUS_OK); + + /* + * Only add discarded tracer messages count for applicable conditions. + * As of 2.13, only "event rule matches" conditions can generate + * reportable errors hence why this function is very specific to this + * condition type. + */ + if (!lttng_trigger_needs_tracer_notifier(trigger)) { + status = LTTNG_TRIGGER_STATUS_OK; + goto end; + } + + error_accounting_status = event_notifier_error_accounting_get_count( + trigger, &discarded_tracer_messages_count); + if (error_accounting_status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + ERR("Failed to retrieve tracer discarded messages count for trigger: trigger name = '%s', trigger owner uid = %d", + trigger_name, (int) trigger_owner); + status = LTTNG_TRIGGER_STATUS_ERROR; + goto end; + } + + discarded_tracer_messages_counter = lttng_error_query_result_counter_create( + "discarded tracer messages", + "Count of messages discarded by the tracer due to a communication error with the session daemon", + discarded_tracer_messages_count); + if (!discarded_tracer_messages_counter) { + status = LTTNG_TRIGGER_STATUS_ERROR; + goto end; + } + + if (lttng_error_query_results_add_result( + results, discarded_tracer_messages_counter)) { + status = LTTNG_TRIGGER_STATUS_ERROR; + goto end; + } + + /* Ownership transferred to the results. */ + discarded_tracer_messages_counter = NULL; + + status = LTTNG_TRIGGER_STATUS_OK; +end: + lttng_error_query_result_destroy(discarded_tracer_messages_counter); + return status; +} + +enum lttng_trigger_status lttng_trigger_add_action_error_query_results( + struct lttng_trigger *trigger, + struct lttng_error_query_results *results) +{ + enum lttng_trigger_status status; + const char *trigger_name; + uid_t trigger_owner; + enum lttng_action_status action_status; + + status = lttng_trigger_get_name(trigger, &trigger_name); + trigger_name = status == LTTNG_TRIGGER_STATUS_OK ? + trigger_name : "(anonymous)"; + status = lttng_trigger_get_owner_uid(trigger, + &trigger_owner); + LTTNG_ASSERT(status == LTTNG_TRIGGER_STATUS_OK); + + action_status = lttng_action_add_error_query_results( + lttng_trigger_get_action(trigger), results); + switch (action_status) { + case LTTNG_ACTION_STATUS_OK: + break; + default: + status = LTTNG_TRIGGER_STATUS_ERROR; + goto end; + } + + status = LTTNG_TRIGGER_STATUS_OK; +end: + return status; +} diff --git a/src/bin/lttng-sessiond/ust-app.c b/src/bin/lttng-sessiond/ust-app.c deleted file mode 100644 index d303e8b66..000000000 --- a/src/bin/lttng-sessiond/ust-app.c +++ /dev/null @@ -1,7818 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2016 Jérémie Galarneau - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#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 "buffer-registry.h" -#include "condition-internal.h" -#include "fd-limit.h" -#include "health-sessiond.h" -#include "ust-app.h" -#include "ust-consumer.h" -#include "lttng-ust-ctl.h" -#include "lttng-ust-error.h" -#include "utils.h" -#include "session.h" -#include "lttng-sessiond.h" -#include "notification-thread-commands.h" -#include "rotate.h" -#include "event.h" -#include "event-notifier-error-accounting.h" - - -struct lttng_ht *ust_app_ht; -struct lttng_ht *ust_app_ht_by_sock; -struct lttng_ht *ust_app_ht_by_notify_sock; - -static -int ust_app_flush_app_session(struct ust_app *app, struct ust_app_session *ua_sess); - -/* Next available channel key. Access under next_channel_key_lock. */ -static uint64_t _next_channel_key; -static pthread_mutex_t next_channel_key_lock = PTHREAD_MUTEX_INITIALIZER; - -/* Next available session ID. Access under next_session_id_lock. */ -static uint64_t _next_session_id; -static pthread_mutex_t next_session_id_lock = PTHREAD_MUTEX_INITIALIZER; - -/* - * Return the incremented value of next_channel_key. - */ -static uint64_t get_next_channel_key(void) -{ - uint64_t ret; - - pthread_mutex_lock(&next_channel_key_lock); - ret = ++_next_channel_key; - pthread_mutex_unlock(&next_channel_key_lock); - return ret; -} - -/* - * Return the atomically incremented value of next_session_id. - */ -static uint64_t get_next_session_id(void) -{ - uint64_t ret; - - pthread_mutex_lock(&next_session_id_lock); - ret = ++_next_session_id; - pthread_mutex_unlock(&next_session_id_lock); - return ret; -} - -static void copy_channel_attr_to_ustctl( - struct lttng_ust_ctl_consumer_channel_attr *attr, - struct lttng_ust_abi_channel_attr *uattr) -{ - /* Copy event attributes since the layout is different. */ - attr->subbuf_size = uattr->subbuf_size; - attr->num_subbuf = uattr->num_subbuf; - attr->overwrite = uattr->overwrite; - attr->switch_timer_interval = uattr->switch_timer_interval; - attr->read_timer_interval = uattr->read_timer_interval; - attr->output = uattr->output; - attr->blocking_timeout = uattr->u.s.blocking_timeout; -} - -/* - * Match function for the hash table lookup. - * - * It matches an ust app event based on three attributes which are the event - * name, the filter bytecode and the loglevel. - */ -static int ht_match_ust_app_event(struct cds_lfht_node *node, const void *_key) -{ - struct ust_app_event *event; - const struct ust_app_ht_key *key; - int ev_loglevel_value; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - event = caa_container_of(node, struct ust_app_event, node.node); - key = _key; - ev_loglevel_value = event->attr.loglevel; - - /* Match the 4 elements of the key: name, filter, loglevel, exclusions */ - - /* Event name */ - if (strncmp(event->attr.name, key->name, sizeof(event->attr.name)) != 0) { - goto no_match; - } - - /* Event loglevel. */ - if (ev_loglevel_value != key->loglevel_type) { - if (event->attr.loglevel_type == LTTNG_UST_ABI_LOGLEVEL_ALL - && key->loglevel_type == 0 && - ev_loglevel_value == -1) { - /* - * Match is accepted. This is because on event creation, the - * loglevel is set to -1 if the event loglevel type is ALL so 0 and - * -1 are accepted for this loglevel type since 0 is the one set by - * the API when receiving an enable event. - */ - } else { - goto no_match; - } - } - - /* One of the filters is NULL, fail. */ - if ((key->filter && !event->filter) || (!key->filter && event->filter)) { - goto no_match; - } - - if (key->filter && event->filter) { - /* Both filters exists, check length followed by the bytecode. */ - if (event->filter->len != key->filter->len || - memcmp(event->filter->data, key->filter->data, - event->filter->len) != 0) { - goto no_match; - } - } - - /* One of the exclusions is NULL, fail. */ - if ((key->exclusion && !event->exclusion) || (!key->exclusion && event->exclusion)) { - goto no_match; - } - - if (key->exclusion && event->exclusion) { - /* Both exclusions exists, check count followed by the names. */ - if (event->exclusion->count != key->exclusion->count || - memcmp(event->exclusion->names, key->exclusion->names, - event->exclusion->count * LTTNG_UST_ABI_SYM_NAME_LEN) != 0) { - goto no_match; - } - } - - - /* Match. */ - return 1; - -no_match: - return 0; -} - -/* - * Unique add of an ust app event in the given ht. This uses the custom - * ht_match_ust_app_event match function and the event name as hash. - */ -static void add_unique_ust_app_event(struct ust_app_channel *ua_chan, - struct ust_app_event *event) -{ - struct cds_lfht_node *node_ptr; - struct ust_app_ht_key key; - struct lttng_ht *ht; - - LTTNG_ASSERT(ua_chan); - LTTNG_ASSERT(ua_chan->events); - LTTNG_ASSERT(event); - - ht = ua_chan->events; - key.name = event->attr.name; - key.filter = event->filter; - key.loglevel_type = event->attr.loglevel; - key.exclusion = event->exclusion; - - node_ptr = cds_lfht_add_unique(ht->ht, - ht->hash_fct(event->node.key, lttng_ht_seed), - ht_match_ust_app_event, &key, &event->node.node); - LTTNG_ASSERT(node_ptr == &event->node.node); -} - -/* - * Close the notify socket from the given RCU head object. This MUST be called - * through a call_rcu(). - */ -static void close_notify_sock_rcu(struct rcu_head *head) -{ - int ret; - struct ust_app_notify_sock_obj *obj = - caa_container_of(head, struct ust_app_notify_sock_obj, head); - - /* Must have a valid fd here. */ - LTTNG_ASSERT(obj->fd >= 0); - - ret = close(obj->fd); - if (ret) { - ERR("close notify sock %d RCU", obj->fd); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - - free(obj); -} - -/* - * Return the session registry according to the buffer type of the given - * session. - * - * A registry per UID object MUST exists before calling this function or else - * it LTTNG_ASSERT() if not found. RCU read side lock must be acquired. - */ -static struct ust_registry_session *get_session_registry( - struct ust_app_session *ua_sess) -{ - struct ust_registry_session *registry = NULL; - - LTTNG_ASSERT(ua_sess); - - switch (ua_sess->buffer_type) { - case LTTNG_BUFFER_PER_PID: - { - struct buffer_reg_pid *reg_pid = buffer_reg_pid_find(ua_sess->id); - if (!reg_pid) { - goto error; - } - registry = reg_pid->registry->reg.ust; - break; - } - case LTTNG_BUFFER_PER_UID: - { - struct buffer_reg_uid *reg_uid = buffer_reg_uid_find( - ua_sess->tracing_id, ua_sess->bits_per_long, - lttng_credentials_get_uid(&ua_sess->real_credentials)); - if (!reg_uid) { - goto error; - } - registry = reg_uid->registry->reg.ust; - break; - } - default: - abort(); - }; - -error: - return registry; -} - -/* - * Delete ust context safely. RCU read lock must be held before calling - * this function. - */ -static -void delete_ust_app_ctx(int sock, struct ust_app_ctx *ua_ctx, - struct ust_app *app) -{ - int ret; - - LTTNG_ASSERT(ua_ctx); - - if (ua_ctx->obj) { - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_release_object(sock, ua_ctx->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app release ctx failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app release ctx failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app release ctx obj handle %d failed with ret %d: pid = %d, sock = %d", - ua_ctx->obj->handle, ret, - app->pid, app->sock); - } - } - free(ua_ctx->obj); - } - free(ua_ctx); -} - -/* - * Delete ust app event safely. RCU read lock must be held before calling - * this function. - */ -static -void delete_ust_app_event(int sock, struct ust_app_event *ua_event, - struct ust_app *app) -{ - int ret; - - LTTNG_ASSERT(ua_event); - - free(ua_event->filter); - if (ua_event->exclusion != NULL) - free(ua_event->exclusion); - if (ua_event->obj != NULL) { - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_release_object(sock, ua_event->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app release event failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app release event failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app release event obj failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - } - free(ua_event->obj); - } - free(ua_event); -} - -/* - * Delayed reclaim of a ust_app_event_notifier_rule object. This MUST be called - * through a call_rcu(). - */ -static -void free_ust_app_event_notifier_rule_rcu(struct rcu_head *head) -{ - struct ust_app_event_notifier_rule *obj = caa_container_of( - head, struct ust_app_event_notifier_rule, rcu_head); - - free(obj); -} - -/* - * Delete ust app event notifier rule safely. - */ -static void delete_ust_app_event_notifier_rule(int sock, - struct ust_app_event_notifier_rule *ua_event_notifier_rule, - struct ust_app *app) -{ - int ret; - - LTTNG_ASSERT(ua_event_notifier_rule); - - if (ua_event_notifier_rule->exclusion != NULL) { - free(ua_event_notifier_rule->exclusion); - } - - if (ua_event_notifier_rule->obj != NULL) { - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_release_object(sock, ua_event_notifier_rule->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app release event notifier failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app release event notifier failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app release event notifier failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - } - - free(ua_event_notifier_rule->obj); - } - - lttng_trigger_put(ua_event_notifier_rule->trigger); - call_rcu(&ua_event_notifier_rule->rcu_head, - free_ust_app_event_notifier_rule_rcu); -} - -/* - * Release ust data object of the given stream. - * - * Return 0 on success or else a negative value. - */ -static int release_ust_app_stream(int sock, struct ust_app_stream *stream, - struct ust_app *app) -{ - int ret = 0; - - LTTNG_ASSERT(stream); - - if (stream->obj) { - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_release_object(sock, stream->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app release stream failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app release stream failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app release stream obj failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - } - lttng_fd_put(LTTNG_FD_APPS, 2); - free(stream->obj); - } - - return ret; -} - -/* - * Delete ust app stream safely. RCU read lock must be held before calling - * this function. - */ -static -void delete_ust_app_stream(int sock, struct ust_app_stream *stream, - struct ust_app *app) -{ - LTTNG_ASSERT(stream); - - (void) release_ust_app_stream(sock, stream, app); - free(stream); -} - -/* - * We need to execute ht_destroy outside of RCU read-side critical - * section and outside of call_rcu thread, so we postpone its execution - * using ht_cleanup_push. It is simpler than to change the semantic of - * the many callers of delete_ust_app_session(). - */ -static -void delete_ust_app_channel_rcu(struct rcu_head *head) -{ - struct ust_app_channel *ua_chan = - caa_container_of(head, struct ust_app_channel, rcu_head); - - ht_cleanup_push(ua_chan->ctx); - ht_cleanup_push(ua_chan->events); - free(ua_chan); -} - -/* - * Extract the lost packet or discarded events counter when the channel is - * being deleted and store the value in the parent channel so we can - * access it from lttng list and at stop/destroy. - * - * The session list lock must be held by the caller. - */ -static -void save_per_pid_lost_discarded_counters(struct ust_app_channel *ua_chan) -{ - uint64_t discarded = 0, lost = 0; - struct ltt_session *session; - struct ltt_ust_channel *uchan; - - if (ua_chan->attr.type != LTTNG_UST_ABI_CHAN_PER_CPU) { - return; - } - - rcu_read_lock(); - session = session_find_by_id(ua_chan->session->tracing_id); - if (!session || !session->ust_session) { - /* - * Not finding the session is not an error because there are - * multiple ways the channels can be torn down. - * - * 1) The session daemon can initiate the destruction of the - * ust app session after receiving a destroy command or - * during its shutdown/teardown. - * 2) The application, since we are in per-pid tracing, is - * unregistering and tearing down its ust app session. - * - * Both paths are protected by the session list lock which - * ensures that the accounting of lost packets and discarded - * events is done exactly once. The session is then unpublished - * from the session list, resulting in this condition. - */ - goto end; - } - - if (ua_chan->attr.overwrite) { - consumer_get_lost_packets(ua_chan->session->tracing_id, - ua_chan->key, session->ust_session->consumer, - &lost); - } else { - consumer_get_discarded_events(ua_chan->session->tracing_id, - ua_chan->key, session->ust_session->consumer, - &discarded); - } - uchan = trace_ust_find_channel_by_name( - session->ust_session->domain_global.channels, - ua_chan->name); - if (!uchan) { - ERR("Missing UST channel to store discarded counters"); - goto end; - } - - uchan->per_pid_closed_app_discarded += discarded; - uchan->per_pid_closed_app_lost += lost; - -end: - rcu_read_unlock(); - if (session) { - session_put(session); - } -} - -/* - * Delete ust app channel safely. RCU read lock must be held before calling - * this function. - * - * The session list lock must be held by the caller. - */ -static -void delete_ust_app_channel(int sock, struct ust_app_channel *ua_chan, - struct ust_app *app) -{ - int ret; - struct lttng_ht_iter iter; - struct ust_app_event *ua_event; - struct ust_app_ctx *ua_ctx; - struct ust_app_stream *stream, *stmp; - struct ust_registry_session *registry; - - LTTNG_ASSERT(ua_chan); - - DBG3("UST app deleting channel %s", ua_chan->name); - - /* Wipe stream */ - cds_list_for_each_entry_safe(stream, stmp, &ua_chan->streams.head, list) { - cds_list_del(&stream->list); - delete_ust_app_stream(sock, stream, app); - } - - /* Wipe context */ - cds_lfht_for_each_entry(ua_chan->ctx->ht, &iter.iter, ua_ctx, node.node) { - cds_list_del(&ua_ctx->list); - ret = lttng_ht_del(ua_chan->ctx, &iter); - LTTNG_ASSERT(!ret); - delete_ust_app_ctx(sock, ua_ctx, app); - } - - /* Wipe events */ - cds_lfht_for_each_entry(ua_chan->events->ht, &iter.iter, ua_event, - node.node) { - ret = lttng_ht_del(ua_chan->events, &iter); - LTTNG_ASSERT(!ret); - delete_ust_app_event(sock, ua_event, app); - } - - if (ua_chan->session->buffer_type == LTTNG_BUFFER_PER_PID) { - /* Wipe and free registry from session registry. */ - registry = get_session_registry(ua_chan->session); - if (registry) { - ust_registry_channel_del_free(registry, ua_chan->key, - sock >= 0); - } - /* - * A negative socket can be used by the caller when - * cleaning-up a ua_chan in an error path. Skip the - * accounting in this case. - */ - if (sock >= 0) { - save_per_pid_lost_discarded_counters(ua_chan); - } - } - - if (ua_chan->obj != NULL) { - /* Remove channel from application UST object descriptor. */ - iter.iter.node = &ua_chan->ust_objd_node.node; - ret = lttng_ht_del(app->ust_objd, &iter); - LTTNG_ASSERT(!ret); - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_release_object(sock, ua_chan->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app channel %s release failed. Application is dead: pid = %d, sock = %d", - ua_chan->name, app->pid, - app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app channel %s release failed. Communication time out: pid = %d, sock = %d", - ua_chan->name, app->pid, - app->sock); - } else { - ERR("UST app channel %s release failed with ret %d: pid = %d, sock = %d", - ua_chan->name, ret, app->pid, - app->sock); - } - } - lttng_fd_put(LTTNG_FD_APPS, 1); - free(ua_chan->obj); - } - call_rcu(&ua_chan->rcu_head, delete_ust_app_channel_rcu); -} - -int ust_app_register_done(struct ust_app *app) -{ - int ret; - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_register_done(app->sock); - pthread_mutex_unlock(&app->sock_lock); - return ret; -} - -int ust_app_release_object(struct ust_app *app, struct lttng_ust_abi_object_data *data) -{ - int ret, sock; - - if (app) { - pthread_mutex_lock(&app->sock_lock); - sock = app->sock; - } else { - sock = -1; - } - ret = lttng_ust_ctl_release_object(sock, data); - if (app) { - pthread_mutex_unlock(&app->sock_lock); - } - return ret; -} - -/* - * Push metadata to consumer socket. - * - * RCU read-side lock must be held to guarantee existance of socket. - * Must be called with the ust app session lock held. - * Must be called with the registry lock held. - * - * On success, return the len of metadata pushed or else a negative value. - * Returning a -EPIPE return value means we could not send the metadata, - * but it can be caused by recoverable errors (e.g. the application has - * terminated concurrently). - */ -ssize_t ust_app_push_metadata(struct ust_registry_session *registry, - struct consumer_socket *socket, int send_zero_data) -{ - int ret; - char *metadata_str = NULL; - size_t len, offset, new_metadata_len_sent; - ssize_t ret_val; - uint64_t metadata_key, metadata_version; - - LTTNG_ASSERT(registry); - LTTNG_ASSERT(socket); - - metadata_key = registry->metadata_key; - - /* - * Means that no metadata was assigned to the session. This can - * happens if no start has been done previously. - */ - if (!metadata_key) { - return 0; - } - - offset = registry->metadata_len_sent; - len = registry->metadata_len - registry->metadata_len_sent; - new_metadata_len_sent = registry->metadata_len; - metadata_version = registry->metadata_version; - if (len == 0) { - DBG3("No metadata to push for metadata key %" PRIu64, - registry->metadata_key); - ret_val = len; - if (send_zero_data) { - DBG("No metadata to push"); - goto push_data; - } - goto end; - } - - /* Allocate only what we have to send. */ - metadata_str = zmalloc(len); - if (!metadata_str) { - PERROR("zmalloc ust app metadata string"); - ret_val = -ENOMEM; - goto error; - } - /* Copy what we haven't sent out. */ - memcpy(metadata_str, registry->metadata + offset, len); - -push_data: - pthread_mutex_unlock(®istry->lock); - /* - * We need to unlock the registry while we push metadata to - * break a circular dependency between the consumerd metadata - * lock and the sessiond registry lock. Indeed, pushing metadata - * to the consumerd awaits that it gets pushed all the way to - * relayd, but doing so requires grabbing the metadata lock. If - * a concurrent metadata request is being performed by - * consumerd, this can try to grab the registry lock on the - * sessiond while holding the metadata lock on the consumer - * daemon. Those push and pull schemes are performed on two - * different bidirectionnal communication sockets. - */ - ret = consumer_push_metadata(socket, metadata_key, - metadata_str, len, offset, metadata_version); - pthread_mutex_lock(®istry->lock); - if (ret < 0) { - /* - * There is an acceptable race here between the registry - * metadata key assignment and the creation on the - * consumer. The session daemon can concurrently push - * metadata for this registry while being created on the - * consumer since the metadata key of the registry is - * assigned *before* it is setup to avoid the consumer - * to ask for metadata that could possibly be not found - * in the session daemon. - * - * The metadata will get pushed either by the session - * being stopped or the consumer requesting metadata if - * that race is triggered. - */ - if (ret == -LTTCOMM_CONSUMERD_CHANNEL_FAIL) { - ret = 0; - } else { - ERR("Error pushing metadata to consumer"); - } - ret_val = ret; - goto error_push; - } else { - /* - * Metadata may have been concurrently pushed, since - * we're not holding the registry lock while pushing to - * consumer. This is handled by the fact that we send - * the metadata content, size, and the offset at which - * that metadata belongs. This may arrive out of order - * on the consumer side, and the consumer is able to - * deal with overlapping fragments. The consumer - * supports overlapping fragments, which must be - * contiguous starting from offset 0. We keep the - * largest metadata_len_sent value of the concurrent - * send. - */ - registry->metadata_len_sent = - max_t(size_t, registry->metadata_len_sent, - new_metadata_len_sent); - } - free(metadata_str); - return len; - -end: -error: - if (ret_val) { - /* - * On error, flag the registry that the metadata is - * closed. We were unable to push anything and this - * means that either the consumer is not responding or - * the metadata cache has been destroyed on the - * consumer. - */ - registry->metadata_closed = 1; - } -error_push: - free(metadata_str); - return ret_val; -} - -/* - * For a given application and session, push metadata to consumer. - * Either sock or consumer is required : if sock is NULL, the default - * socket to send the metadata is retrieved from consumer, if sock - * is not NULL we use it to send the metadata. - * RCU read-side lock must be held while calling this function, - * therefore ensuring existance of registry. It also ensures existance - * of socket throughout this function. - * - * Return 0 on success else a negative error. - * Returning a -EPIPE return value means we could not send the metadata, - * but it can be caused by recoverable errors (e.g. the application has - * terminated concurrently). - */ -static int push_metadata(struct ust_registry_session *registry, - struct consumer_output *consumer) -{ - int ret_val; - ssize_t ret; - struct consumer_socket *socket; - - LTTNG_ASSERT(registry); - LTTNG_ASSERT(consumer); - - pthread_mutex_lock(®istry->lock); - if (registry->metadata_closed) { - ret_val = -EPIPE; - goto error; - } - - /* Get consumer socket to use to push the metadata.*/ - socket = consumer_find_socket_by_bitness(registry->bits_per_long, - consumer); - if (!socket) { - ret_val = -1; - goto error; - } - - ret = ust_app_push_metadata(registry, socket, 0); - if (ret < 0) { - ret_val = ret; - goto error; - } - pthread_mutex_unlock(®istry->lock); - return 0; - -error: - pthread_mutex_unlock(®istry->lock); - return ret_val; -} - -/* - * Send to the consumer a close metadata command for the given session. Once - * done, the metadata channel is deleted and the session metadata pointer is - * nullified. The session lock MUST be held unless the application is - * in the destroy path. - * - * Do not hold the registry lock while communicating with the consumerd, because - * doing so causes inter-process deadlocks between consumerd and sessiond with - * the metadata request notification. - * - * Return 0 on success else a negative value. - */ -static int close_metadata(struct ust_registry_session *registry, - struct consumer_output *consumer) -{ - int ret; - struct consumer_socket *socket; - uint64_t metadata_key; - bool registry_was_already_closed; - - LTTNG_ASSERT(registry); - LTTNG_ASSERT(consumer); - - rcu_read_lock(); - - pthread_mutex_lock(®istry->lock); - metadata_key = registry->metadata_key; - registry_was_already_closed = registry->metadata_closed; - if (metadata_key != 0) { - /* - * Metadata closed. Even on error this means that the consumer - * is not responding or not found so either way a second close - * should NOT be emit for this registry. - */ - registry->metadata_closed = 1; - } - pthread_mutex_unlock(®istry->lock); - - if (metadata_key == 0 || registry_was_already_closed) { - ret = 0; - goto end; - } - - /* Get consumer socket to use to push the metadata.*/ - socket = consumer_find_socket_by_bitness(registry->bits_per_long, - consumer); - if (!socket) { - ret = -1; - goto end; - } - - ret = consumer_close_metadata(socket, metadata_key); - if (ret < 0) { - goto end; - } - -end: - rcu_read_unlock(); - return ret; -} - -/* - * We need to execute ht_destroy outside of RCU read-side critical - * section and outside of call_rcu thread, so we postpone its execution - * using ht_cleanup_push. It is simpler than to change the semantic of - * the many callers of delete_ust_app_session(). - */ -static -void delete_ust_app_session_rcu(struct rcu_head *head) -{ - struct ust_app_session *ua_sess = - caa_container_of(head, struct ust_app_session, rcu_head); - - ht_cleanup_push(ua_sess->channels); - free(ua_sess); -} - -/* - * Delete ust app session safely. RCU read lock must be held before calling - * this function. - * - * The session list lock must be held by the caller. - */ -static -void delete_ust_app_session(int sock, struct ust_app_session *ua_sess, - struct ust_app *app) -{ - int ret; - struct lttng_ht_iter iter; - struct ust_app_channel *ua_chan; - struct ust_registry_session *registry; - - LTTNG_ASSERT(ua_sess); - - pthread_mutex_lock(&ua_sess->lock); - - LTTNG_ASSERT(!ua_sess->deleted); - ua_sess->deleted = true; - - registry = get_session_registry(ua_sess); - /* Registry can be null on error path during initialization. */ - if (registry) { - /* Push metadata for application before freeing the application. */ - (void) push_metadata(registry, ua_sess->consumer); - - /* - * Don't ask to close metadata for global per UID buffers. Close - * metadata only on destroy trace session in this case. Also, the - * previous push metadata could have flag the metadata registry to - * close so don't send a close command if closed. - */ - if (ua_sess->buffer_type != LTTNG_BUFFER_PER_UID) { - /* And ask to close it for this session registry. */ - (void) close_metadata(registry, ua_sess->consumer); - } - } - - cds_lfht_for_each_entry(ua_sess->channels->ht, &iter.iter, ua_chan, - node.node) { - ret = lttng_ht_del(ua_sess->channels, &iter); - LTTNG_ASSERT(!ret); - delete_ust_app_channel(sock, ua_chan, app); - } - - /* In case of per PID, the registry is kept in the session. */ - if (ua_sess->buffer_type == LTTNG_BUFFER_PER_PID) { - struct buffer_reg_pid *reg_pid = buffer_reg_pid_find(ua_sess->id); - if (reg_pid) { - /* - * Registry can be null on error path during - * initialization. - */ - buffer_reg_pid_remove(reg_pid); - buffer_reg_pid_destroy(reg_pid); - } - } - - if (ua_sess->handle != -1) { - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_release_handle(sock, ua_sess->handle); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app release session handle failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app release session handle failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app release session handle failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - } - - /* Remove session from application UST object descriptor. */ - iter.iter.node = &ua_sess->ust_objd_node.node; - ret = lttng_ht_del(app->ust_sessions_objd, &iter); - LTTNG_ASSERT(!ret); - } - - pthread_mutex_unlock(&ua_sess->lock); - - consumer_output_put(ua_sess->consumer); - - call_rcu(&ua_sess->rcu_head, delete_ust_app_session_rcu); -} - -/* - * Delete a traceable application structure from the global list. Never call - * this function outside of a call_rcu call. - * - * RCU read side lock should _NOT_ be held when calling this function. - */ -static -void delete_ust_app(struct ust_app *app) -{ - int ret, sock; - struct ust_app_session *ua_sess, *tmp_ua_sess; - struct lttng_ht_iter iter; - struct ust_app_event_notifier_rule *event_notifier_rule; - bool event_notifier_write_fd_is_open; - - /* - * The session list lock must be held during this function to guarantee - * the existence of ua_sess. - */ - session_lock_list(); - /* Delete ust app sessions info */ - sock = app->sock; - app->sock = -1; - - /* Wipe sessions */ - cds_list_for_each_entry_safe(ua_sess, tmp_ua_sess, &app->teardown_head, - teardown_node) { - /* Free every object in the session and the session. */ - rcu_read_lock(); - delete_ust_app_session(sock, ua_sess, app); - rcu_read_unlock(); - } - - /* Remove the event notifier rules associated with this app. */ - rcu_read_lock(); - cds_lfht_for_each_entry (app->token_to_event_notifier_rule_ht->ht, - &iter.iter, event_notifier_rule, node.node) { - ret = lttng_ht_del(app->token_to_event_notifier_rule_ht, &iter); - LTTNG_ASSERT(!ret); - - delete_ust_app_event_notifier_rule( - app->sock, event_notifier_rule, app); - } - - rcu_read_unlock(); - - ht_cleanup_push(app->sessions); - ht_cleanup_push(app->ust_sessions_objd); - ht_cleanup_push(app->ust_objd); - ht_cleanup_push(app->token_to_event_notifier_rule_ht); - - /* - * This could be NULL if the event notifier setup failed (e.g the app - * was killed or the tracer does not support this feature). - */ - if (app->event_notifier_group.object) { - enum lttng_error_code ret_code; - enum event_notifier_error_accounting_status status; - - const int event_notifier_read_fd = lttng_pipe_get_readfd( - app->event_notifier_group.event_pipe); - - ret_code = notification_thread_command_remove_tracer_event_source( - the_notification_thread_handle, - event_notifier_read_fd); - if (ret_code != LTTNG_OK) { - ERR("Failed to remove application tracer event source from notification thread"); - } - - status = event_notifier_error_accounting_unregister_app(app); - if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { - ERR("Error unregistering app from event notifier error accounting"); - } - - lttng_ust_ctl_release_object(sock, app->event_notifier_group.object); - free(app->event_notifier_group.object); - } - - event_notifier_write_fd_is_open = lttng_pipe_is_write_open( - app->event_notifier_group.event_pipe); - lttng_pipe_destroy(app->event_notifier_group.event_pipe); - /* - * Release the file descriptors reserved for the event notifier pipe. - * The app could be destroyed before the write end of the pipe could be - * passed to the application (and closed). In that case, both file - * descriptors must be released. - */ - lttng_fd_put(LTTNG_FD_APPS, event_notifier_write_fd_is_open ? 2 : 1); - - /* - * Wait until we have deleted the application from the sock hash table - * before closing this socket, otherwise an application could re-use the - * socket ID and race with the teardown, using the same hash table entry. - * - * It's OK to leave the close in call_rcu. We want it to stay unique for - * all RCU readers that could run concurrently with unregister app, - * therefore we _need_ to only close that socket after a grace period. So - * it should stay in this RCU callback. - * - * This close() is a very important step of the synchronization model so - * every modification to this function must be carefully reviewed. - */ - ret = close(sock); - if (ret) { - PERROR("close"); - } - lttng_fd_put(LTTNG_FD_APPS, 1); - - DBG2("UST app pid %d deleted", app->pid); - free(app); - session_unlock_list(); -} - -/* - * URCU intermediate call to delete an UST app. - */ -static -void delete_ust_app_rcu(struct rcu_head *head) -{ - struct lttng_ht_node_ulong *node = - caa_container_of(head, struct lttng_ht_node_ulong, head); - struct ust_app *app = - caa_container_of(node, struct ust_app, pid_n); - - DBG3("Call RCU deleting app PID %d", app->pid); - delete_ust_app(app); -} - -/* - * Delete the session from the application ht and delete the data structure by - * freeing every object inside and releasing them. - * - * The session list lock must be held by the caller. - */ -static void destroy_app_session(struct ust_app *app, - struct ust_app_session *ua_sess) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(ua_sess); - - iter.iter.node = &ua_sess->node.node; - ret = lttng_ht_del(app->sessions, &iter); - if (ret) { - /* Already scheduled for teardown. */ - goto end; - } - - /* Once deleted, free the data structure. */ - delete_ust_app_session(app->sock, ua_sess, app); - -end: - return; -} - -/* - * Alloc new UST app session. - */ -static -struct ust_app_session *alloc_ust_app_session(void) -{ - struct ust_app_session *ua_sess; - - /* Init most of the default value by allocating and zeroing */ - ua_sess = zmalloc(sizeof(struct ust_app_session)); - if (ua_sess == NULL) { - PERROR("malloc"); - goto error_free; - } - - ua_sess->handle = -1; - ua_sess->channels = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); - ua_sess->metadata_attr.type = LTTNG_UST_ABI_CHAN_METADATA; - pthread_mutex_init(&ua_sess->lock, NULL); - - return ua_sess; - -error_free: - return NULL; -} - -/* - * Alloc new UST app channel. - */ -static -struct ust_app_channel *alloc_ust_app_channel(const char *name, - struct ust_app_session *ua_sess, - struct lttng_ust_abi_channel_attr *attr) -{ - struct ust_app_channel *ua_chan; - - /* Init most of the default value by allocating and zeroing */ - ua_chan = zmalloc(sizeof(struct ust_app_channel)); - if (ua_chan == NULL) { - PERROR("malloc"); - goto error; - } - - /* Setup channel name */ - strncpy(ua_chan->name, name, sizeof(ua_chan->name)); - ua_chan->name[sizeof(ua_chan->name) - 1] = '\0'; - - ua_chan->enabled = 1; - ua_chan->handle = -1; - ua_chan->session = ua_sess; - ua_chan->key = get_next_channel_key(); - ua_chan->ctx = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); - ua_chan->events = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); - lttng_ht_node_init_str(&ua_chan->node, ua_chan->name); - - CDS_INIT_LIST_HEAD(&ua_chan->streams.head); - CDS_INIT_LIST_HEAD(&ua_chan->ctx_list); - - /* Copy attributes */ - if (attr) { - /* Translate from lttng_ust_channel to lttng_ust_ctl_consumer_channel_attr. */ - ua_chan->attr.subbuf_size = attr->subbuf_size; - ua_chan->attr.num_subbuf = attr->num_subbuf; - ua_chan->attr.overwrite = attr->overwrite; - ua_chan->attr.switch_timer_interval = attr->switch_timer_interval; - ua_chan->attr.read_timer_interval = attr->read_timer_interval; - ua_chan->attr.output = attr->output; - ua_chan->attr.blocking_timeout = attr->u.s.blocking_timeout; - } - /* By default, the channel is a per cpu channel. */ - ua_chan->attr.type = LTTNG_UST_ABI_CHAN_PER_CPU; - - DBG3("UST app channel %s allocated", ua_chan->name); - - return ua_chan; - -error: - return NULL; -} - -/* - * Allocate and initialize a UST app stream. - * - * Return newly allocated stream pointer or NULL on error. - */ -struct ust_app_stream *ust_app_alloc_stream(void) -{ - struct ust_app_stream *stream = NULL; - - stream = zmalloc(sizeof(*stream)); - if (stream == NULL) { - PERROR("zmalloc ust app stream"); - goto error; - } - - /* Zero could be a valid value for a handle so flag it to -1. */ - stream->handle = -1; - -error: - return stream; -} - -/* - * Alloc new UST app event. - */ -static -struct ust_app_event *alloc_ust_app_event(char *name, - struct lttng_ust_abi_event *attr) -{ - struct ust_app_event *ua_event; - - /* Init most of the default value by allocating and zeroing */ - ua_event = zmalloc(sizeof(struct ust_app_event)); - if (ua_event == NULL) { - PERROR("Failed to allocate ust_app_event structure"); - goto error; - } - - ua_event->enabled = 1; - strncpy(ua_event->name, name, sizeof(ua_event->name)); - ua_event->name[sizeof(ua_event->name) - 1] = '\0'; - lttng_ht_node_init_str(&ua_event->node, ua_event->name); - - /* Copy attributes */ - if (attr) { - memcpy(&ua_event->attr, attr, sizeof(ua_event->attr)); - } - - DBG3("UST app event %s allocated", ua_event->name); - - return ua_event; - -error: - return NULL; -} - -/* - * Allocate a new UST app event notifier rule. - */ -static struct ust_app_event_notifier_rule *alloc_ust_app_event_notifier_rule( - struct lttng_trigger *trigger) -{ - enum lttng_event_rule_generate_exclusions_status - generate_exclusion_status; - enum lttng_condition_status cond_status; - struct ust_app_event_notifier_rule *ua_event_notifier_rule; - struct lttng_condition *condition = NULL; - const struct lttng_event_rule *event_rule = NULL; - - ua_event_notifier_rule = zmalloc(sizeof(struct ust_app_event_notifier_rule)); - if (ua_event_notifier_rule == NULL) { - PERROR("Failed to allocate ust_app_event_notifier_rule structure"); - goto error; - } - - ua_event_notifier_rule->enabled = 1; - ua_event_notifier_rule->token = lttng_trigger_get_tracer_token(trigger); - lttng_ht_node_init_u64(&ua_event_notifier_rule->node, - ua_event_notifier_rule->token); - - condition = lttng_trigger_get_condition(trigger); - LTTNG_ASSERT(condition); - LTTNG_ASSERT(lttng_condition_get_type(condition) == - LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); - - cond_status = lttng_condition_event_rule_matches_get_rule( - condition, &event_rule); - LTTNG_ASSERT(cond_status == LTTNG_CONDITION_STATUS_OK); - LTTNG_ASSERT(event_rule); - - ua_event_notifier_rule->error_counter_index = - lttng_condition_event_rule_matches_get_error_counter_index(condition); - /* Acquire the event notifier's reference to the trigger. */ - lttng_trigger_get(trigger); - - ua_event_notifier_rule->trigger = trigger; - ua_event_notifier_rule->filter = lttng_event_rule_get_filter_bytecode(event_rule); - generate_exclusion_status = lttng_event_rule_generate_exclusions( - event_rule, &ua_event_notifier_rule->exclusion); - switch (generate_exclusion_status) { - case LTTNG_EVENT_RULE_GENERATE_EXCLUSIONS_STATUS_OK: - case LTTNG_EVENT_RULE_GENERATE_EXCLUSIONS_STATUS_NONE: - break; - default: - /* Error occurred. */ - ERR("Failed to generate exclusions from trigger while allocating an event notifier rule"); - goto error_put_trigger; - } - - DBG3("UST app event notifier rule allocated: token = %" PRIu64, - ua_event_notifier_rule->token); - - return ua_event_notifier_rule; - -error_put_trigger: - lttng_trigger_put(trigger); -error: - free(ua_event_notifier_rule); - return NULL; -} - -/* - * Alloc new UST app context. - */ -static -struct ust_app_ctx *alloc_ust_app_ctx(struct lttng_ust_context_attr *uctx) -{ - struct ust_app_ctx *ua_ctx; - - ua_ctx = zmalloc(sizeof(struct ust_app_ctx)); - if (ua_ctx == NULL) { - goto error; - } - - CDS_INIT_LIST_HEAD(&ua_ctx->list); - - if (uctx) { - memcpy(&ua_ctx->ctx, uctx, sizeof(ua_ctx->ctx)); - if (uctx->ctx == LTTNG_UST_ABI_CONTEXT_APP_CONTEXT) { - char *provider_name = NULL, *ctx_name = NULL; - - provider_name = strdup(uctx->u.app_ctx.provider_name); - ctx_name = strdup(uctx->u.app_ctx.ctx_name); - if (!provider_name || !ctx_name) { - free(provider_name); - free(ctx_name); - goto error; - } - - ua_ctx->ctx.u.app_ctx.provider_name = provider_name; - ua_ctx->ctx.u.app_ctx.ctx_name = ctx_name; - } - } - - DBG3("UST app context %d allocated", ua_ctx->ctx.ctx); - return ua_ctx; -error: - free(ua_ctx); - return NULL; -} - -/* - * Create a liblttng-ust filter bytecode from given bytecode. - * - * Return allocated filter or NULL on error. - */ -static struct lttng_ust_abi_filter_bytecode *create_ust_filter_bytecode_from_bytecode( - const struct lttng_bytecode *orig_f) -{ - struct lttng_ust_abi_filter_bytecode *filter = NULL; - - /* Copy filter bytecode. */ - filter = zmalloc(sizeof(*filter) + orig_f->len); - if (!filter) { - PERROR("Failed to allocate lttng_ust_filter_bytecode: bytecode len = %" PRIu32 " bytes", orig_f->len); - goto error; - } - - LTTNG_ASSERT(sizeof(struct lttng_bytecode) == - sizeof(struct lttng_ust_abi_filter_bytecode)); - memcpy(filter, orig_f, sizeof(*filter) + orig_f->len); -error: - return filter; -} - -/* - * Create a liblttng-ust capture bytecode from given bytecode. - * - * Return allocated filter or NULL on error. - */ -static struct lttng_ust_abi_capture_bytecode * -create_ust_capture_bytecode_from_bytecode(const struct lttng_bytecode *orig_f) -{ - struct lttng_ust_abi_capture_bytecode *capture = NULL; - - /* Copy capture bytecode. */ - capture = zmalloc(sizeof(*capture) + orig_f->len); - if (!capture) { - PERROR("Failed to allocate lttng_ust_abi_capture_bytecode: bytecode len = %" PRIu32 " bytes", orig_f->len); - goto error; - } - - LTTNG_ASSERT(sizeof(struct lttng_bytecode) == - sizeof(struct lttng_ust_abi_capture_bytecode)); - memcpy(capture, orig_f, sizeof(*capture) + orig_f->len); -error: - return capture; -} - -/* - * Find an ust_app using the sock and return it. RCU read side lock must be - * held before calling this helper function. - */ -struct ust_app *ust_app_find_by_sock(int sock) -{ - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter iter; - - lttng_ht_lookup(ust_app_ht_by_sock, (void *)((unsigned long) sock), &iter); - node = lttng_ht_iter_get_node_ulong(&iter); - if (node == NULL) { - DBG2("UST app find by sock %d not found", sock); - goto error; - } - - return caa_container_of(node, struct ust_app, sock_n); - -error: - return NULL; -} - -/* - * Find an ust_app using the notify sock and return it. RCU read side lock must - * be held before calling this helper function. - */ -static struct ust_app *find_app_by_notify_sock(int sock) -{ - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter iter; - - lttng_ht_lookup(ust_app_ht_by_notify_sock, (void *)((unsigned long) sock), - &iter); - node = lttng_ht_iter_get_node_ulong(&iter); - if (node == NULL) { - DBG2("UST app find by notify sock %d not found", sock); - goto error; - } - - return caa_container_of(node, struct ust_app, notify_sock_n); - -error: - return NULL; -} - -/* - * Lookup for an ust app event based on event name, filter bytecode and the - * event loglevel. - * - * Return an ust_app_event object or NULL on error. - */ -static struct ust_app_event *find_ust_app_event(struct lttng_ht *ht, - const char *name, const struct lttng_bytecode *filter, - int loglevel_value, - const struct lttng_event_exclusion *exclusion) -{ - struct lttng_ht_iter iter; - struct lttng_ht_node_str *node; - struct ust_app_event *event = NULL; - struct ust_app_ht_key key; - - LTTNG_ASSERT(name); - LTTNG_ASSERT(ht); - - /* Setup key for event lookup. */ - key.name = name; - key.filter = filter; - key.loglevel_type = loglevel_value; - /* lttng_event_exclusion and lttng_ust_event_exclusion structures are similar */ - key.exclusion = exclusion; - - /* Lookup using the event name as hash and a custom match fct. */ - cds_lfht_lookup(ht->ht, ht->hash_fct((void *) name, lttng_ht_seed), - ht_match_ust_app_event, &key, &iter.iter); - node = lttng_ht_iter_get_node_str(&iter); - if (node == NULL) { - goto end; - } - - event = caa_container_of(node, struct ust_app_event, node); - -end: - return event; -} - -/* - * Look-up an event notifier rule based on its token id. - * - * Must be called with the RCU read lock held. - * Return an ust_app_event_notifier_rule object or NULL on error. - */ -static struct ust_app_event_notifier_rule *find_ust_app_event_notifier_rule( - struct lttng_ht *ht, uint64_t token) -{ - struct lttng_ht_iter iter; - struct lttng_ht_node_u64 *node; - struct ust_app_event_notifier_rule *event_notifier_rule = NULL; - - LTTNG_ASSERT(ht); - - lttng_ht_lookup(ht, &token, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (node == NULL) { - DBG2("UST app event notifier rule token not found: token = %" PRIu64, - token); - goto end; - } - - event_notifier_rule = caa_container_of( - node, struct ust_app_event_notifier_rule, node); -end: - return event_notifier_rule; -} - -/* - * Create the channel context on the tracer. - * - * Called with UST app session lock held. - */ -static -int create_ust_channel_context(struct ust_app_channel *ua_chan, - struct ust_app_ctx *ua_ctx, struct ust_app *app) -{ - int ret; - - health_code_update(); - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_add_context(app->sock, &ua_ctx->ctx, - ua_chan->obj, &ua_ctx->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app create channel context failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app create channel context failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app create channel context failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - goto error; - } - - ua_ctx->handle = ua_ctx->obj->handle; - - DBG2("UST app context handle %d created successfully for channel %s", - ua_ctx->handle, ua_chan->name); - -error: - health_code_update(); - return ret; -} - -/* - * Set the filter on the tracer. - */ -static int set_ust_object_filter(struct ust_app *app, - const struct lttng_bytecode *bytecode, - struct lttng_ust_abi_object_data *ust_object) -{ - int ret; - struct lttng_ust_abi_filter_bytecode *ust_bytecode = NULL; - - health_code_update(); - - ust_bytecode = create_ust_filter_bytecode_from_bytecode(bytecode); - if (!ust_bytecode) { - ret = -LTTNG_ERR_NOMEM; - goto error; - } - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_set_filter(app->sock, ust_bytecode, - ust_object); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app set filter failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app set filter failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app set filter failed with ret %d: pid = %d, sock = %d, object = %p", - ret, app->pid, app->sock, ust_object); - } - goto error; - } - - DBG2("UST filter successfully set: object = %p", ust_object); - -error: - health_code_update(); - free(ust_bytecode); - return ret; -} - -/* - * Set a capture bytecode for the passed object. - * The sequence number enforces the ordering at runtime and on reception of - * the captured payloads. - */ -static int set_ust_capture(struct ust_app *app, - const struct lttng_bytecode *bytecode, - unsigned int capture_seqnum, - struct lttng_ust_abi_object_data *ust_object) -{ - int ret; - struct lttng_ust_abi_capture_bytecode *ust_bytecode = NULL; - - health_code_update(); - - ust_bytecode = create_ust_capture_bytecode_from_bytecode(bytecode); - if (!ust_bytecode) { - ret = -LTTNG_ERR_NOMEM; - goto error; - } - - /* - * Set the sequence number to ensure the capture of fields is ordered. - */ - ust_bytecode->seqnum = capture_seqnum; - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_set_capture(app->sock, ust_bytecode, - ust_object); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app set capture failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - DBG3("UST app set capture failed. Communication timeout: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app event set capture failed with ret %d: pid = %d, sock = %d", - ret, app->pid, - app->sock); - } - - goto error; - } - - DBG2("UST capture successfully set: object = %p", ust_object); - -error: - health_code_update(); - free(ust_bytecode); - return ret; -} - -static -struct lttng_ust_abi_event_exclusion *create_ust_exclusion_from_exclusion( - const struct lttng_event_exclusion *exclusion) -{ - struct lttng_ust_abi_event_exclusion *ust_exclusion = NULL; - size_t exclusion_alloc_size = sizeof(struct lttng_ust_abi_event_exclusion) + - LTTNG_UST_ABI_SYM_NAME_LEN * exclusion->count; - - ust_exclusion = zmalloc(exclusion_alloc_size); - if (!ust_exclusion) { - PERROR("malloc"); - goto end; - } - - LTTNG_ASSERT(sizeof(struct lttng_event_exclusion) == - sizeof(struct lttng_ust_abi_event_exclusion)); - memcpy(ust_exclusion, exclusion, exclusion_alloc_size); -end: - return ust_exclusion; -} - -/* - * Set event exclusions on the tracer. - */ -static int set_ust_object_exclusions(struct ust_app *app, - const struct lttng_event_exclusion *exclusions, - struct lttng_ust_abi_object_data *ust_object) -{ - int ret; - struct lttng_ust_abi_event_exclusion *ust_exclusions = NULL; - - LTTNG_ASSERT(exclusions && exclusions->count > 0); - - health_code_update(); - - ust_exclusions = create_ust_exclusion_from_exclusion( - exclusions); - if (!ust_exclusions) { - ret = -LTTNG_ERR_NOMEM; - goto error; - } - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_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) { - ret = 0; - DBG3("UST app event exclusion failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app event exclusion failed. Communication time out(pid: %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app event exclusions failed with ret %d: pid = %d, sock = %d, object = %p", - ret, app->pid, app->sock, ust_object); - } - goto error; - } - - DBG2("UST exclusions set successfully for object %p", ust_object); - -error: - health_code_update(); - free(ust_exclusions); - return ret; -} - -/* - * Disable the specified event on to UST tracer for the UST session. - */ -static int disable_ust_object(struct ust_app *app, - struct lttng_ust_abi_object_data *object) -{ - int ret; - - health_code_update(); - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_disable(app->sock, object); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app disable object failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app disable object failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app disable object failed with ret %d: pid = %d, sock = %d, object = %p", - ret, app->pid, app->sock, object); - } - goto error; - } - - DBG2("UST app object %p disabled successfully for app: pid = %d", - object, app->pid); - -error: - health_code_update(); - return ret; -} - -/* - * Disable the specified channel on to UST tracer for the UST session. - */ -static int disable_ust_channel(struct ust_app *app, - struct ust_app_session *ua_sess, struct ust_app_channel *ua_chan) -{ - int ret; - - health_code_update(); - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_disable(app->sock, ua_chan->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app disable channel failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app disable channel failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app channel %s disable failed, session handle %d, with ret %d: pid = %d, sock = %d", - ua_chan->name, ua_sess->handle, ret, - app->pid, app->sock); - } - goto error; - } - - DBG2("UST app channel %s disabled successfully for app: pid = %d", - ua_chan->name, app->pid); - -error: - health_code_update(); - return ret; -} - -/* - * Enable the specified channel on to UST tracer for the UST session. - */ -static int enable_ust_channel(struct ust_app *app, - struct ust_app_session *ua_sess, struct ust_app_channel *ua_chan) -{ - int ret; - - health_code_update(); - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_enable(app->sock, ua_chan->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app channel %s enable failed. Application is dead: pid = %d, sock = %d", - ua_chan->name, app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app channel %s enable failed. Communication time out: pid = %d, sock = %d", - ua_chan->name, app->pid, app->sock); - } else { - ERR("UST app channel %s enable failed, session handle %d, with ret %d: pid = %d, sock = %d", - ua_chan->name, ua_sess->handle, ret, - app->pid, app->sock); - } - goto error; - } - - ua_chan->enabled = 1; - - DBG2("UST app channel %s enabled successfully for app: pid = %d", - ua_chan->name, app->pid); - -error: - health_code_update(); - return ret; -} - -/* - * Enable the specified event on to UST tracer for the UST session. - */ -static int enable_ust_object( - struct ust_app *app, struct lttng_ust_abi_object_data *ust_object) -{ - int ret; - - health_code_update(); - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_enable(app->sock, ust_object); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app enable object failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app enable object failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app enable object failed with ret %d: pid = %d, sock = %d, object = %p", - ret, app->pid, app->sock, ust_object); - } - goto error; - } - - DBG2("UST app object %p enabled successfully for app: pid = %d", - ust_object, app->pid); - -error: - health_code_update(); - return ret; -} - -/* - * Send channel and stream buffer to application. - * - * Return 0 on success. On error, a negative value is returned. - */ -static int send_channel_pid_to_ust(struct ust_app *app, - struct ust_app_session *ua_sess, struct ust_app_channel *ua_chan) -{ - int ret; - struct ust_app_stream *stream, *stmp; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(ua_chan); - - health_code_update(); - - DBG("UST app sending channel %s to UST app sock %d", ua_chan->name, - app->sock); - - /* Send channel to the application. */ - ret = ust_consumer_send_channel_to_ust(app, ua_sess, ua_chan); - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = -ENOTCONN; /* Caused by app exiting. */ - goto error; - } else if (ret == -EAGAIN) { - /* Caused by timeout. */ - WARN("Communication with application %d timed out on send_channel for channel \"%s\" of session \"%" PRIu64 "\".", - app->pid, ua_chan->name, ua_sess->tracing_id); - /* Treat this the same way as an application that is exiting. */ - ret = -ENOTCONN; - goto error; - } else if (ret < 0) { - goto error; - } - - health_code_update(); - - /* Send all streams to application. */ - cds_list_for_each_entry_safe(stream, stmp, &ua_chan->streams.head, list) { - ret = ust_consumer_send_stream_to_ust(app, ua_chan, stream); - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = -ENOTCONN; /* Caused by app exiting. */ - goto error; - } else if (ret == -EAGAIN) { - /* Caused by timeout. */ - WARN("Communication with application %d timed out on send_stream for stream \"%s\" of channel \"%s\" of session \"%" PRIu64 "\".", - app->pid, stream->name, ua_chan->name, - ua_sess->tracing_id); - /* - * Treat this the same way as an application that is - * exiting. - */ - ret = -ENOTCONN; - } else if (ret < 0) { - goto error; - } - /* We don't need the stream anymore once sent to the tracer. */ - cds_list_del(&stream->list); - delete_ust_app_stream(-1, stream, app); - } - /* Flag the channel that it is sent to the application. */ - ua_chan->is_sent = 1; - -error: - health_code_update(); - return ret; -} - -/* - * Create the specified event onto the UST tracer for a UST session. - * - * Should be called with session mutex held. - */ -static -int create_ust_event(struct ust_app *app, struct ust_app_session *ua_sess, - struct ust_app_channel *ua_chan, struct ust_app_event *ua_event) -{ - int ret = 0; - - health_code_update(); - - /* Create UST event on tracer */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_create_event(app->sock, &ua_event->attr, ua_chan->obj, - &ua_event->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app create event failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app create event failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app create event '%s' failed with ret %d: pid = %d, sock = %d", - ua_event->attr.name, ret, app->pid, - app->sock); - } - goto error; - } - - ua_event->handle = ua_event->obj->handle; - - 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_object_filter(app, ua_event->filter, ua_event->obj); - if (ret < 0) { - goto error; - } - } - - /* Set exclusions for the event */ - if (ua_event->exclusion) { - ret = set_ust_object_exclusions(app, ua_event->exclusion, ua_event->obj); - if (ret < 0) { - goto error; - } - } - - /* If event not enabled, disable it on the tracer */ - if (ua_event->enabled) { - /* - * We now need to explicitly enable the event, since it - * is now disabled at creation. - */ - ret = enable_ust_object(app, ua_event->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 */ - abort(); - case -LTTNG_UST_ERR_EXIST: - /* It's OK for our use case. */ - ret = 0; - break; - default: - break; - } - goto error; - } - } - -error: - health_code_update(); - return ret; -} - -static int init_ust_event_notifier_from_event_rule( - const struct lttng_event_rule *rule, - struct lttng_ust_abi_event_notifier *event_notifier) -{ - enum lttng_event_rule_status status; - enum lttng_ust_abi_loglevel_type ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_ALL; - int loglevel = -1, ret = 0; - const char *pattern; - - - memset(event_notifier, 0, sizeof(*event_notifier)); - - if (lttng_event_rule_targets_agent_domain(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_ABI_LOGLEVEL_ALL; - } else { - const struct lttng_log_level_rule *log_level_rule; - - LTTNG_ASSERT(lttng_event_rule_get_type(rule) == - LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT); - - status = lttng_event_rule_user_tracepoint_get_name_pattern(rule, &pattern); - if (status != LTTNG_EVENT_RULE_STATUS_OK) { - /* At this point, this is a fatal error. */ - abort(); - } - - status = lttng_event_rule_user_tracepoint_get_log_level_rule( - rule, &log_level_rule); - if (status == LTTNG_EVENT_RULE_STATUS_UNSET) { - ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_ALL; - } else if (status == LTTNG_EVENT_RULE_STATUS_OK) { - enum lttng_log_level_rule_status llr_status; - - switch (lttng_log_level_rule_get_type(log_level_rule)) { - case LTTNG_LOG_LEVEL_RULE_TYPE_EXACTLY: - ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_SINGLE; - llr_status = lttng_log_level_rule_exactly_get_level( - log_level_rule, &loglevel); - break; - case LTTNG_LOG_LEVEL_RULE_TYPE_AT_LEAST_AS_SEVERE_AS: - ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_RANGE; - llr_status = lttng_log_level_rule_at_least_as_severe_as_get_level( - log_level_rule, &loglevel); - break; - default: - abort(); - } - - LTTNG_ASSERT(llr_status == LTTNG_LOG_LEVEL_RULE_STATUS_OK); - } else { - /* At this point this is a fatal error. */ - abort(); - } - } - - event_notifier->event.instrumentation = LTTNG_UST_ABI_TRACEPOINT; - ret = lttng_strncpy(event_notifier->event.name, pattern, - LTTNG_UST_ABI_SYM_NAME_LEN - 1); - if (ret) { - ERR("Failed to copy event rule pattern to notifier: pattern = '%s' ", - pattern); - goto end; - } - - event_notifier->event.loglevel_type = ust_loglevel_type; - event_notifier->event.loglevel = loglevel; -end: - return ret; -} - -/* - * Create the specified event notifier against the user space tracer of a - * given application. - */ -static int create_ust_event_notifier(struct ust_app *app, - struct ust_app_event_notifier_rule *ua_event_notifier_rule) -{ - int ret = 0; - enum lttng_condition_status condition_status; - const struct lttng_condition *condition = NULL; - struct lttng_ust_abi_event_notifier event_notifier; - const struct lttng_event_rule *event_rule = NULL; - unsigned int capture_bytecode_count = 0, i; - enum lttng_condition_status cond_status; - enum lttng_event_rule_type event_rule_type; - - health_code_update(); - LTTNG_ASSERT(app->event_notifier_group.object); - - condition = lttng_trigger_get_const_condition( - ua_event_notifier_rule->trigger); - LTTNG_ASSERT(condition); - LTTNG_ASSERT(lttng_condition_get_type(condition) == - LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); - - condition_status = lttng_condition_event_rule_matches_get_rule( - condition, &event_rule); - LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); - - LTTNG_ASSERT(event_rule); - - event_rule_type = lttng_event_rule_get_type(event_rule); - LTTNG_ASSERT(event_rule_type == LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT || - event_rule_type == LTTNG_EVENT_RULE_TYPE_JUL_LOGGING || - event_rule_type == - LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING || - event_rule_type == - LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING); - - init_ust_event_notifier_from_event_rule(event_rule, &event_notifier); - event_notifier.event.token = ua_event_notifier_rule->token; - event_notifier.error_counter_index = ua_event_notifier_rule->error_counter_index; - - /* Create UST event notifier against the tracer. */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_create_event_notifier(app->sock, &event_notifier, - app->event_notifier_group.object, - &ua_event_notifier_rule->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app create event notifier failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app create event notifier failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app create event notifier '%s' failed with ret %d: pid = %d, sock = %d", - event_notifier.event.name, ret, app->pid, - app->sock); - } - goto error; - } - - ua_event_notifier_rule->handle = ua_event_notifier_rule->obj->handle; - - DBG2("UST app event notifier %s created successfully: app = '%s': pid = %d), object = %p", - event_notifier.event.name, app->name, app->pid, - ua_event_notifier_rule->obj); - - health_code_update(); - - /* Set filter if one is present. */ - if (ua_event_notifier_rule->filter) { - ret = set_ust_object_filter(app, ua_event_notifier_rule->filter, - ua_event_notifier_rule->obj); - if (ret < 0) { - goto error; - } - } - - /* Set exclusions for the event. */ - if (ua_event_notifier_rule->exclusion) { - ret = set_ust_object_exclusions(app, - ua_event_notifier_rule->exclusion, - ua_event_notifier_rule->obj); - if (ret < 0) { - goto error; - } - } - - /* Set the capture bytecodes. */ - cond_status = lttng_condition_event_rule_matches_get_capture_descriptor_count( - condition, &capture_bytecode_count); - LTTNG_ASSERT(cond_status == LTTNG_CONDITION_STATUS_OK); - - for (i = 0; i < capture_bytecode_count; i++) { - const struct lttng_bytecode *capture_bytecode = - lttng_condition_event_rule_matches_get_capture_bytecode_at_index( - condition, i); - - ret = set_ust_capture(app, capture_bytecode, i, - ua_event_notifier_rule->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_event_notifier_rule->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. */ - abort(); - case -LTTNG_UST_ERR_EXIST: - /* It's OK for our use case. */ - ret = 0; - break; - default: - break; - } - - goto error; - } - - ua_event_notifier_rule->enabled = true; - -error: - health_code_update(); - return ret; -} - -/* - * Copy data between an UST app event and a LTT event. - */ -static void shadow_copy_event(struct ust_app_event *ua_event, - struct ltt_ust_event *uevent) -{ - size_t exclusion_alloc_size; - - strncpy(ua_event->name, uevent->attr.name, sizeof(ua_event->name)); - ua_event->name[sizeof(ua_event->name) - 1] = '\0'; - - ua_event->enabled = uevent->enabled; - - /* Copy event attributes */ - memcpy(&ua_event->attr, &uevent->attr, sizeof(ua_event->attr)); - - /* Copy filter bytecode */ - if (uevent->filter) { - ua_event->filter = lttng_bytecode_copy(uevent->filter); - /* Filter might be NULL here in case of ENONEM. */ - } - - /* Copy exclusion data */ - if (uevent->exclusion) { - exclusion_alloc_size = sizeof(struct lttng_event_exclusion) + - LTTNG_UST_ABI_SYM_NAME_LEN * uevent->exclusion->count; - ua_event->exclusion = zmalloc(exclusion_alloc_size); - if (ua_event->exclusion == NULL) { - PERROR("malloc"); - } else { - memcpy(ua_event->exclusion, uevent->exclusion, - exclusion_alloc_size); - } - } -} - -/* - * Copy data between an UST app channel and a LTT channel. - */ -static void shadow_copy_channel(struct ust_app_channel *ua_chan, - struct ltt_ust_channel *uchan) -{ - DBG2("UST app shadow copy of channel %s started", ua_chan->name); - - strncpy(ua_chan->name, uchan->name, sizeof(ua_chan->name)); - ua_chan->name[sizeof(ua_chan->name) - 1] = '\0'; - - ua_chan->tracefile_size = uchan->tracefile_size; - ua_chan->tracefile_count = uchan->tracefile_count; - - /* Copy event attributes since the layout is different. */ - ua_chan->attr.subbuf_size = uchan->attr.subbuf_size; - ua_chan->attr.num_subbuf = uchan->attr.num_subbuf; - ua_chan->attr.overwrite = uchan->attr.overwrite; - ua_chan->attr.switch_timer_interval = uchan->attr.switch_timer_interval; - ua_chan->attr.read_timer_interval = uchan->attr.read_timer_interval; - ua_chan->monitor_timer_interval = uchan->monitor_timer_interval; - ua_chan->attr.output = uchan->attr.output; - ua_chan->attr.blocking_timeout = uchan->attr.u.s.blocking_timeout; - - /* - * Note that the attribute channel type is not set since the channel on the - * tracing registry side does not have this information. - */ - - ua_chan->enabled = uchan->enabled; - ua_chan->tracing_channel_id = uchan->id; - - DBG3("UST app shadow copy of channel %s done", ua_chan->name); -} - -/* - * Copy data between a UST app session and a regular LTT session. - */ -static void shadow_copy_session(struct ust_app_session *ua_sess, - struct ltt_ust_session *usess, struct ust_app *app) -{ - struct tm *timeinfo; - char datetime[16]; - int ret; - char tmp_shm_path[PATH_MAX]; - - timeinfo = localtime(&app->registration_time); - strftime(datetime, sizeof(datetime), "%Y%m%d-%H%M%S", timeinfo); - - DBG2("Shadow copy of session handle %d", ua_sess->handle); - - ua_sess->tracing_id = usess->id; - ua_sess->id = get_next_session_id(); - LTTNG_OPTIONAL_SET(&ua_sess->real_credentials.uid, app->uid); - LTTNG_OPTIONAL_SET(&ua_sess->real_credentials.gid, app->gid); - LTTNG_OPTIONAL_SET(&ua_sess->effective_credentials.uid, usess->uid); - LTTNG_OPTIONAL_SET(&ua_sess->effective_credentials.gid, usess->gid); - ua_sess->buffer_type = usess->buffer_type; - ua_sess->bits_per_long = app->bits_per_long; - - /* There is only one consumer object per session possible. */ - consumer_output_get(usess->consumer); - ua_sess->consumer = usess->consumer; - - ua_sess->output_traces = usess->output_traces; - ua_sess->live_timer_interval = usess->live_timer_interval; - copy_channel_attr_to_ustctl(&ua_sess->metadata_attr, - &usess->metadata_attr); - - switch (ua_sess->buffer_type) { - case LTTNG_BUFFER_PER_PID: - ret = snprintf(ua_sess->path, sizeof(ua_sess->path), - DEFAULT_UST_TRACE_PID_PATH "/%s-%d-%s", app->name, app->pid, - datetime); - break; - case LTTNG_BUFFER_PER_UID: - ret = snprintf(ua_sess->path, sizeof(ua_sess->path), - DEFAULT_UST_TRACE_UID_PATH, - lttng_credentials_get_uid(&ua_sess->real_credentials), - app->bits_per_long); - break; - default: - abort(); - goto error; - } - if (ret < 0) { - PERROR("asprintf UST shadow copy session"); - abort(); - goto error; - } - - strncpy(ua_sess->root_shm_path, usess->root_shm_path, - sizeof(ua_sess->root_shm_path)); - ua_sess->root_shm_path[sizeof(ua_sess->root_shm_path) - 1] = '\0'; - strncpy(ua_sess->shm_path, usess->shm_path, - sizeof(ua_sess->shm_path)); - ua_sess->shm_path[sizeof(ua_sess->shm_path) - 1] = '\0'; - if (ua_sess->shm_path[0]) { - switch (ua_sess->buffer_type) { - case LTTNG_BUFFER_PER_PID: - ret = snprintf(tmp_shm_path, sizeof(tmp_shm_path), - "/" DEFAULT_UST_TRACE_PID_PATH "/%s-%d-%s", - app->name, app->pid, datetime); - break; - case LTTNG_BUFFER_PER_UID: - ret = snprintf(tmp_shm_path, sizeof(tmp_shm_path), - "/" DEFAULT_UST_TRACE_UID_PATH, - app->uid, app->bits_per_long); - break; - default: - abort(); - goto error; - } - if (ret < 0) { - PERROR("sprintf UST shadow copy session"); - abort(); - goto error; - } - strncat(ua_sess->shm_path, tmp_shm_path, - sizeof(ua_sess->shm_path) - strlen(ua_sess->shm_path) - 1); - ua_sess->shm_path[sizeof(ua_sess->shm_path) - 1] = '\0'; - } - return; - -error: - consumer_output_put(ua_sess->consumer); -} - -/* - * Lookup sesison wrapper. - */ -static -void __lookup_session_by_app(const struct ltt_ust_session *usess, - struct ust_app *app, struct lttng_ht_iter *iter) -{ - /* Get right UST app session from app */ - lttng_ht_lookup(app->sessions, &usess->id, iter); -} - -/* - * Return ust app session from the app session hashtable using the UST session - * id. - */ -static struct ust_app_session *lookup_session_by_app( - const struct ltt_ust_session *usess, struct ust_app *app) -{ - struct lttng_ht_iter iter; - struct lttng_ht_node_u64 *node; - - __lookup_session_by_app(usess, app, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (node == NULL) { - goto error; - } - - return caa_container_of(node, struct ust_app_session, node); - -error: - return NULL; -} - -/* - * Setup buffer registry per PID for the given session and application. If none - * is found, a new one is created, added to the global registry and - * initialized. If regp is valid, it's set with the newly created object. - * - * Return 0 on success or else a negative value. - */ -static int setup_buffer_reg_pid(struct ust_app_session *ua_sess, - struct ust_app *app, struct buffer_reg_pid **regp) -{ - int ret = 0; - struct buffer_reg_pid *reg_pid; - - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(app); - - rcu_read_lock(); - - reg_pid = buffer_reg_pid_find(ua_sess->id); - if (!reg_pid) { - /* - * This is the create channel path meaning that if there is NO - * registry available, we have to create one for this session. - */ - ret = buffer_reg_pid_create(ua_sess->id, ®_pid, - ua_sess->root_shm_path, ua_sess->shm_path); - if (ret < 0) { - goto error; - } - } else { - goto end; - } - - /* Initialize registry. */ - ret = ust_registry_session_init(®_pid->registry->reg.ust, app, - app->bits_per_long, app->uint8_t_alignment, - app->uint16_t_alignment, app->uint32_t_alignment, - app->uint64_t_alignment, app->long_alignment, - app->byte_order, app->version.major, app->version.minor, - reg_pid->root_shm_path, reg_pid->shm_path, - lttng_credentials_get_uid(&ua_sess->effective_credentials), - lttng_credentials_get_gid(&ua_sess->effective_credentials), - ua_sess->tracing_id, - app->uid); - if (ret < 0) { - /* - * reg_pid->registry->reg.ust is NULL upon error, so we need to - * destroy the buffer registry, because it is always expected - * that if the buffer registry can be found, its ust registry is - * non-NULL. - */ - buffer_reg_pid_destroy(reg_pid); - goto error; - } - - buffer_reg_pid_add(reg_pid); - - DBG3("UST app buffer registry per PID created successfully"); - -end: - if (regp) { - *regp = reg_pid; - } -error: - rcu_read_unlock(); - return ret; -} - -/* - * Setup buffer registry per UID for the given session and application. If none - * is found, a new one is created, added to the global registry and - * initialized. If regp is valid, it's set with the newly created object. - * - * Return 0 on success or else a negative value. - */ -static int setup_buffer_reg_uid(struct ltt_ust_session *usess, - struct ust_app_session *ua_sess, - struct ust_app *app, struct buffer_reg_uid **regp) -{ - int ret = 0; - struct buffer_reg_uid *reg_uid; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(app); - - rcu_read_lock(); - - reg_uid = buffer_reg_uid_find(usess->id, app->bits_per_long, app->uid); - if (!reg_uid) { - /* - * This is the create channel path meaning that if there is NO - * registry available, we have to create one for this session. - */ - ret = buffer_reg_uid_create(usess->id, app->bits_per_long, app->uid, - LTTNG_DOMAIN_UST, ®_uid, - ua_sess->root_shm_path, ua_sess->shm_path); - if (ret < 0) { - goto error; - } - } else { - goto end; - } - - /* Initialize registry. */ - ret = ust_registry_session_init(®_uid->registry->reg.ust, NULL, - app->bits_per_long, app->uint8_t_alignment, - app->uint16_t_alignment, app->uint32_t_alignment, - app->uint64_t_alignment, app->long_alignment, - app->byte_order, app->version.major, - app->version.minor, reg_uid->root_shm_path, - reg_uid->shm_path, usess->uid, usess->gid, - ua_sess->tracing_id, app->uid); - if (ret < 0) { - /* - * reg_uid->registry->reg.ust is NULL upon error, so we need to - * destroy the buffer registry, because it is always expected - * that if the buffer registry can be found, its ust registry is - * non-NULL. - */ - buffer_reg_uid_destroy(reg_uid, NULL); - goto error; - } - /* Add node to teardown list of the session. */ - cds_list_add(®_uid->lnode, &usess->buffer_reg_uid_list); - - buffer_reg_uid_add(reg_uid); - - DBG3("UST app buffer registry per UID created successfully"); -end: - if (regp) { - *regp = reg_uid; - } -error: - rcu_read_unlock(); - return ret; -} - -/* - * Create a session on the tracer side for the given app. - * - * On success, ua_sess_ptr is populated with the session pointer or else left - * untouched. If the session was created, is_created is set to 1. On error, - * it's left untouched. Note that ua_sess_ptr is mandatory but is_created can - * be NULL. - * - * Returns 0 on success or else a negative code which is either -ENOMEM or - * -ENOTCONN which is the default code if the lttng_ust_ctl_create_session fails. - */ -static int find_or_create_ust_app_session(struct ltt_ust_session *usess, - struct ust_app *app, struct ust_app_session **ua_sess_ptr, - int *is_created) -{ - int ret, created = 0; - struct ust_app_session *ua_sess; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(app); - LTTNG_ASSERT(ua_sess_ptr); - - health_code_update(); - - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - DBG2("UST app pid: %d session id %" PRIu64 " not found, creating it", - app->pid, usess->id); - ua_sess = alloc_ust_app_session(); - if (ua_sess == NULL) { - /* Only malloc can failed so something is really wrong */ - ret = -ENOMEM; - goto error; - } - shadow_copy_session(ua_sess, usess, app); - created = 1; - } - - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_PID: - /* Init local registry. */ - ret = setup_buffer_reg_pid(ua_sess, app, NULL); - if (ret < 0) { - delete_ust_app_session(-1, ua_sess, app); - goto error; - } - break; - case LTTNG_BUFFER_PER_UID: - /* Look for a global registry. If none exists, create one. */ - ret = setup_buffer_reg_uid(usess, ua_sess, app, NULL); - if (ret < 0) { - delete_ust_app_session(-1, ua_sess, app); - goto error; - } - break; - default: - abort(); - ret = -EINVAL; - goto error; - } - - health_code_update(); - - if (ua_sess->handle == -1) { - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_create_session(app->sock); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG("UST app creating session failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - ret = 0; - } else if (ret == -EAGAIN) { - DBG("UST app creating session failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - ret = 0; - } else { - ERR("UST app creating session failed with ret %d: pid = %d, sock =%d", - ret, app->pid, app->sock); - } - delete_ust_app_session(-1, ua_sess, app); - if (ret != -ENOMEM) { - /* - * Tracer is probably gone or got an internal error so let's - * behave like it will soon unregister or not usable. - */ - ret = -ENOTCONN; - } - goto error; - } - - ua_sess->handle = ret; - - /* Add ust app session to app's HT */ - lttng_ht_node_init_u64(&ua_sess->node, - ua_sess->tracing_id); - lttng_ht_add_unique_u64(app->sessions, &ua_sess->node); - lttng_ht_node_init_ulong(&ua_sess->ust_objd_node, ua_sess->handle); - lttng_ht_add_unique_ulong(app->ust_sessions_objd, - &ua_sess->ust_objd_node); - - DBG2("UST app session created successfully with handle %d", ret); - } - - *ua_sess_ptr = ua_sess; - if (is_created) { - *is_created = created; - } - - /* Everything went well. */ - ret = 0; - -error: - health_code_update(); - return ret; -} - -/* - * Match function for a hash table lookup of ust_app_ctx. - * - * It matches an ust app context based on the context type and, in the case - * of perf counters, their name. - */ -static int ht_match_ust_app_ctx(struct cds_lfht_node *node, const void *_key) -{ - struct ust_app_ctx *ctx; - const struct lttng_ust_context_attr *key; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - ctx = caa_container_of(node, struct ust_app_ctx, node.node); - key = _key; - - /* Context type */ - if (ctx->ctx.ctx != key->ctx) { - goto no_match; - } - - switch(key->ctx) { - case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: - if (strncmp(key->u.perf_counter.name, - ctx->ctx.u.perf_counter.name, - sizeof(key->u.perf_counter.name))) { - goto no_match; - } - break; - case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: - if (strcmp(key->u.app_ctx.provider_name, - ctx->ctx.u.app_ctx.provider_name) || - strcmp(key->u.app_ctx.ctx_name, - ctx->ctx.u.app_ctx.ctx_name)) { - goto no_match; - } - break; - default: - break; - } - - /* Match. */ - return 1; - -no_match: - return 0; -} - -/* - * Lookup for an ust app context from an lttng_ust_context. - * - * Must be called while holding RCU read side lock. - * Return an ust_app_ctx object or NULL on error. - */ -static -struct ust_app_ctx *find_ust_app_context(struct lttng_ht *ht, - struct lttng_ust_context_attr *uctx) -{ - struct lttng_ht_iter iter; - struct lttng_ht_node_ulong *node; - struct ust_app_ctx *app_ctx = NULL; - - LTTNG_ASSERT(uctx); - LTTNG_ASSERT(ht); - - /* Lookup using the lttng_ust_context_type and a custom match fct. */ - cds_lfht_lookup(ht->ht, ht->hash_fct((void *) uctx->ctx, lttng_ht_seed), - ht_match_ust_app_ctx, uctx, &iter.iter); - node = lttng_ht_iter_get_node_ulong(&iter); - if (!node) { - goto end; - } - - app_ctx = caa_container_of(node, struct ust_app_ctx, node); - -end: - return app_ctx; -} - -/* - * Create a context for the channel on the tracer. - * - * Called with UST app session lock held and a RCU read side lock. - */ -static -int create_ust_app_channel_context(struct ust_app_channel *ua_chan, - struct lttng_ust_context_attr *uctx, - struct ust_app *app) -{ - int ret = 0; - struct ust_app_ctx *ua_ctx; - - DBG2("UST app adding context to channel %s", ua_chan->name); - - ua_ctx = find_ust_app_context(ua_chan->ctx, uctx); - if (ua_ctx) { - ret = -EEXIST; - goto error; - } - - ua_ctx = alloc_ust_app_ctx(uctx); - if (ua_ctx == NULL) { - /* malloc failed */ - ret = -ENOMEM; - goto error; - } - - lttng_ht_node_init_ulong(&ua_ctx->node, (unsigned long) ua_ctx->ctx.ctx); - lttng_ht_add_ulong(ua_chan->ctx, &ua_ctx->node); - cds_list_add_tail(&ua_ctx->list, &ua_chan->ctx_list); - - ret = create_ust_channel_context(ua_chan, ua_ctx, app); - if (ret < 0) { - goto error; - } - -error: - return ret; -} - -/* - * Enable on the tracer side a ust app event for the session and channel. - * - * Called with UST app session lock held. - */ -static -int enable_ust_app_event(struct ust_app_session *ua_sess, - struct ust_app_event *ua_event, struct ust_app *app) -{ - int ret; - - ret = enable_ust_object(app, ua_event->obj); - if (ret < 0) { - goto error; - } - - ua_event->enabled = 1; - -error: - return ret; -} - -/* - * Disable on the tracer side a ust app event for the session and channel. - */ -static int disable_ust_app_event(struct ust_app_session *ua_sess, - struct ust_app_event *ua_event, struct ust_app *app) -{ - int ret; - - ret = disable_ust_object(app, ua_event->obj); - if (ret < 0) { - goto error; - } - - ua_event->enabled = 0; - -error: - return ret; -} - -/* - * Lookup ust app channel for session and disable it on the tracer side. - */ -static -int disable_ust_app_channel(struct ust_app_session *ua_sess, - struct ust_app_channel *ua_chan, struct ust_app *app) -{ - int ret; - - ret = disable_ust_channel(app, ua_sess, ua_chan); - if (ret < 0) { - goto error; - } - - ua_chan->enabled = 0; - -error: - return ret; -} - -/* - * Lookup ust app channel for session and enable it on the tracer side. This - * MUST be called with a RCU read side lock acquired. - */ -static int enable_ust_app_channel(struct ust_app_session *ua_sess, - struct ltt_ust_channel *uchan, struct ust_app *app) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct lttng_ht_node_str *ua_chan_node; - struct ust_app_channel *ua_chan; - - lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &iter); - ua_chan_node = lttng_ht_iter_get_node_str(&iter); - if (ua_chan_node == NULL) { - DBG2("Unable to find channel %s in ust session id %" PRIu64, - uchan->name, ua_sess->tracing_id); - goto error; - } - - ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); - - ret = enable_ust_channel(app, ua_sess, ua_chan); - if (ret < 0) { - goto error; - } - -error: - return ret; -} - -/* - * Ask the consumer to create a channel and get it if successful. - * - * Called with UST app session lock held. - * - * Return 0 on success or else a negative value. - */ -static int do_consumer_create_channel(struct ltt_ust_session *usess, - struct ust_app_session *ua_sess, struct ust_app_channel *ua_chan, - int bitness, struct ust_registry_session *registry, - uint64_t trace_archive_id) -{ - int ret; - unsigned int nb_fd = 0; - struct consumer_socket *socket; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(ua_chan); - LTTNG_ASSERT(registry); - - rcu_read_lock(); - health_code_update(); - - /* Get the right consumer socket for the application. */ - socket = consumer_find_socket_by_bitness(bitness, usess->consumer); - if (!socket) { - ret = -EINVAL; - goto error; - } - - health_code_update(); - - /* Need one fd for the channel. */ - ret = lttng_fd_get(LTTNG_FD_APPS, 1); - if (ret < 0) { - ERR("Exhausted number of available FD upon create channel"); - goto error; - } - - /* - * Ask consumer to create channel. The consumer will return the number of - * stream we have to expect. - */ - ret = ust_consumer_ask_channel(ua_sess, ua_chan, usess->consumer, socket, - registry, usess->current_trace_chunk); - if (ret < 0) { - goto error_ask; - } - - /* - * Compute the number of fd needed before receiving them. It must be 2 per - * stream (2 being the default value here). - */ - nb_fd = DEFAULT_UST_STREAM_FD_NUM * ua_chan->expected_stream_count; - - /* Reserve the amount of file descriptor we need. */ - ret = lttng_fd_get(LTTNG_FD_APPS, nb_fd); - if (ret < 0) { - ERR("Exhausted number of available FD upon create channel"); - goto error_fd_get_stream; - } - - health_code_update(); - - /* - * Now get the channel from the consumer. This call will populate the stream - * list of that channel and set the ust objects. - */ - if (usess->consumer->enabled) { - ret = ust_consumer_get_channel(socket, ua_chan); - if (ret < 0) { - goto error_destroy; - } - } - - rcu_read_unlock(); - return 0; - -error_destroy: - lttng_fd_put(LTTNG_FD_APPS, nb_fd); -error_fd_get_stream: - /* - * Initiate a destroy channel on the consumer since we had an error - * handling it on our side. The return value is of no importance since we - * already have a ret value set by the previous error that we need to - * return. - */ - (void) ust_consumer_destroy_channel(socket, ua_chan); -error_ask: - lttng_fd_put(LTTNG_FD_APPS, 1); -error: - health_code_update(); - rcu_read_unlock(); - return ret; -} - -/* - * Duplicate the ust data object of the ust app stream and save it in the - * buffer registry stream. - * - * Return 0 on success or else a negative value. - */ -static int duplicate_stream_object(struct buffer_reg_stream *reg_stream, - struct ust_app_stream *stream) -{ - int ret; - - LTTNG_ASSERT(reg_stream); - LTTNG_ASSERT(stream); - - /* Duplicating a stream requires 2 new fds. Reserve them. */ - ret = lttng_fd_get(LTTNG_FD_APPS, 2); - if (ret < 0) { - ERR("Exhausted number of available FD upon duplicate stream"); - goto error; - } - - /* Duplicate object for stream once the original is in the registry. */ - ret = lttng_ust_ctl_duplicate_ust_object_data(&stream->obj, - reg_stream->obj.ust); - if (ret < 0) { - ERR("Duplicate stream obj from %p to %p failed with ret %d", - reg_stream->obj.ust, stream->obj, ret); - lttng_fd_put(LTTNG_FD_APPS, 2); - goto error; - } - stream->handle = stream->obj->handle; - -error: - return ret; -} - -/* - * Duplicate the ust data object of the ust app. channel and save it in the - * buffer registry channel. - * - * Return 0 on success or else a negative value. - */ -static int duplicate_channel_object(struct buffer_reg_channel *buf_reg_chan, - struct ust_app_channel *ua_chan) -{ - int ret; - - LTTNG_ASSERT(buf_reg_chan); - LTTNG_ASSERT(ua_chan); - - /* Duplicating a channel requires 1 new fd. Reserve it. */ - ret = lttng_fd_get(LTTNG_FD_APPS, 1); - if (ret < 0) { - ERR("Exhausted number of available FD upon duplicate channel"); - goto error_fd_get; - } - - /* Duplicate object for stream once the original is in the registry. */ - ret = lttng_ust_ctl_duplicate_ust_object_data(&ua_chan->obj, buf_reg_chan->obj.ust); - if (ret < 0) { - ERR("Duplicate channel obj from %p to %p failed with ret: %d", - buf_reg_chan->obj.ust, ua_chan->obj, ret); - goto error; - } - ua_chan->handle = ua_chan->obj->handle; - - return 0; - -error: - lttng_fd_put(LTTNG_FD_APPS, 1); -error_fd_get: - return ret; -} - -/* - * For a given channel buffer registry, setup all streams of the given ust - * application channel. - * - * Return 0 on success or else a negative value. - */ -static int setup_buffer_reg_streams(struct buffer_reg_channel *buf_reg_chan, - struct ust_app_channel *ua_chan, - struct ust_app *app) -{ - int ret = 0; - struct ust_app_stream *stream, *stmp; - - LTTNG_ASSERT(buf_reg_chan); - LTTNG_ASSERT(ua_chan); - - DBG2("UST app setup buffer registry stream"); - - /* Send all streams to application. */ - cds_list_for_each_entry_safe(stream, stmp, &ua_chan->streams.head, list) { - struct buffer_reg_stream *reg_stream; - - ret = buffer_reg_stream_create(®_stream); - if (ret < 0) { - goto error; - } - - /* - * Keep original pointer and nullify it in the stream so the delete - * stream call does not release the object. - */ - reg_stream->obj.ust = stream->obj; - stream->obj = NULL; - buffer_reg_stream_add(reg_stream, buf_reg_chan); - - /* We don't need the streams anymore. */ - cds_list_del(&stream->list); - delete_ust_app_stream(-1, stream, app); - } - -error: - return ret; -} - -/* - * Create a buffer registry channel for the given session registry and - * application channel object. If regp pointer is valid, it's set with the - * created object. Important, the created object is NOT added to the session - * registry hash table. - * - * Return 0 on success else a negative value. - */ -static int create_buffer_reg_channel(struct buffer_reg_session *reg_sess, - struct ust_app_channel *ua_chan, struct buffer_reg_channel **regp) -{ - int ret; - struct buffer_reg_channel *buf_reg_chan = NULL; - - LTTNG_ASSERT(reg_sess); - LTTNG_ASSERT(ua_chan); - - DBG2("UST app creating buffer registry channel for %s", ua_chan->name); - - /* Create buffer registry channel. */ - ret = buffer_reg_channel_create(ua_chan->tracing_channel_id, &buf_reg_chan); - if (ret < 0) { - goto error_create; - } - LTTNG_ASSERT(buf_reg_chan); - buf_reg_chan->consumer_key = ua_chan->key; - buf_reg_chan->subbuf_size = ua_chan->attr.subbuf_size; - buf_reg_chan->num_subbuf = ua_chan->attr.num_subbuf; - - /* Create and add a channel registry to session. */ - ret = ust_registry_channel_add(reg_sess->reg.ust, - ua_chan->tracing_channel_id); - if (ret < 0) { - goto error; - } - buffer_reg_channel_add(reg_sess, buf_reg_chan); - - if (regp) { - *regp = buf_reg_chan; - } - - return 0; - -error: - /* Safe because the registry channel object was not added to any HT. */ - buffer_reg_channel_destroy(buf_reg_chan, LTTNG_DOMAIN_UST); -error_create: - return ret; -} - -/* - * Setup buffer registry channel for the given session registry and application - * channel object. If regp pointer is valid, it's set with the created object. - * - * Return 0 on success else a negative value. - */ -static int setup_buffer_reg_channel(struct buffer_reg_session *reg_sess, - struct ust_app_channel *ua_chan, struct buffer_reg_channel *buf_reg_chan, - struct ust_app *app) -{ - int ret; - - LTTNG_ASSERT(reg_sess); - LTTNG_ASSERT(buf_reg_chan); - LTTNG_ASSERT(ua_chan); - LTTNG_ASSERT(ua_chan->obj); - - DBG2("UST app setup buffer registry channel for %s", ua_chan->name); - - /* Setup all streams for the registry. */ - ret = setup_buffer_reg_streams(buf_reg_chan, ua_chan, app); - if (ret < 0) { - goto error; - } - - buf_reg_chan->obj.ust = ua_chan->obj; - ua_chan->obj = NULL; - - return 0; - -error: - buffer_reg_channel_remove(reg_sess, buf_reg_chan); - buffer_reg_channel_destroy(buf_reg_chan, LTTNG_DOMAIN_UST); - return ret; -} - -/* - * Send buffer registry channel to the application. - * - * Return 0 on success else a negative value. - */ -static int send_channel_uid_to_ust(struct buffer_reg_channel *buf_reg_chan, - struct ust_app *app, struct ust_app_session *ua_sess, - struct ust_app_channel *ua_chan) -{ - int ret; - struct buffer_reg_stream *reg_stream; - - LTTNG_ASSERT(buf_reg_chan); - LTTNG_ASSERT(app); - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(ua_chan); - - DBG("UST app sending buffer registry channel to ust sock %d", app->sock); - - ret = duplicate_channel_object(buf_reg_chan, ua_chan); - if (ret < 0) { - goto error; - } - - /* Send channel to the application. */ - ret = ust_consumer_send_channel_to_ust(app, ua_sess, ua_chan); - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = -ENOTCONN; /* Caused by app exiting. */ - goto error; - } else if (ret == -EAGAIN) { - /* Caused by timeout. */ - WARN("Communication with application %d timed out on send_channel for channel \"%s\" of session \"%" PRIu64 "\".", - app->pid, ua_chan->name, ua_sess->tracing_id); - /* Treat this the same way as an application that is exiting. */ - ret = -ENOTCONN; - goto error; - } else if (ret < 0) { - goto error; - } - - health_code_update(); - - /* Send all streams to application. */ - pthread_mutex_lock(&buf_reg_chan->stream_list_lock); - cds_list_for_each_entry(reg_stream, &buf_reg_chan->streams, lnode) { - struct ust_app_stream stream; - - ret = duplicate_stream_object(reg_stream, &stream); - if (ret < 0) { - goto error_stream_unlock; - } - - ret = ust_consumer_send_stream_to_ust(app, ua_chan, &stream); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = -ENOTCONN; /* Caused by app exiting. */ - } else if (ret == -EAGAIN) { - /* - * Caused by timeout. - * Treat this the same way as an application - * that is exiting. - */ - WARN("Communication with application %d timed out on send_stream for stream \"%s\" of channel \"%s\" of session \"%" PRIu64 "\".", - app->pid, stream.name, - ua_chan->name, - ua_sess->tracing_id); - ret = -ENOTCONN; - } - (void) release_ust_app_stream(-1, &stream, app); - goto error_stream_unlock; - } - - /* - * The return value is not important here. This function will output an - * error if needed. - */ - (void) release_ust_app_stream(-1, &stream, app); - } - ua_chan->is_sent = 1; - -error_stream_unlock: - pthread_mutex_unlock(&buf_reg_chan->stream_list_lock); -error: - return ret; -} - -/* - * Create and send to the application the created buffers with per UID buffers. - * - * This MUST be called with a RCU read side lock acquired. - * The session list lock and the session's lock must be acquired. - * - * Return 0 on success else a negative value. - */ -static int create_channel_per_uid(struct ust_app *app, - struct ltt_ust_session *usess, struct ust_app_session *ua_sess, - struct ust_app_channel *ua_chan) -{ - int ret; - struct buffer_reg_uid *reg_uid; - struct buffer_reg_channel *buf_reg_chan; - struct ltt_session *session = NULL; - enum lttng_error_code notification_ret; - struct ust_registry_channel *ust_reg_chan; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(usess); - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(ua_chan); - - DBG("UST app creating channel %s with per UID buffers", ua_chan->name); - - reg_uid = buffer_reg_uid_find(usess->id, app->bits_per_long, app->uid); - /* - * The session creation handles the creation of this global registry - * object. If none can be find, there is a code flow problem or a - * teardown race. - */ - LTTNG_ASSERT(reg_uid); - - buf_reg_chan = buffer_reg_channel_find(ua_chan->tracing_channel_id, - reg_uid); - if (buf_reg_chan) { - goto send_channel; - } - - /* Create the buffer registry channel object. */ - ret = create_buffer_reg_channel(reg_uid->registry, ua_chan, &buf_reg_chan); - if (ret < 0) { - ERR("Error creating the UST channel \"%s\" registry instance", - ua_chan->name); - goto error; - } - - session = session_find_by_id(ua_sess->tracing_id); - LTTNG_ASSERT(session); - LTTNG_ASSERT(pthread_mutex_trylock(&session->lock)); - LTTNG_ASSERT(session_trylock_list()); - - /* - * Create the buffers on the consumer side. This call populates the - * ust app channel object with all streams and data object. - */ - ret = do_consumer_create_channel(usess, ua_sess, ua_chan, - app->bits_per_long, reg_uid->registry->reg.ust, - session->most_recent_chunk_id.value); - if (ret < 0) { - ERR("Error creating UST channel \"%s\" on the consumer daemon", - ua_chan->name); - - /* - * Let's remove the previously created buffer registry channel so - * it's not visible anymore in the session registry. - */ - ust_registry_channel_del_free(reg_uid->registry->reg.ust, - ua_chan->tracing_channel_id, false); - buffer_reg_channel_remove(reg_uid->registry, buf_reg_chan); - buffer_reg_channel_destroy(buf_reg_chan, LTTNG_DOMAIN_UST); - goto error; - } - - /* - * Setup the streams and add it to the session registry. - */ - ret = setup_buffer_reg_channel(reg_uid->registry, - ua_chan, buf_reg_chan, app); - if (ret < 0) { - ERR("Error setting up UST channel \"%s\"", ua_chan->name); - goto error; - } - - /* Notify the notification subsystem of the channel's creation. */ - pthread_mutex_lock(®_uid->registry->reg.ust->lock); - ust_reg_chan = ust_registry_channel_find(reg_uid->registry->reg.ust, - ua_chan->tracing_channel_id); - LTTNG_ASSERT(ust_reg_chan); - ust_reg_chan->consumer_key = ua_chan->key; - ust_reg_chan = NULL; - pthread_mutex_unlock(®_uid->registry->reg.ust->lock); - - notification_ret = notification_thread_command_add_channel( - the_notification_thread_handle, session->name, - lttng_credentials_get_uid( - &ua_sess->effective_credentials), - lttng_credentials_get_gid( - &ua_sess->effective_credentials), - ua_chan->name, ua_chan->key, LTTNG_DOMAIN_UST, - ua_chan->attr.subbuf_size * ua_chan->attr.num_subbuf); - if (notification_ret != LTTNG_OK) { - ret = - (int) notification_ret; - ERR("Failed to add channel to notification thread"); - goto error; - } - -send_channel: - /* Send buffers to the application. */ - ret = send_channel_uid_to_ust(buf_reg_chan, app, ua_sess, ua_chan); - if (ret < 0) { - if (ret != -ENOTCONN) { - ERR("Error sending channel to application"); - } - goto error; - } - -error: - if (session) { - session_put(session); - } - return ret; -} - -/* - * Create and send to the application the created buffers with per PID buffers. - * - * Called with UST app session lock held. - * The session list lock and the session's lock must be acquired. - * - * Return 0 on success else a negative value. - */ -static int create_channel_per_pid(struct ust_app *app, - struct ltt_ust_session *usess, struct ust_app_session *ua_sess, - struct ust_app_channel *ua_chan) -{ - int ret; - struct ust_registry_session *registry; - enum lttng_error_code cmd_ret; - struct ltt_session *session = NULL; - uint64_t chan_reg_key; - struct ust_registry_channel *ust_reg_chan; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(usess); - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(ua_chan); - - DBG("UST app creating channel %s with per PID buffers", ua_chan->name); - - rcu_read_lock(); - - registry = get_session_registry(ua_sess); - /* The UST app session lock is held, registry shall not be null. */ - LTTNG_ASSERT(registry); - - /* Create and add a new channel registry to session. */ - ret = ust_registry_channel_add(registry, ua_chan->key); - if (ret < 0) { - ERR("Error creating the UST channel \"%s\" registry instance", - ua_chan->name); - goto error; - } - - session = session_find_by_id(ua_sess->tracing_id); - LTTNG_ASSERT(session); - - LTTNG_ASSERT(pthread_mutex_trylock(&session->lock)); - LTTNG_ASSERT(session_trylock_list()); - - /* Create and get channel on the consumer side. */ - ret = do_consumer_create_channel(usess, ua_sess, ua_chan, - app->bits_per_long, registry, - session->most_recent_chunk_id.value); - if (ret < 0) { - ERR("Error creating UST channel \"%s\" on the consumer daemon", - ua_chan->name); - goto error_remove_from_registry; - } - - ret = send_channel_pid_to_ust(app, ua_sess, ua_chan); - if (ret < 0) { - if (ret != -ENOTCONN) { - ERR("Error sending channel to application"); - } - goto error_remove_from_registry; - } - - chan_reg_key = ua_chan->key; - pthread_mutex_lock(®istry->lock); - ust_reg_chan = ust_registry_channel_find(registry, chan_reg_key); - LTTNG_ASSERT(ust_reg_chan); - ust_reg_chan->consumer_key = ua_chan->key; - pthread_mutex_unlock(®istry->lock); - - cmd_ret = notification_thread_command_add_channel( - the_notification_thread_handle, session->name, - lttng_credentials_get_uid( - &ua_sess->effective_credentials), - lttng_credentials_get_gid( - &ua_sess->effective_credentials), - ua_chan->name, ua_chan->key, LTTNG_DOMAIN_UST, - ua_chan->attr.subbuf_size * ua_chan->attr.num_subbuf); - if (cmd_ret != LTTNG_OK) { - ret = - (int) cmd_ret; - ERR("Failed to add channel to notification thread"); - goto error_remove_from_registry; - } - -error_remove_from_registry: - if (ret) { - ust_registry_channel_del_free(registry, ua_chan->key, false); - } -error: - rcu_read_unlock(); - if (session) { - session_put(session); - } - return ret; -} - -/* - * From an already allocated ust app channel, create the channel buffers if - * needed and send them to the application. This MUST be called with a RCU read - * side lock acquired. - * - * Called with UST app session lock held. - * - * Return 0 on success or else a negative value. Returns -ENOTCONN if - * the application exited concurrently. - */ -static int ust_app_channel_send(struct ust_app *app, - struct ltt_ust_session *usess, struct ust_app_session *ua_sess, - struct ust_app_channel *ua_chan) -{ - int ret; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(usess); - LTTNG_ASSERT(usess->active); - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(ua_chan); - - /* Handle buffer type before sending the channel to the application. */ - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_UID: - { - ret = create_channel_per_uid(app, usess, ua_sess, ua_chan); - if (ret < 0) { - goto error; - } - break; - } - case LTTNG_BUFFER_PER_PID: - { - ret = create_channel_per_pid(app, usess, ua_sess, ua_chan); - if (ret < 0) { - goto error; - } - break; - } - default: - abort(); - ret = -EINVAL; - goto error; - } - - /* Initialize ust objd object using the received handle and add it. */ - lttng_ht_node_init_ulong(&ua_chan->ust_objd_node, ua_chan->handle); - lttng_ht_add_unique_ulong(app->ust_objd, &ua_chan->ust_objd_node); - - /* If channel is not enabled, disable it on the tracer */ - if (!ua_chan->enabled) { - ret = disable_ust_channel(app, ua_sess, ua_chan); - if (ret < 0) { - goto error; - } - } - -error: - return ret; -} - -/* - * Create UST app channel and return it through ua_chanp if not NULL. - * - * Called with UST app session lock and RCU read-side lock held. - * - * Return 0 on success or else a negative value. - */ -static int ust_app_channel_allocate(struct ust_app_session *ua_sess, - struct ltt_ust_channel *uchan, - enum lttng_ust_abi_chan_type type, struct ltt_ust_session *usess, - struct ust_app_channel **ua_chanp) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct lttng_ht_node_str *ua_chan_node; - struct ust_app_channel *ua_chan; - - /* Lookup channel in the ust app session */ - lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &iter); - ua_chan_node = lttng_ht_iter_get_node_str(&iter); - if (ua_chan_node != NULL) { - ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); - goto end; - } - - ua_chan = alloc_ust_app_channel(uchan->name, ua_sess, &uchan->attr); - if (ua_chan == NULL) { - /* Only malloc can fail here */ - ret = -ENOMEM; - goto error; - } - shadow_copy_channel(ua_chan, uchan); - - /* Set channel type. */ - ua_chan->attr.type = type; - - /* Only add the channel if successful on the tracer side. */ - lttng_ht_add_unique_str(ua_sess->channels, &ua_chan->node); -end: - if (ua_chanp) { - *ua_chanp = ua_chan; - } - - /* Everything went well. */ - return 0; - -error: - return ret; -} - -/* - * Create UST app event and create it on the tracer side. - * - * Must be called with the RCU read side lock held. - * Called with ust app session mutex held. - */ -static -int create_ust_app_event(struct ust_app_session *ua_sess, - struct ust_app_channel *ua_chan, struct ltt_ust_event *uevent, - struct ust_app *app) -{ - int ret = 0; - struct ust_app_event *ua_event; - - ua_event = alloc_ust_app_event(uevent->attr.name, &uevent->attr); - if (ua_event == NULL) { - /* Only failure mode of alloc_ust_app_event(). */ - ret = -ENOMEM; - goto end; - } - shadow_copy_event(ua_event, uevent); - - /* Create it on the tracer side */ - ret = create_ust_event(app, ua_sess, ua_chan, ua_event); - 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 an event being created already existed: " - "event_name = \"%s\", pid = %d, ppid = %d, uid = %d, gid = %d", - uevent->attr.name, - app->pid, app->ppid, app->uid, - app->gid); - } - goto error; - } - - add_unique_ust_app_event(ua_chan, ua_event); - - DBG2("UST app create event completed: app = '%s' pid = %d", - app->name, app->pid); - -end: - return ret; - -error: - /* Valid. Calling here is already in a read side lock */ - delete_ust_app_event(-1, ua_event, app); - return ret; -} - -/* - * Create UST app event notifier rule and create it on the tracer side. - * - * Must be called with the RCU read side lock held. - * Called with ust app session mutex held. - */ -static -int create_ust_app_event_notifier_rule(struct lttng_trigger *trigger, - struct ust_app *app) -{ - int ret = 0; - struct ust_app_event_notifier_rule *ua_event_notifier_rule; - - ua_event_notifier_rule = alloc_ust_app_event_notifier_rule(trigger); - if (ua_event_notifier_rule == NULL) { - ret = -ENOMEM; - goto end; - } - - /* Create it on the tracer side. */ - ret = create_ust_event_notifier(app, ua_event_notifier_rule); - 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 an event notifier being created already exists: " - "token = \"%" PRIu64 "\", pid = %d, ppid = %d, uid = %d, gid = %d", - lttng_trigger_get_tracer_token(trigger), - app->pid, app->ppid, app->uid, - app->gid); - } - goto error; - } - - lttng_ht_add_unique_u64(app->token_to_event_notifier_rule_ht, - &ua_event_notifier_rule->node); - - DBG2("UST app create token event rule completed: app = '%s', pid = %d), token = %" PRIu64, - app->name, app->pid, lttng_trigger_get_tracer_token(trigger)); - - goto end; - -error: - /* The RCU read side lock is already being held by the caller. */ - delete_ust_app_event_notifier_rule(-1, ua_event_notifier_rule, app); -end: - return ret; -} - -/* - * Create UST metadata and open it on the tracer side. - * - * Called with UST app session lock held and RCU read side lock. - */ -static int create_ust_app_metadata(struct ust_app_session *ua_sess, - struct ust_app *app, struct consumer_output *consumer) -{ - int ret = 0; - struct ust_app_channel *metadata; - struct consumer_socket *socket; - struct ust_registry_session *registry; - struct ltt_session *session = NULL; - - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(app); - LTTNG_ASSERT(consumer); - - registry = get_session_registry(ua_sess); - /* The UST app session is held registry shall not be null. */ - LTTNG_ASSERT(registry); - - pthread_mutex_lock(®istry->lock); - - /* Metadata already exists for this registry or it was closed previously */ - if (registry->metadata_key || registry->metadata_closed) { - ret = 0; - goto error; - } - - /* Allocate UST metadata */ - metadata = alloc_ust_app_channel(DEFAULT_METADATA_NAME, ua_sess, NULL); - if (!metadata) { - /* malloc() failed */ - ret = -ENOMEM; - goto error; - } - - memcpy(&metadata->attr, &ua_sess->metadata_attr, sizeof(metadata->attr)); - - /* Need one fd for the channel. */ - ret = lttng_fd_get(LTTNG_FD_APPS, 1); - if (ret < 0) { - ERR("Exhausted number of available FD upon create metadata"); - goto error; - } - - /* Get the right consumer socket for the application. */ - socket = consumer_find_socket_by_bitness(app->bits_per_long, consumer); - if (!socket) { - ret = -EINVAL; - goto error_consumer; - } - - /* - * Keep metadata key so we can identify it on the consumer side. Assign it - * to the registry *before* we ask the consumer so we avoid the race of the - * consumer requesting the metadata and the ask_channel call on our side - * did not returned yet. - */ - registry->metadata_key = metadata->key; - - session = session_find_by_id(ua_sess->tracing_id); - LTTNG_ASSERT(session); - - LTTNG_ASSERT(pthread_mutex_trylock(&session->lock)); - LTTNG_ASSERT(session_trylock_list()); - - /* - * Ask the metadata channel creation to the consumer. The metadata object - * will be created by the consumer and kept their. However, the stream is - * never added or monitored until we do a first push metadata to the - * consumer. - */ - ret = ust_consumer_ask_channel(ua_sess, metadata, consumer, socket, - registry, session->current_trace_chunk); - if (ret < 0) { - /* Nullify the metadata key so we don't try to close it later on. */ - registry->metadata_key = 0; - goto error_consumer; - } - - /* - * The setup command will make the metadata stream be sent to the relayd, - * if applicable, and the thread managing the metadatas. This is important - * because after this point, if an error occurs, the only way the stream - * can be deleted is to be monitored in the consumer. - */ - ret = consumer_setup_metadata(socket, metadata->key); - if (ret < 0) { - /* Nullify the metadata key so we don't try to close it later on. */ - registry->metadata_key = 0; - goto error_consumer; - } - - DBG2("UST metadata with key %" PRIu64 " created for app pid %d", - metadata->key, app->pid); - -error_consumer: - lttng_fd_put(LTTNG_FD_APPS, 1); - delete_ust_app_channel(-1, metadata, app); -error: - pthread_mutex_unlock(®istry->lock); - if (session) { - session_put(session); - } - return ret; -} - -/* - * Return ust app pointer or NULL if not found. RCU read side lock MUST be - * acquired before calling this function. - */ -struct ust_app *ust_app_find_by_pid(pid_t pid) -{ - struct ust_app *app = NULL; - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter iter; - - lttng_ht_lookup(ust_app_ht, (void *)((unsigned long) pid), &iter); - node = lttng_ht_iter_get_node_ulong(&iter); - if (node == NULL) { - DBG2("UST app no found with pid %d", pid); - goto error; - } - - DBG2("Found UST app by pid %d", pid); - - app = caa_container_of(node, struct ust_app, pid_n); - -error: - return app; -} - -/* - * Allocate and init an UST app object using the registration information and - * the command socket. This is called when the command socket connects to the - * session daemon. - * - * The object is returned on success or else NULL. - */ -struct ust_app *ust_app_create(struct ust_register_msg *msg, int sock) -{ - int ret; - struct ust_app *lta = NULL; - struct lttng_pipe *event_notifier_event_source_pipe = NULL; - - LTTNG_ASSERT(msg); - LTTNG_ASSERT(sock >= 0); - - DBG3("UST app creating application for socket %d", sock); - - if ((msg->bits_per_long == 64 && - (uatomic_read(&the_ust_consumerd64_fd) == - -EINVAL)) || - (msg->bits_per_long == 32 && - (uatomic_read(&the_ust_consumerd32_fd) == - -EINVAL))) { - ERR("Registration failed: application \"%s\" (pid: %d) has " - "%d-bit long, but no consumerd for this size is available.\n", - msg->name, msg->pid, msg->bits_per_long); - goto error; - } - - /* - * Reserve the two file descriptors of the event source pipe. The write - * end will be closed once it is passed to the application, at which - * point a single 'put' will be performed. - */ - ret = lttng_fd_get(LTTNG_FD_APPS, 2); - if (ret) { - ERR("Failed to reserve two file descriptors for the event source pipe while creating a new application instance: app = '%s', pid = %d", - msg->name, (int) msg->pid); - goto error; - } - - event_notifier_event_source_pipe = lttng_pipe_open(FD_CLOEXEC); - if (!event_notifier_event_source_pipe) { - PERROR("Failed to open application event source pipe: '%s' (pid = %d)", - msg->name, msg->pid); - goto error; - } - - lta = zmalloc(sizeof(struct ust_app)); - if (lta == NULL) { - PERROR("malloc"); - goto error_free_pipe; - } - - lta->event_notifier_group.event_pipe = event_notifier_event_source_pipe; - - lta->ppid = msg->ppid; - lta->uid = msg->uid; - lta->gid = msg->gid; - - lta->bits_per_long = msg->bits_per_long; - lta->uint8_t_alignment = msg->uint8_t_alignment; - lta->uint16_t_alignment = msg->uint16_t_alignment; - lta->uint32_t_alignment = msg->uint32_t_alignment; - lta->uint64_t_alignment = msg->uint64_t_alignment; - lta->long_alignment = msg->long_alignment; - lta->byte_order = msg->byte_order; - - lta->v_major = msg->major; - lta->v_minor = msg->minor; - lta->sessions = lttng_ht_new(0, LTTNG_HT_TYPE_U64); - 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->token_to_event_notifier_rule_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)); - lta->name[UST_APP_PROCNAME_LEN] = '\0'; - - /* - * Before this can be called, when receiving the registration information, - * the application compatibility is checked. So, at this point, the - * application can work with this session daemon. - */ - lta->compatible = 1; - - lta->pid = msg->pid; - lttng_ht_node_init_ulong(<a->pid_n, (unsigned long) lta->pid); - lta->sock = sock; - pthread_mutex_init(<a->sock_lock, NULL); - lttng_ht_node_init_ulong(<a->sock_n, (unsigned long) lta->sock); - - CDS_INIT_LIST_HEAD(<a->teardown_head); - return lta; - -error_free_pipe: - lttng_pipe_destroy(event_notifier_event_source_pipe); - lttng_fd_put(LTTNG_FD_APPS, 2); -error: - return NULL; -} - -/* - * For a given application object, add it to every hash table. - */ -void ust_app_add(struct ust_app *app) -{ - LTTNG_ASSERT(app); - LTTNG_ASSERT(app->notify_sock >= 0); - - app->registration_time = time(NULL); - - rcu_read_lock(); - - /* - * On a re-registration, we want to kick out the previous registration of - * that pid - */ - lttng_ht_add_replace_ulong(ust_app_ht, &app->pid_n); - - /* - * The socket _should_ be unique until _we_ call close. So, a add_unique - * for the ust_app_ht_by_sock is used which asserts fail if the entry was - * already in the table. - */ - lttng_ht_add_unique_ulong(ust_app_ht_by_sock, &app->sock_n); - - /* Add application to the notify socket hash table. */ - lttng_ht_node_init_ulong(&app->notify_sock_n, app->notify_sock); - lttng_ht_add_unique_ulong(ust_app_ht_by_notify_sock, &app->notify_sock_n); - - DBG("App registered with pid:%d ppid:%d uid:%d gid:%d sock =%d name:%s " - "notify_sock =%d (version %d.%d)", app->pid, app->ppid, app->uid, - app->gid, app->sock, app->name, app->notify_sock, app->v_major, - app->v_minor); - - rcu_read_unlock(); -} - -/* - * Set the application version into the object. - * - * Return 0 on success else a negative value either an errno code or a - * LTTng-UST error code. - */ -int ust_app_version(struct ust_app *app) -{ - int ret; - - LTTNG_ASSERT(app); - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_tracer_version(app->sock, &app->version); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -LTTNG_UST_ERR_EXITING || ret == -EPIPE) { - DBG3("UST app version failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app version failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app version failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - } - - return ret; -} - -bool ust_app_supports_notifiers(const struct ust_app *app) -{ - return app->v_major >= 9; -} - -bool ust_app_supports_counters(const struct ust_app *app) -{ - return app->v_major >= 9; -} - -/* - * Setup the base event notifier group. - * - * Return 0 on success else a negative value either an errno code or a - * LTTng-UST error code. - */ -int ust_app_setup_event_notifier_group(struct ust_app *app) -{ - int ret; - int event_pipe_write_fd; - struct lttng_ust_abi_object_data *event_notifier_group = NULL; - enum lttng_error_code lttng_ret; - enum event_notifier_error_accounting_status event_notifier_error_accounting_status; - - LTTNG_ASSERT(app); - - if (!ust_app_supports_notifiers(app)) { - ret = -ENOSYS; - goto error; - } - - /* Get the write side of the pipe. */ - event_pipe_write_fd = lttng_pipe_get_writefd( - app->event_notifier_group.event_pipe); - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_create_event_notifier_group(app->sock, - event_pipe_write_fd, &event_notifier_group); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - ret = 0; - DBG3("UST app create event notifier group failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - ret = 0; - WARN("UST app create event notifier group failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app create event notifier group failed with ret %d: pid = %d, sock = %d, event_pipe_write_fd: %d", - ret, app->pid, app->sock, event_pipe_write_fd); - } - goto error; - } - - ret = lttng_pipe_write_close(app->event_notifier_group.event_pipe); - if (ret) { - ERR("Failed to close write end of the application's event source pipe: app = '%s' (pid = %d)", - app->name, app->pid); - goto error; - } - - /* - * Release the file descriptor that was reserved for the write-end of - * the pipe. - */ - lttng_fd_put(LTTNG_FD_APPS, 1); - - lttng_ret = notification_thread_command_add_tracer_event_source( - the_notification_thread_handle, - lttng_pipe_get_readfd( - app->event_notifier_group.event_pipe), - LTTNG_DOMAIN_UST); - if (lttng_ret != LTTNG_OK) { - ERR("Failed to add tracer event source to notification thread"); - ret = - 1; - goto error; - } - - /* Assign handle only when the complete setup is valid. */ - app->event_notifier_group.object = event_notifier_group; - - event_notifier_error_accounting_status = - event_notifier_error_accounting_register_app(app); - switch (event_notifier_error_accounting_status) { - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK: - break; - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_UNSUPPORTED: - DBG3("Failed to setup event notifier error accounting (application does not support notifier error accounting): app socket fd = %d, app name = '%s', app pid = %d", - app->sock, app->name, (int) app->pid); - ret = 0; - goto error_accounting; - case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD: - DBG3("Failed to setup event notifier error accounting (application is dead): app socket fd = %d, app name = '%s', app pid = %d", - app->sock, app->name, (int) app->pid); - ret = 0; - goto error_accounting; - default: - ERR("Failed to setup event notifier error accounting for app"); - ret = -1; - goto error_accounting; - } - - return ret; - -error_accounting: - lttng_ret = notification_thread_command_remove_tracer_event_source( - the_notification_thread_handle, - lttng_pipe_get_readfd( - app->event_notifier_group.event_pipe)); - if (lttng_ret != LTTNG_OK) { - ERR("Failed to remove application tracer event source from notification thread"); - } - -error: - lttng_ust_ctl_release_object(app->sock, app->event_notifier_group.object); - free(app->event_notifier_group.object); - app->event_notifier_group.object = NULL; - return ret; -} - -/* - * Unregister app by removing it from the global traceable app list and freeing - * the data struct. - * - * The socket is already closed at this point so no close to sock. - */ -void ust_app_unregister(int sock) -{ - struct ust_app *lta; - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter ust_app_sock_iter; - struct lttng_ht_iter iter; - struct ust_app_session *ua_sess; - int ret; - - rcu_read_lock(); - - /* Get the node reference for a call_rcu */ - lttng_ht_lookup(ust_app_ht_by_sock, (void *)((unsigned long) sock), &ust_app_sock_iter); - node = lttng_ht_iter_get_node_ulong(&ust_app_sock_iter); - LTTNG_ASSERT(node); - - lta = caa_container_of(node, struct ust_app, sock_n); - DBG("PID %d unregistering with sock %d", lta->pid, sock); - - /* - * For per-PID buffers, perform "push metadata" and flush all - * application streams before removing app from hash tables, - * ensuring proper behavior of data_pending check. - * Remove sessions so they are not visible during deletion. - */ - cds_lfht_for_each_entry(lta->sessions->ht, &iter.iter, ua_sess, - node.node) { - struct ust_registry_session *registry; - - ret = lttng_ht_del(lta->sessions, &iter); - if (ret) { - /* The session was already removed so scheduled for teardown. */ - continue; - } - - if (ua_sess->buffer_type == LTTNG_BUFFER_PER_PID) { - (void) ust_app_flush_app_session(lta, ua_sess); - } - - /* - * Add session to list for teardown. This is safe since at this point we - * are the only one using this list. - */ - pthread_mutex_lock(&ua_sess->lock); - - if (ua_sess->deleted) { - pthread_mutex_unlock(&ua_sess->lock); - continue; - } - - /* - * Normally, this is done in the delete session process which is - * executed in the call rcu below. However, upon registration we can't - * afford to wait for the grace period before pushing data or else the - * data pending feature can race between the unregistration and stop - * command where the data pending command is sent *before* the grace - * period ended. - * - * The close metadata below nullifies the metadata pointer in the - * session so the delete session will NOT push/close a second time. - */ - registry = get_session_registry(ua_sess); - if (registry) { - /* Push metadata for application before freeing the application. */ - (void) push_metadata(registry, ua_sess->consumer); - - /* - * Don't ask to close metadata for global per UID buffers. Close - * metadata only on destroy trace session in this case. Also, the - * previous push metadata could have flag the metadata registry to - * close so don't send a close command if closed. - */ - if (ua_sess->buffer_type != LTTNG_BUFFER_PER_UID) { - /* And ask to close it for this session registry. */ - (void) close_metadata(registry, ua_sess->consumer); - } - } - cds_list_add(&ua_sess->teardown_node, <a->teardown_head); - - pthread_mutex_unlock(&ua_sess->lock); - } - - /* Remove application from PID hash table */ - ret = lttng_ht_del(ust_app_ht_by_sock, &ust_app_sock_iter); - LTTNG_ASSERT(!ret); - - /* - * Remove application from notify hash table. The thread handling the - * notify socket could have deleted the node so ignore on error because - * either way it's valid. The close of that socket is handled by the - * apps_notify_thread. - */ - iter.iter.node = <a->notify_sock_n.node; - (void) lttng_ht_del(ust_app_ht_by_notify_sock, &iter); - - /* - * Ignore return value since the node might have been removed before by an - * add replace during app registration because the PID can be reassigned by - * the OS. - */ - iter.iter.node = <a->pid_n.node; - ret = lttng_ht_del(ust_app_ht, &iter); - if (ret) { - DBG3("Unregister app by PID %d failed. This can happen on pid reuse", - lta->pid); - } - - /* Free memory */ - call_rcu(<a->pid_n.head, delete_ust_app_rcu); - - rcu_read_unlock(); - return; -} - -/* - * Fill events array with all events name of all registered apps. - */ -int ust_app_list_events(struct lttng_event **events) -{ - int ret, handle; - size_t nbmem, count = 0; - struct lttng_ht_iter iter; - struct ust_app *app; - struct lttng_event *tmp_event; - - nbmem = UST_APP_EVENT_LIST_SIZE; - tmp_event = zmalloc(nbmem * sizeof(struct lttng_event)); - if (tmp_event == NULL) { - PERROR("zmalloc ust app events"); - ret = -ENOMEM; - goto error; - } - - rcu_read_lock(); - - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - struct lttng_ust_abi_tracepoint_iter uiter; - - health_code_update(); - - if (!app->compatible) { - /* - * TODO: In time, we should notice the caller of this error by - * telling him that this is a version error. - */ - continue; - } - pthread_mutex_lock(&app->sock_lock); - handle = lttng_ust_ctl_tracepoint_list(app->sock); - if (handle < 0) { - if (handle != -EPIPE && handle != -LTTNG_UST_ERR_EXITING) { - ERR("UST app list events getting handle failed for app pid %d", - app->pid); - } - pthread_mutex_unlock(&app->sock_lock); - continue; - } - - while ((ret = lttng_ust_ctl_tracepoint_list_get(app->sock, handle, - &uiter)) != -LTTNG_UST_ERR_NOENT) { - /* Handle ustctl error. */ - if (ret < 0) { - int release_ret; - - if (ret != -LTTNG_UST_ERR_EXITING && ret != -EPIPE) { - ERR("UST app tp list get failed for app %d with ret %d", - app->sock, ret); - } else { - DBG3("UST app tp list get failed. Application is dead"); - break; - } - free(tmp_event); - release_ret = lttng_ust_ctl_release_handle(app->sock, handle); - if (release_ret < 0 && - release_ret != -LTTNG_UST_ERR_EXITING && - release_ret != -EPIPE) { - ERR("Error releasing app handle for app %d with ret %d", app->sock, release_ret); - } - pthread_mutex_unlock(&app->sock_lock); - goto rcu_error; - } - - health_code_update(); - if (count >= nbmem) { - /* In case the realloc fails, we free the memory */ - struct lttng_event *new_tmp_event; - size_t new_nbmem; - - new_nbmem = nbmem << 1; - DBG2("Reallocating event list from %zu to %zu entries", - nbmem, new_nbmem); - new_tmp_event = realloc(tmp_event, - new_nbmem * sizeof(struct lttng_event)); - if (new_tmp_event == NULL) { - int release_ret; - - PERROR("realloc ust app events"); - free(tmp_event); - ret = -ENOMEM; - release_ret = lttng_ust_ctl_release_handle(app->sock, handle); - if (release_ret < 0 && - release_ret != -LTTNG_UST_ERR_EXITING && - release_ret != -EPIPE) { - ERR("Error releasing app handle for app %d with ret %d", app->sock, release_ret); - } - pthread_mutex_unlock(&app->sock_lock); - goto rcu_error; - } - /* Zero the new memory */ - memset(new_tmp_event + nbmem, 0, - (new_nbmem - nbmem) * sizeof(struct lttng_event)); - nbmem = new_nbmem; - tmp_event = new_tmp_event; - } - memcpy(tmp_event[count].name, uiter.name, LTTNG_UST_ABI_SYM_NAME_LEN); - tmp_event[count].loglevel = uiter.loglevel; - tmp_event[count].type = (enum lttng_event_type) LTTNG_UST_ABI_TRACEPOINT; - tmp_event[count].pid = app->pid; - tmp_event[count].enabled = -1; - count++; - } - ret = lttng_ust_ctl_release_handle(app->sock, handle); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("Error releasing app handle. Application died: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("Error releasing app handle. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("Error releasing app handle with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - } - } - - ret = count; - *events = tmp_event; - - DBG2("UST app list events done (%zu events)", count); - -rcu_error: - rcu_read_unlock(); -error: - health_code_update(); - return ret; -} - -/* - * Fill events array with all events name of all registered apps. - */ -int ust_app_list_event_fields(struct lttng_event_field **fields) -{ - int ret, handle; - size_t nbmem, count = 0; - struct lttng_ht_iter iter; - struct ust_app *app; - struct lttng_event_field *tmp_event; - - nbmem = UST_APP_EVENT_LIST_SIZE; - tmp_event = zmalloc(nbmem * sizeof(struct lttng_event_field)); - if (tmp_event == NULL) { - PERROR("zmalloc ust app event fields"); - ret = -ENOMEM; - goto error; - } - - rcu_read_lock(); - - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - struct lttng_ust_abi_field_iter uiter; - - health_code_update(); - - if (!app->compatible) { - /* - * TODO: In time, we should notice the caller of this error by - * telling him that this is a version error. - */ - continue; - } - pthread_mutex_lock(&app->sock_lock); - handle = lttng_ust_ctl_tracepoint_field_list(app->sock); - if (handle < 0) { - if (handle != -EPIPE && handle != -LTTNG_UST_ERR_EXITING) { - ERR("UST app list field getting handle failed for app pid %d", - app->pid); - } - pthread_mutex_unlock(&app->sock_lock); - continue; - } - - while ((ret = lttng_ust_ctl_tracepoint_field_list_get(app->sock, handle, - &uiter)) != -LTTNG_UST_ERR_NOENT) { - /* Handle ustctl error. */ - if (ret < 0) { - int release_ret; - - if (ret != -LTTNG_UST_ERR_EXITING && ret != -EPIPE) { - ERR("UST app tp list field failed for app %d with ret %d", - app->sock, ret); - } else { - DBG3("UST app tp list field failed. Application is dead"); - break; - } - free(tmp_event); - release_ret = lttng_ust_ctl_release_handle(app->sock, handle); - pthread_mutex_unlock(&app->sock_lock); - if (release_ret < 0 && - release_ret != -LTTNG_UST_ERR_EXITING && - release_ret != -EPIPE) { - ERR("Error releasing app handle for app %d with ret %d", app->sock, release_ret); - } - goto rcu_error; - } - - health_code_update(); - if (count >= nbmem) { - /* In case the realloc fails, we free the memory */ - struct lttng_event_field *new_tmp_event; - size_t new_nbmem; - - new_nbmem = nbmem << 1; - DBG2("Reallocating event field list from %zu to %zu entries", - nbmem, new_nbmem); - new_tmp_event = realloc(tmp_event, - new_nbmem * sizeof(struct lttng_event_field)); - if (new_tmp_event == NULL) { - int release_ret; - - PERROR("realloc ust app event fields"); - free(tmp_event); - ret = -ENOMEM; - release_ret = lttng_ust_ctl_release_handle(app->sock, handle); - pthread_mutex_unlock(&app->sock_lock); - if (release_ret && - release_ret != -LTTNG_UST_ERR_EXITING && - release_ret != -EPIPE) { - ERR("Error releasing app handle for app %d with ret %d", app->sock, release_ret); - } - goto rcu_error; - } - /* Zero the new memory */ - memset(new_tmp_event + nbmem, 0, - (new_nbmem - nbmem) * sizeof(struct lttng_event_field)); - nbmem = new_nbmem; - tmp_event = new_tmp_event; - } - - memcpy(tmp_event[count].field_name, uiter.field_name, LTTNG_UST_ABI_SYM_NAME_LEN); - /* Mapping between these enums matches 1 to 1. */ - tmp_event[count].type = (enum lttng_event_field_type) uiter.type; - tmp_event[count].nowrite = uiter.nowrite; - - memcpy(tmp_event[count].event.name, uiter.event_name, LTTNG_UST_ABI_SYM_NAME_LEN); - tmp_event[count].event.loglevel = uiter.loglevel; - tmp_event[count].event.type = LTTNG_EVENT_TRACEPOINT; - tmp_event[count].event.pid = app->pid; - tmp_event[count].event.enabled = -1; - count++; - } - ret = lttng_ust_ctl_release_handle(app->sock, handle); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0 && - ret != -LTTNG_UST_ERR_EXITING && - ret != -EPIPE) { - ERR("Error releasing app handle for app %d with ret %d", app->sock, ret); - } - } - - ret = count; - *fields = tmp_event; - - DBG2("UST app list event fields done (%zu events)", count); - -rcu_error: - rcu_read_unlock(); -error: - health_code_update(); - return ret; -} - -/* - * Free and clean all traceable apps of the global list. - * - * Should _NOT_ be called with RCU read-side lock held. - */ -void ust_app_clean_list(void) -{ - int ret; - struct ust_app *app; - struct lttng_ht_iter iter; - - DBG2("UST app cleaning registered apps hash table"); - - rcu_read_lock(); - - /* Cleanup notify socket hash table */ - if (ust_app_ht_by_notify_sock) { - cds_lfht_for_each_entry(ust_app_ht_by_notify_sock->ht, &iter.iter, app, - notify_sock_n.node) { - /* - * Assert that all notifiers are gone as all triggers - * are unregistered prior to this clean-up. - */ - LTTNG_ASSERT(lttng_ht_get_count(app->token_to_event_notifier_rule_ht) == 0); - - ust_app_notify_sock_unregister(app->notify_sock); - } - } - - if (ust_app_ht) { - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - ret = lttng_ht_del(ust_app_ht, &iter); - LTTNG_ASSERT(!ret); - call_rcu(&app->pid_n.head, delete_ust_app_rcu); - } - } - - /* Cleanup socket hash table */ - if (ust_app_ht_by_sock) { - cds_lfht_for_each_entry(ust_app_ht_by_sock->ht, &iter.iter, app, - sock_n.node) { - ret = lttng_ht_del(ust_app_ht_by_sock, &iter); - LTTNG_ASSERT(!ret); - } - } - - rcu_read_unlock(); - - /* Destroy is done only when the ht is empty */ - if (ust_app_ht) { - ht_cleanup_push(ust_app_ht); - } - if (ust_app_ht_by_sock) { - ht_cleanup_push(ust_app_ht_by_sock); - } - if (ust_app_ht_by_notify_sock) { - ht_cleanup_push(ust_app_ht_by_notify_sock); - } -} - -/* - * Init UST app hash table. - */ -int ust_app_ht_alloc(void) -{ - ust_app_ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); - if (!ust_app_ht) { - return -1; - } - ust_app_ht_by_sock = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); - if (!ust_app_ht_by_sock) { - return -1; - } - ust_app_ht_by_notify_sock = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); - if (!ust_app_ht_by_notify_sock) { - return -1; - } - return 0; -} - -/* - * For a specific UST session, disable the channel for all registered apps. - */ -int ust_app_disable_channel_glb(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct lttng_ht_node_str *ua_chan_node; - struct ust_app *app; - struct ust_app_session *ua_sess; - struct ust_app_channel *ua_chan; - - LTTNG_ASSERT(usess->active); - DBG2("UST app disabling channel %s from global domain for session id %" PRIu64, - uchan->name, usess->id); - - rcu_read_lock(); - - /* For every registered applications */ - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - struct lttng_ht_iter uiter; - if (!app->compatible) { - /* - * TODO: In time, we should notice the caller of this error by - * telling him that this is a version error. - */ - continue; - } - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - continue; - } - - /* Get channel */ - lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); - ua_chan_node = lttng_ht_iter_get_node_str(&uiter); - /* If the session if found for the app, the channel must be there */ - LTTNG_ASSERT(ua_chan_node); - - ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); - /* The channel must not be already disabled */ - LTTNG_ASSERT(ua_chan->enabled == 1); - - /* Disable channel onto application */ - ret = disable_ust_app_channel(ua_sess, ua_chan, app); - if (ret < 0) { - /* XXX: We might want to report this error at some point... */ - continue; - } - } - - rcu_read_unlock(); - return ret; -} - -/* - * For a specific UST session, enable the channel for all registered apps. - */ -int ust_app_enable_channel_glb(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct ust_app *app; - struct ust_app_session *ua_sess; - - LTTNG_ASSERT(usess->active); - DBG2("UST app enabling channel %s to global domain for session id %" PRIu64, - uchan->name, usess->id); - - rcu_read_lock(); - - /* For every registered applications */ - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - if (!app->compatible) { - /* - * TODO: In time, we should notice the caller of this error by - * telling him that this is a version error. - */ - continue; - } - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - continue; - } - - /* Enable channel onto application */ - ret = enable_ust_app_channel(ua_sess, uchan, app); - if (ret < 0) { - /* XXX: We might want to report this error at some point... */ - continue; - } - } - - rcu_read_unlock(); - return ret; -} - -/* - * Disable an event in a channel and for a specific session. - */ -int ust_app_disable_event_glb(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan, struct ltt_ust_event *uevent) -{ - int ret = 0; - struct lttng_ht_iter iter, uiter; - struct lttng_ht_node_str *ua_chan_node; - struct ust_app *app; - struct ust_app_session *ua_sess; - struct ust_app_channel *ua_chan; - struct ust_app_event *ua_event; - - LTTNG_ASSERT(usess->active); - DBG("UST app disabling event %s for all apps in channel " - "%s for session id %" PRIu64, - uevent->attr.name, uchan->name, usess->id); - - rcu_read_lock(); - - /* For all registered applications */ - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - if (!app->compatible) { - /* - * TODO: In time, we should notice the caller of this error by - * telling him that this is a version error. - */ - continue; - } - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - /* Next app */ - continue; - } - - /* Lookup channel in the ust app session */ - lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); - ua_chan_node = lttng_ht_iter_get_node_str(&uiter); - if (ua_chan_node == NULL) { - DBG2("Channel %s not found in session id %" PRIu64 " for app pid %d." - "Skipping", uchan->name, usess->id, app->pid); - continue; - } - ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); - - ua_event = find_ust_app_event(ua_chan->events, uevent->attr.name, - uevent->filter, uevent->attr.loglevel, - uevent->exclusion); - if (ua_event == NULL) { - DBG2("Event %s not found in channel %s for app pid %d." - "Skipping", uevent->attr.name, uchan->name, app->pid); - continue; - } - - ret = disable_ust_app_event(ua_sess, ua_event, app); - if (ret < 0) { - /* XXX: Report error someday... */ - continue; - } - } - - rcu_read_unlock(); - return ret; -} - -/* The ua_sess lock must be held by the caller. */ -static -int ust_app_channel_create(struct ltt_ust_session *usess, - struct ust_app_session *ua_sess, - struct ltt_ust_channel *uchan, struct ust_app *app, - struct ust_app_channel **_ua_chan) -{ - int ret = 0; - struct ust_app_channel *ua_chan = NULL; - - LTTNG_ASSERT(ua_sess); - ASSERT_LOCKED(ua_sess->lock); - - if (!strncmp(uchan->name, DEFAULT_METADATA_NAME, - sizeof(uchan->name))) { - copy_channel_attr_to_ustctl(&ua_sess->metadata_attr, - &uchan->attr); - ret = 0; - } else { - struct ltt_ust_context *uctx = NULL; - - /* - * Create channel onto application and synchronize its - * configuration. - */ - ret = ust_app_channel_allocate(ua_sess, uchan, - LTTNG_UST_ABI_CHAN_PER_CPU, usess, - &ua_chan); - if (ret < 0) { - goto error; - } - - ret = ust_app_channel_send(app, usess, - ua_sess, ua_chan); - if (ret) { - goto error; - } - - /* Add contexts. */ - cds_list_for_each_entry(uctx, &uchan->ctx_list, list) { - ret = create_ust_app_channel_context(ua_chan, - &uctx->ctx, app); - if (ret) { - goto error; - } - } - } - -error: - if (ret < 0) { - switch (ret) { - case -ENOTCONN: - /* - * The application's socket is not valid. Either a bad socket - * or a timeout on it. We can't inform the caller that for a - * specific app, the session failed so lets continue here. - */ - ret = 0; /* Not an error. */ - break; - case -ENOMEM: - default: - break; - } - } - - if (ret == 0 && _ua_chan) { - /* - * Only return the application's channel on success. Note - * that the channel can still be part of the application's - * channel hashtable on error. - */ - *_ua_chan = ua_chan; - } - return ret; -} - -/* - * Enable event for a specific session and channel on the tracer. - */ -int ust_app_enable_event_glb(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan, struct ltt_ust_event *uevent) -{ - int ret = 0; - struct lttng_ht_iter iter, uiter; - struct lttng_ht_node_str *ua_chan_node; - struct ust_app *app; - struct ust_app_session *ua_sess; - struct ust_app_channel *ua_chan; - struct ust_app_event *ua_event; - - LTTNG_ASSERT(usess->active); - DBG("UST app enabling event %s for all apps for session id %" PRIu64, - uevent->attr.name, usess->id); - - /* - * NOTE: At this point, this function is called only if the session and - * channel passed are already created for all apps. and enabled on the - * tracer also. - */ - - rcu_read_lock(); - - /* For all registered applications */ - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - if (!app->compatible) { - /* - * TODO: In time, we should notice the caller of this error by - * telling him that this is a version error. - */ - continue; - } - ua_sess = lookup_session_by_app(usess, app); - if (!ua_sess) { - /* The application has problem or is probably dead. */ - continue; - } - - pthread_mutex_lock(&ua_sess->lock); - - if (ua_sess->deleted) { - pthread_mutex_unlock(&ua_sess->lock); - continue; - } - - /* Lookup channel in the ust app session */ - lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); - ua_chan_node = lttng_ht_iter_get_node_str(&uiter); - /* - * It is possible that the channel cannot be found is - * the channel/event creation occurs concurrently with - * an application exit. - */ - if (!ua_chan_node) { - pthread_mutex_unlock(&ua_sess->lock); - continue; - } - - ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); - - /* Get event node */ - ua_event = find_ust_app_event(ua_chan->events, uevent->attr.name, - uevent->filter, uevent->attr.loglevel, uevent->exclusion); - if (ua_event == NULL) { - DBG3("UST app enable event %s not found for app PID %d." - "Skipping app", uevent->attr.name, app->pid); - goto next_app; - } - - ret = enable_ust_app_event(ua_sess, ua_event, app); - if (ret < 0) { - pthread_mutex_unlock(&ua_sess->lock); - goto error; - } - next_app: - pthread_mutex_unlock(&ua_sess->lock); - } - -error: - rcu_read_unlock(); - return ret; -} - -/* - * For a specific existing UST session and UST channel, creates the event for - * all registered apps. - */ -int ust_app_create_event_glb(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan, struct ltt_ust_event *uevent) -{ - int ret = 0; - struct lttng_ht_iter iter, uiter; - struct lttng_ht_node_str *ua_chan_node; - struct ust_app *app; - struct ust_app_session *ua_sess; - struct ust_app_channel *ua_chan; - - LTTNG_ASSERT(usess->active); - DBG("UST app creating event %s for all apps for session id %" PRIu64, - uevent->attr.name, usess->id); - - rcu_read_lock(); - - /* For all registered applications */ - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - if (!app->compatible) { - /* - * TODO: In time, we should notice the caller of this error by - * telling him that this is a version error. - */ - continue; - } - ua_sess = lookup_session_by_app(usess, app); - if (!ua_sess) { - /* The application has problem or is probably dead. */ - continue; - } - - pthread_mutex_lock(&ua_sess->lock); - - if (ua_sess->deleted) { - pthread_mutex_unlock(&ua_sess->lock); - continue; - } - - /* Lookup channel in the ust app session */ - lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); - ua_chan_node = lttng_ht_iter_get_node_str(&uiter); - /* If the channel is not found, there is a code flow error */ - LTTNG_ASSERT(ua_chan_node); - - ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); - - ret = create_ust_app_event(ua_sess, ua_chan, uevent, app); - pthread_mutex_unlock(&ua_sess->lock); - if (ret < 0) { - if (ret != -LTTNG_UST_ERR_EXIST) { - /* Possible value at this point: -ENOMEM. If so, we stop! */ - break; - } - DBG2("UST app event %s already exist on app PID %d", - uevent->attr.name, app->pid); - continue; - } - } - - rcu_read_unlock(); - return ret; -} - -/* - * Start tracing for a specific UST session and app. - * - * Called with UST app session lock held. - * - */ -static -int ust_app_start_trace(struct ltt_ust_session *usess, struct ust_app *app) -{ - int ret = 0; - struct ust_app_session *ua_sess; - - DBG("Starting tracing for ust app pid %d", app->pid); - - rcu_read_lock(); - - if (!app->compatible) { - goto end; - } - - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - /* The session is in teardown process. Ignore and continue. */ - goto end; - } - - pthread_mutex_lock(&ua_sess->lock); - - if (ua_sess->deleted) { - pthread_mutex_unlock(&ua_sess->lock); - goto end; - } - - if (ua_sess->enabled) { - pthread_mutex_unlock(&ua_sess->lock); - goto end; - } - - /* Upon restart, we skip the setup, already done */ - if (ua_sess->started) { - goto skip_setup; - } - - health_code_update(); - -skip_setup: - /* This starts the UST tracing */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_start_session(app->sock, ua_sess->handle); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app start session failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - pthread_mutex_unlock(&ua_sess->lock); - goto end; - } else if (ret == -EAGAIN) { - WARN("UST app start session failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - pthread_mutex_unlock(&ua_sess->lock); - goto end; - - } else { - ERR("UST app start session failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - goto error_unlock; - } - - /* Indicate that the session has been started once */ - ua_sess->started = 1; - ua_sess->enabled = 1; - - pthread_mutex_unlock(&ua_sess->lock); - - health_code_update(); - - /* Quiescent wait after starting trace */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_wait_quiescent(app->sock); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app wait quiescent failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app wait quiescent failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app wait quiescent failed with ret %d: pid %d, sock = %d", - ret, app->pid, app->sock); - } - } - -end: - rcu_read_unlock(); - health_code_update(); - return 0; - -error_unlock: - pthread_mutex_unlock(&ua_sess->lock); - rcu_read_unlock(); - health_code_update(); - return -1; -} - -/* - * Stop tracing for a specific UST session and app. - */ -static -int ust_app_stop_trace(struct ltt_ust_session *usess, struct ust_app *app) -{ - int ret = 0; - struct ust_app_session *ua_sess; - struct ust_registry_session *registry; - - DBG("Stopping tracing for ust app pid %d", app->pid); - - rcu_read_lock(); - - if (!app->compatible) { - goto end_no_session; - } - - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - goto end_no_session; - } - - pthread_mutex_lock(&ua_sess->lock); - - if (ua_sess->deleted) { - pthread_mutex_unlock(&ua_sess->lock); - goto end_no_session; - } - - /* - * If started = 0, it means that stop trace has been called for a session - * that was never started. It's possible since we can have a fail start - * from either the application manager thread or the command thread. Simply - * indicate that this is a stop error. - */ - if (!ua_sess->started) { - goto error_rcu_unlock; - } - - health_code_update(); - - /* This inhibits UST tracing */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_stop_session(app->sock, ua_sess->handle); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app stop session failed. Application is dead: pid = %d, sock = %d", - app->pid, app->sock); - goto end_unlock; - } else if (ret == -EAGAIN) { - WARN("UST app stop session failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - goto end_unlock; - - } else { - ERR("UST app stop session failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - goto error_rcu_unlock; - } - - health_code_update(); - ua_sess->enabled = 0; - - /* Quiescent wait after stopping trace */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_wait_quiescent(app->sock); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app wait quiescent failed. Application is dead: pid= %d, sock = %d)", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app wait quiescent failed. Communication time out: pid= %d, sock = %d)", - app->pid, app->sock); - } else { - ERR("UST app wait quiescent failed with ret %d: pid= %d, sock = %d)", - ret, app->pid, app->sock); - } - } - - health_code_update(); - - registry = get_session_registry(ua_sess); - - /* The UST app session is held registry shall not be null. */ - LTTNG_ASSERT(registry); - - /* Push metadata for application before freeing the application. */ - (void) push_metadata(registry, ua_sess->consumer); - -end_unlock: - pthread_mutex_unlock(&ua_sess->lock); -end_no_session: - rcu_read_unlock(); - health_code_update(); - return 0; - -error_rcu_unlock: - pthread_mutex_unlock(&ua_sess->lock); - rcu_read_unlock(); - health_code_update(); - return -1; -} - -static -int ust_app_flush_app_session(struct ust_app *app, - struct ust_app_session *ua_sess) -{ - int ret, retval = 0; - struct lttng_ht_iter iter; - struct ust_app_channel *ua_chan; - struct consumer_socket *socket; - - DBG("Flushing app session buffers for ust app pid %d", app->pid); - - rcu_read_lock(); - - if (!app->compatible) { - goto end_not_compatible; - } - - pthread_mutex_lock(&ua_sess->lock); - - if (ua_sess->deleted) { - goto end_deleted; - } - - health_code_update(); - - /* Flushing buffers */ - socket = consumer_find_socket_by_bitness(app->bits_per_long, - ua_sess->consumer); - - /* Flush buffers and push metadata. */ - switch (ua_sess->buffer_type) { - case LTTNG_BUFFER_PER_PID: - cds_lfht_for_each_entry(ua_sess->channels->ht, &iter.iter, ua_chan, - node.node) { - health_code_update(); - ret = consumer_flush_channel(socket, ua_chan->key); - if (ret) { - ERR("Error flushing consumer channel"); - retval = -1; - continue; - } - } - break; - case LTTNG_BUFFER_PER_UID: - default: - abort(); - break; - } - - health_code_update(); - -end_deleted: - pthread_mutex_unlock(&ua_sess->lock); - -end_not_compatible: - rcu_read_unlock(); - health_code_update(); - return retval; -} - -/* - * Flush buffers for all applications for a specific UST session. - * Called with UST session lock held. - */ -static -int ust_app_flush_session(struct ltt_ust_session *usess) - -{ - int ret = 0; - - DBG("Flushing session buffers for all ust apps"); - - rcu_read_lock(); - - /* Flush buffers and push metadata. */ - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_UID: - { - struct buffer_reg_uid *reg; - struct lttng_ht_iter iter; - - /* Flush all per UID buffers associated to that session. */ - cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { - struct ust_registry_session *ust_session_reg; - struct buffer_reg_channel *buf_reg_chan; - struct consumer_socket *socket; - - /* Get consumer socket to use to push the metadata.*/ - socket = consumer_find_socket_by_bitness(reg->bits_per_long, - usess->consumer); - if (!socket) { - /* Ignore request if no consumer is found for the session. */ - continue; - } - - cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, - buf_reg_chan, node.node) { - /* - * The following call will print error values so the return - * code is of little importance because whatever happens, we - * have to try them all. - */ - (void) consumer_flush_channel(socket, buf_reg_chan->consumer_key); - } - - ust_session_reg = reg->registry->reg.ust; - /* Push metadata. */ - (void) push_metadata(ust_session_reg, usess->consumer); - } - break; - } - case LTTNG_BUFFER_PER_PID: - { - struct ust_app_session *ua_sess; - struct lttng_ht_iter iter; - struct ust_app *app; - - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - continue; - } - (void) ust_app_flush_app_session(app, ua_sess); - } - break; - } - default: - ret = -1; - abort(); - break; - } - - rcu_read_unlock(); - health_code_update(); - return ret; -} - -static -int ust_app_clear_quiescent_app_session(struct ust_app *app, - struct ust_app_session *ua_sess) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct ust_app_channel *ua_chan; - struct consumer_socket *socket; - - DBG("Clearing stream quiescent state for ust app pid %d", app->pid); - - rcu_read_lock(); - - if (!app->compatible) { - goto end_not_compatible; - } - - pthread_mutex_lock(&ua_sess->lock); - - if (ua_sess->deleted) { - goto end_unlock; - } - - health_code_update(); - - socket = consumer_find_socket_by_bitness(app->bits_per_long, - ua_sess->consumer); - if (!socket) { - ERR("Failed to find consumer (%" PRIu32 ") socket", - app->bits_per_long); - ret = -1; - goto end_unlock; - } - - /* Clear quiescent state. */ - switch (ua_sess->buffer_type) { - case LTTNG_BUFFER_PER_PID: - cds_lfht_for_each_entry(ua_sess->channels->ht, &iter.iter, - ua_chan, node.node) { - health_code_update(); - ret = consumer_clear_quiescent_channel(socket, - ua_chan->key); - if (ret) { - ERR("Error clearing quiescent state for consumer channel"); - ret = -1; - continue; - } - } - break; - case LTTNG_BUFFER_PER_UID: - default: - abort(); - ret = -1; - break; - } - - health_code_update(); - -end_unlock: - pthread_mutex_unlock(&ua_sess->lock); - -end_not_compatible: - rcu_read_unlock(); - health_code_update(); - return ret; -} - -/* - * Clear quiescent state in each stream for all applications for a - * specific UST session. - * Called with UST session lock held. - */ -static -int ust_app_clear_quiescent_session(struct ltt_ust_session *usess) - -{ - int ret = 0; - - DBG("Clearing stream quiescent state for all ust apps"); - - rcu_read_lock(); - - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_UID: - { - struct lttng_ht_iter iter; - struct buffer_reg_uid *reg; - - /* - * Clear quiescent for all per UID buffers associated to - * that session. - */ - cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { - struct consumer_socket *socket; - struct buffer_reg_channel *buf_reg_chan; - - /* Get associated consumer socket.*/ - socket = consumer_find_socket_by_bitness( - reg->bits_per_long, usess->consumer); - if (!socket) { - /* - * Ignore request if no consumer is found for - * the session. - */ - continue; - } - - cds_lfht_for_each_entry(reg->registry->channels->ht, - &iter.iter, buf_reg_chan, node.node) { - /* - * The following call will print error values so - * the return code is of little importance - * because whatever happens, we have to try them - * all. - */ - (void) consumer_clear_quiescent_channel(socket, - buf_reg_chan->consumer_key); - } - } - break; - } - case LTTNG_BUFFER_PER_PID: - { - struct ust_app_session *ua_sess; - struct lttng_ht_iter iter; - struct ust_app *app; - - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, - pid_n.node) { - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - continue; - } - (void) ust_app_clear_quiescent_app_session(app, - ua_sess); - } - break; - } - default: - ret = -1; - abort(); - break; - } - - rcu_read_unlock(); - health_code_update(); - return ret; -} - -/* - * Destroy a specific UST session in apps. - */ -static int destroy_trace(struct ltt_ust_session *usess, struct ust_app *app) -{ - int ret; - struct ust_app_session *ua_sess; - struct lttng_ht_iter iter; - struct lttng_ht_node_u64 *node; - - DBG("Destroy tracing for ust app pid %d", app->pid); - - rcu_read_lock(); - - if (!app->compatible) { - goto end; - } - - __lookup_session_by_app(usess, app, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (node == NULL) { - /* Session is being or is deleted. */ - goto end; - } - ua_sess = caa_container_of(node, struct ust_app_session, node); - - health_code_update(); - destroy_app_session(app, ua_sess); - - health_code_update(); - - /* Quiescent wait after stopping trace */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_wait_quiescent(app->sock); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app wait quiescent failed. Application is dead: pid= %d, sock = %d)", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app wait quiescent failed. Communication time out: pid= %d, sock = %d)", - app->pid, app->sock); - } else { - ERR("UST app wait quiescent failed with ret %d: pid= %d, sock = %d)", - ret, app->pid, app->sock); - } - } -end: - rcu_read_unlock(); - health_code_update(); - return 0; -} - -/* - * Start tracing for the UST session. - */ -int ust_app_start_trace_all(struct ltt_ust_session *usess) -{ - struct lttng_ht_iter iter; - struct ust_app *app; - - DBG("Starting all UST traces"); - - /* - * Even though the start trace might fail, flag this session active so - * other application coming in are started by default. - */ - usess->active = 1; - - rcu_read_lock(); - - /* - * In a start-stop-start use-case, we need to clear the quiescent state - * of each channel set by the prior stop command, thus ensuring that a - * following stop or destroy is sure to grab a timestamp_end near those - * operations, even if the packet is empty. - */ - (void) ust_app_clear_quiescent_session(usess); - - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - ust_app_global_update(usess, app); - } - - rcu_read_unlock(); - - return 0; -} - -/* - * Start tracing for the UST session. - * Called with UST session lock held. - */ -int ust_app_stop_trace_all(struct ltt_ust_session *usess) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct ust_app *app; - - DBG("Stopping all UST traces"); - - /* - * Even though the stop trace might fail, flag this session inactive so - * other application coming in are not started by default. - */ - usess->active = 0; - - rcu_read_lock(); - - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - ret = ust_app_stop_trace(usess, app); - if (ret < 0) { - /* Continue to next apps even on error */ - continue; - } - } - - (void) ust_app_flush_session(usess); - - rcu_read_unlock(); - - return 0; -} - -/* - * Destroy app UST session. - */ -int ust_app_destroy_trace_all(struct ltt_ust_session *usess) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct ust_app *app; - - DBG("Destroy all UST traces"); - - rcu_read_lock(); - - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - ret = destroy_trace(usess, app); - if (ret < 0) { - /* Continue to next apps even on error */ - continue; - } - } - - rcu_read_unlock(); - - return 0; -} - -/* The ua_sess lock must be held by the caller. */ -static -int find_or_create_ust_app_channel( - struct ltt_ust_session *usess, - struct ust_app_session *ua_sess, - struct ust_app *app, - struct ltt_ust_channel *uchan, - struct ust_app_channel **ua_chan) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct lttng_ht_node_str *ua_chan_node; - - lttng_ht_lookup(ua_sess->channels, (void *) uchan->name, &iter); - ua_chan_node = lttng_ht_iter_get_node_str(&iter); - if (ua_chan_node) { - *ua_chan = caa_container_of(ua_chan_node, - struct ust_app_channel, node); - goto end; - } - - ret = ust_app_channel_create(usess, ua_sess, uchan, app, ua_chan); - if (ret) { - goto end; - } -end: - return ret; -} - -static -int ust_app_channel_synchronize_event(struct ust_app_channel *ua_chan, - struct ltt_ust_event *uevent, struct ust_app_session *ua_sess, - struct ust_app *app) -{ - int ret = 0; - struct ust_app_event *ua_event = NULL; - - ua_event = find_ust_app_event(ua_chan->events, uevent->attr.name, - uevent->filter, uevent->attr.loglevel, uevent->exclusion); - if (!ua_event) { - ret = create_ust_app_event(ua_sess, ua_chan, uevent, app); - if (ret < 0) { - goto end; - } - } else { - if (ua_event->enabled != uevent->enabled) { - ret = uevent->enabled ? - enable_ust_app_event(ua_sess, ua_event, app) : - disable_ust_app_event(ua_sess, ua_event, app); - } - } - -end: - return ret; -} - -/* Called with RCU read-side lock held. */ -static -void ust_app_synchronize_event_notifier_rules(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 = NULL; - struct ust_app_event_notifier_rule *event_notifier_rule; - unsigned int count, i; - - if (!ust_app_supports_notifiers(app)) { - goto end; - } - - /* - * Currrently, registering or unregistering a trigger with an - * event rule condition causes a full synchronization of the event - * notifiers. - * - * The first step attempts to add an event notifier for all registered - * triggers that apply to the user space tracers. Then, the - * application's event notifiers rules are all checked against the list - * of registered triggers. Any event notifier that doesn't have a - * matching trigger can be assumed to have been disabled. - * - * All of this is inefficient, but is put in place to get the feature - * rolling as it is simpler at this moment. It will be optimized Soon™ - * to allow the state of enabled - * event notifiers to be synchronized in a piece-wise way. - */ - - /* Get all triggers using uid 0 (root) */ - ret_code = notification_thread_command_list_triggers( - the_notification_thread_handle, 0, &triggers); - if (ret_code != LTTNG_OK) { - goto end; - } - - LTTNG_ASSERT(triggers); - - t_status = lttng_triggers_get_count(triggers, &count); - if (t_status != LTTNG_TRIGGER_STATUS_OK) { - goto end; - } - - for (i = 0; i < count; i++) { - struct lttng_condition *condition; - struct lttng_event_rule *event_rule; - struct lttng_trigger *trigger; - const struct ust_app_event_notifier_rule *looked_up_event_notifier_rule; - enum lttng_condition_status condition_status; - uint64_t token; - - trigger = lttng_triggers_borrow_mutable_at_index(triggers, i); - LTTNG_ASSERT(trigger); - - token = lttng_trigger_get_tracer_token(trigger); - condition = lttng_trigger_get_condition(trigger); - - if (lttng_condition_get_type(condition) != - LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES) { - /* Does not apply */ - continue; - } - - condition_status = - lttng_condition_event_rule_matches_borrow_rule_mutable( - condition, &event_rule); - LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); - - if (lttng_event_rule_get_domain_type(event_rule) == LTTNG_DOMAIN_KERNEL) { - /* Skip kernel related triggers. */ - continue; - } - - /* - * Find or create the associated token event rule. The caller - * holds the RCU read lock, so this is safe to call without - * explicitly acquiring it here. - */ - looked_up_event_notifier_rule = find_ust_app_event_notifier_rule( - app->token_to_event_notifier_rule_ht, token); - if (!looked_up_event_notifier_rule) { - ret = create_ust_app_event_notifier_rule(trigger, app); - if (ret < 0) { - goto end; - } - } - } - - rcu_read_lock(); - /* Remove all unknown event sources from the app. */ - cds_lfht_for_each_entry (app->token_to_event_notifier_rule_ht->ht, - &app_trigger_iter.iter, event_notifier_rule, - node.node) { - const uint64_t app_token = event_notifier_rule->token; - bool found = false; - - /* - * Check if the app event trigger still exists on the - * notification side. - */ - for (i = 0; i < count; i++) { - uint64_t notification_thread_token; - const struct lttng_trigger *trigger = - lttng_triggers_get_at_index( - triggers, i); - - LTTNG_ASSERT(trigger); - - notification_thread_token = - lttng_trigger_get_tracer_token(trigger); - - if (notification_thread_token == app_token) { - found = true; - break; - } - } - - if (found) { - /* Still valid. */ - continue; - } - - /* - * This trigger was unregistered, disable it on the tracer's - * side. - */ - ret = lttng_ht_del(app->token_to_event_notifier_rule_ht, - &app_trigger_iter); - LTTNG_ASSERT(ret == 0); - - /* Callee logs errors. */ - (void) disable_ust_object(app, event_notifier_rule->obj); - - delete_ust_app_event_notifier_rule( - app->sock, event_notifier_rule, app); - } - - rcu_read_unlock(); - -end: - lttng_triggers_destroy(triggers); - return; -} - -/* - * RCU read lock must be held by the caller. - */ -static -void ust_app_synchronize_all_channels(struct ltt_ust_session *usess, - struct ust_app_session *ua_sess, - struct ust_app *app) -{ - int ret = 0; - struct cds_lfht_iter uchan_iter; - struct ltt_ust_channel *uchan; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(app); - - cds_lfht_for_each_entry(usess->domain_global.channels->ht, &uchan_iter, - uchan, node.node) { - struct ust_app_channel *ua_chan; - struct cds_lfht_iter uevent_iter; - struct ltt_ust_event *uevent; - - /* - * Search for a matching ust_app_channel. If none is found, - * create it. Creating the channel will cause the ua_chan - * structure to be allocated, the channel buffers to be - * allocated (if necessary) and sent to the application, and - * all enabled contexts will be added to the channel. - */ - ret = find_or_create_ust_app_channel(usess, ua_sess, - app, uchan, &ua_chan); - if (ret) { - /* Tracer is probably gone or ENOMEM. */ - goto end; - } - - if (!ua_chan) { - /* ua_chan will be NULL for the metadata channel */ - continue; - } - - cds_lfht_for_each_entry(uchan->events->ht, &uevent_iter, uevent, - node.node) { - ret = ust_app_channel_synchronize_event(ua_chan, - uevent, ua_sess, app); - if (ret) { - goto end; - } - } - - if (ua_chan->enabled != uchan->enabled) { - ret = uchan->enabled ? - enable_ust_app_channel(ua_sess, uchan, app) : - disable_ust_app_channel(ua_sess, ua_chan, app); - if (ret) { - goto end; - } - } - } -end: - return; -} - -/* - * The caller must ensure that the application is compatible and is tracked - * by the process attribute trackers. - */ -static -void ust_app_synchronize(struct ltt_ust_session *usess, - struct ust_app *app) -{ - int ret = 0; - struct ust_app_session *ua_sess = NULL; - - /* - * The application's configuration should only be synchronized for - * active sessions. - */ - LTTNG_ASSERT(usess->active); - - ret = find_or_create_ust_app_session(usess, app, &ua_sess, NULL); - if (ret < 0) { - /* Tracer is probably gone or ENOMEM. */ - if (ua_sess) { - destroy_app_session(app, ua_sess); - } - goto end; - } - LTTNG_ASSERT(ua_sess); - - pthread_mutex_lock(&ua_sess->lock); - if (ua_sess->deleted) { - goto deleted_session; - } - - rcu_read_lock(); - - ust_app_synchronize_all_channels(usess, ua_sess, app); - - /* - * Create the metadata for the application. This returns gracefully if a - * metadata was already set for the session. - * - * The metadata channel must be created after the data channels as the - * consumer daemon assumes this ordering. When interacting with a relay - * daemon, the consumer will use this assumption to send the - * "STREAMS_SENT" message to the relay daemon. - */ - ret = create_ust_app_metadata(ua_sess, app, usess->consumer); - if (ret < 0) { - ERR("Metadata creation failed for app sock %d for session id %" PRIu64, - app->sock, usess->id); - } - - rcu_read_unlock(); - -deleted_session: - pthread_mutex_unlock(&ua_sess->lock); -end: - return; -} - -static -void ust_app_global_destroy(struct ltt_ust_session *usess, struct ust_app *app) -{ - struct ust_app_session *ua_sess; - - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - return; - } - destroy_app_session(app, ua_sess); -} - -/* - * Add channels/events from UST global domain to registered apps at sock. - * - * Called with session lock held. - * Called with RCU read-side lock held. - */ -void ust_app_global_update(struct ltt_ust_session *usess, struct ust_app *app) -{ - LTTNG_ASSERT(usess); - LTTNG_ASSERT(usess->active); - - DBG2("UST app global update for app sock %d for session id %" PRIu64, - app->sock, usess->id); - - if (!app->compatible) { - return; - } - if (trace_ust_id_tracker_lookup(LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID, - usess, app->pid) && - trace_ust_id_tracker_lookup( - LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID, - usess, app->uid) && - trace_ust_id_tracker_lookup( - LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID, - usess, app->gid)) { - /* - * Synchronize the application's internal tracing configuration - * and start tracing. - */ - ust_app_synchronize(usess, app); - ust_app_start_trace(usess, app); - } else { - ust_app_global_destroy(usess, app); - } -} - -/* - * Add all event notifiers to an application. - * - * Called with session lock held. - * Called with RCU read-side lock held. - */ -void ust_app_global_update_event_notifier_rules(struct ust_app *app) -{ - DBG2("UST application global event notifier rules update: app = '%s', pid = %d)", - app->name, app->pid); - - if (!app->compatible || !ust_app_supports_notifiers(app)) { - return; - } - - if (app->event_notifier_group.object == NULL) { - WARN("UST app global update of event notifiers for app skipped since communication handle is null: app = '%s' pid = %d)", - app->name, app->pid); - return; - } - - ust_app_synchronize_event_notifier_rules(app); -} - -/* - * Called with session lock held. - */ -void ust_app_global_update_all(struct ltt_ust_session *usess) -{ - 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(usess, app); - } - rcu_read_unlock(); -} - -void ust_app_global_update_all_event_notifier_rules(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_event_notifier_rules(app); - } - - rcu_read_unlock(); -} - -/* - * Add context to a specific channel for global UST domain. - */ -int ust_app_add_ctx_channel_glb(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan, struct ltt_ust_context *uctx) -{ - int ret = 0; - struct lttng_ht_node_str *ua_chan_node; - struct lttng_ht_iter iter, uiter; - struct ust_app_channel *ua_chan = NULL; - struct ust_app_session *ua_sess; - struct ust_app *app; - - LTTNG_ASSERT(usess->active); - - rcu_read_lock(); - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - if (!app->compatible) { - /* - * TODO: In time, we should notice the caller of this error by - * telling him that this is a version error. - */ - continue; - } - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - continue; - } - - pthread_mutex_lock(&ua_sess->lock); - - if (ua_sess->deleted) { - pthread_mutex_unlock(&ua_sess->lock); - continue; - } - - /* Lookup channel in the ust app session */ - lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); - ua_chan_node = lttng_ht_iter_get_node_str(&uiter); - if (ua_chan_node == NULL) { - goto next_app; - } - ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, - node); - ret = create_ust_app_channel_context(ua_chan, &uctx->ctx, app); - if (ret < 0) { - goto next_app; - } - next_app: - pthread_mutex_unlock(&ua_sess->lock); - } - - rcu_read_unlock(); - return ret; -} - -/* - * Receive registration and populate the given msg structure. - * - * On success return 0 else a negative value returned by the ustctl call. - */ -int ust_app_recv_registration(int sock, struct ust_register_msg *msg) -{ - int ret; - uint32_t pid, ppid, uid, gid; - - LTTNG_ASSERT(msg); - - ret = lttng_ust_ctl_recv_reg_msg(sock, &msg->type, &msg->major, &msg->minor, - &pid, &ppid, &uid, &gid, - &msg->bits_per_long, - &msg->uint8_t_alignment, - &msg->uint16_t_alignment, - &msg->uint32_t_alignment, - &msg->uint64_t_alignment, - &msg->long_alignment, - &msg->byte_order, - msg->name); - if (ret < 0) { - switch (-ret) { - case EPIPE: - case ECONNRESET: - case LTTNG_UST_ERR_EXITING: - DBG3("UST app recv reg message failed. Application died"); - break; - case LTTNG_UST_ERR_UNSUP_MAJOR: - ERR("UST app recv reg unsupported version %d.%d. Supporting %d.%d", - msg->major, msg->minor, LTTNG_UST_ABI_MAJOR_VERSION, - LTTNG_UST_ABI_MINOR_VERSION); - break; - default: - ERR("UST app recv reg message failed with ret %d", ret); - break; - } - goto error; - } - msg->pid = (pid_t) pid; - msg->ppid = (pid_t) ppid; - msg->uid = (uid_t) uid; - msg->gid = (gid_t) gid; - -error: - return ret; -} - -/* - * Return a ust app session object using the application object and the - * session object descriptor has a key. If not found, NULL is returned. - * A RCU read side lock MUST be acquired when calling this function. -*/ -static struct ust_app_session *find_session_by_objd(struct ust_app *app, - int objd) -{ - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter iter; - struct ust_app_session *ua_sess = NULL; - - LTTNG_ASSERT(app); - - lttng_ht_lookup(app->ust_sessions_objd, (void *)((unsigned long) objd), &iter); - node = lttng_ht_iter_get_node_ulong(&iter); - if (node == NULL) { - DBG2("UST app session find by objd %d not found", objd); - goto error; - } - - ua_sess = caa_container_of(node, struct ust_app_session, ust_objd_node); - -error: - return ua_sess; -} - -/* - * Return a ust app channel object using the application object and the channel - * object descriptor has a key. If not found, NULL is returned. A RCU read side - * lock MUST be acquired before calling this function. - */ -static struct ust_app_channel *find_channel_by_objd(struct ust_app *app, - int objd) -{ - struct lttng_ht_node_ulong *node; - struct lttng_ht_iter iter; - struct ust_app_channel *ua_chan = NULL; - - LTTNG_ASSERT(app); - - lttng_ht_lookup(app->ust_objd, (void *)((unsigned long) objd), &iter); - node = lttng_ht_iter_get_node_ulong(&iter); - if (node == NULL) { - DBG2("UST app channel find by objd %d not found", objd); - goto error; - } - - ua_chan = caa_container_of(node, struct ust_app_channel, ust_objd_node); - -error: - return ua_chan; -} - -/* - * Reply to a register channel notification from an application on the notify - * socket. The channel metadata is also created. - * - * The session UST registry lock is acquired in this function. - * - * On success 0 is returned else a negative value. - */ -static int reply_ust_register_channel(int sock, int cobjd, - size_t nr_fields, struct lttng_ust_ctl_field *fields) -{ - int ret, ret_code = 0; - uint32_t chan_id; - uint64_t chan_reg_key; - enum lttng_ust_ctl_channel_header type; - struct ust_app *app; - struct ust_app_channel *ua_chan; - struct ust_app_session *ua_sess; - struct ust_registry_session *registry; - struct ust_registry_channel *ust_reg_chan; - - rcu_read_lock(); - - /* Lookup application. If not found, there is a code flow error. */ - app = find_app_by_notify_sock(sock); - if (!app) { - DBG("Application socket %d is being torn down. Abort event notify", - sock); - ret = -1; - goto error_rcu_unlock; - } - - /* Lookup channel by UST object descriptor. */ - ua_chan = find_channel_by_objd(app, cobjd); - if (!ua_chan) { - DBG("Application channel is being torn down. Abort event notify"); - ret = 0; - goto error_rcu_unlock; - } - - LTTNG_ASSERT(ua_chan->session); - ua_sess = ua_chan->session; - - /* Get right session registry depending on the session buffer type. */ - registry = get_session_registry(ua_sess); - if (!registry) { - DBG("Application session is being torn down. Abort event notify"); - ret = 0; - goto error_rcu_unlock; - }; - - /* Depending on the buffer type, a different channel key is used. */ - if (ua_sess->buffer_type == LTTNG_BUFFER_PER_UID) { - chan_reg_key = ua_chan->tracing_channel_id; - } else { - chan_reg_key = ua_chan->key; - } - - pthread_mutex_lock(®istry->lock); - - ust_reg_chan = ust_registry_channel_find(registry, chan_reg_key); - LTTNG_ASSERT(ust_reg_chan); - - if (!ust_reg_chan->register_done) { - /* - * TODO: eventually use the registry event count for - * this channel to better guess header type for per-pid - * buffers. - */ - type = LTTNG_UST_CTL_CHANNEL_HEADER_LARGE; - ust_reg_chan->nr_ctx_fields = nr_fields; - ust_reg_chan->ctx_fields = fields; - fields = NULL; - ust_reg_chan->header_type = type; - } else { - /* Get current already assigned values. */ - type = ust_reg_chan->header_type; - } - /* Channel id is set during the object creation. */ - chan_id = ust_reg_chan->chan_id; - - /* Append to metadata */ - if (!ust_reg_chan->metadata_dumped) { - ret_code = ust_metadata_channel_statedump(registry, ust_reg_chan); - if (ret_code) { - ERR("Error appending channel metadata (errno = %d)", ret_code); - goto reply; - } - } - -reply: - DBG3("UST app replying to register channel key %" PRIu64 - " with id %u, type = %d, ret = %d", chan_reg_key, chan_id, type, - ret_code); - - ret = lttng_ust_ctl_reply_register_channel(sock, chan_id, type, ret_code); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app reply channel failed. Application died: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app reply channel failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app reply channel failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - goto error; - } - - /* This channel registry registration is completed. */ - ust_reg_chan->register_done = 1; - -error: - pthread_mutex_unlock(®istry->lock); -error_rcu_unlock: - rcu_read_unlock(); - free(fields); - return ret; -} - -/* - * Add event to the UST channel registry. When the event is added to the - * registry, the metadata is also created. Once done, this replies to the - * application with the appropriate error code. - * - * The session UST registry lock is acquired in the function. - * - * On success 0 is returned else a negative value. - */ -static int add_event_ust_registry(int sock, int sobjd, int cobjd, char *name, - char *sig, size_t nr_fields, struct lttng_ust_ctl_field *fields, - int loglevel_value, char *model_emf_uri) -{ - int ret, ret_code; - uint32_t event_id = 0; - uint64_t chan_reg_key; - struct ust_app *app; - struct ust_app_channel *ua_chan; - struct ust_app_session *ua_sess; - struct ust_registry_session *registry; - - rcu_read_lock(); - - /* Lookup application. If not found, there is a code flow error. */ - app = find_app_by_notify_sock(sock); - if (!app) { - DBG("Application socket %d is being torn down. Abort event notify", - sock); - ret = -1; - goto error_rcu_unlock; - } - - /* Lookup channel by UST object descriptor. */ - ua_chan = find_channel_by_objd(app, cobjd); - if (!ua_chan) { - DBG("Application channel is being torn down. Abort event notify"); - ret = 0; - goto error_rcu_unlock; - } - - LTTNG_ASSERT(ua_chan->session); - ua_sess = ua_chan->session; - - registry = get_session_registry(ua_sess); - if (!registry) { - DBG("Application session is being torn down. Abort event notify"); - ret = 0; - goto error_rcu_unlock; - } - - if (ua_sess->buffer_type == LTTNG_BUFFER_PER_UID) { - chan_reg_key = ua_chan->tracing_channel_id; - } else { - chan_reg_key = ua_chan->key; - } - - pthread_mutex_lock(®istry->lock); - - /* - * From this point on, this call acquires the ownership of the sig, fields - * and model_emf_uri meaning any free are done inside it if needed. These - * three variables MUST NOT be read/write after this. - */ - ret_code = ust_registry_create_event(registry, chan_reg_key, - sobjd, cobjd, name, sig, nr_fields, fields, - loglevel_value, model_emf_uri, ua_sess->buffer_type, - &event_id, app); - sig = NULL; - fields = NULL; - model_emf_uri = NULL; - - /* - * The return value is returned to ustctl so in case of an error, the - * application can be notified. In case of an error, it's important not to - * return a negative error or else the application will get closed. - */ - ret = lttng_ust_ctl_reply_register_event(sock, event_id, ret_code); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app reply event failed. Application died: pid = %d, sock = %d.", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app reply event failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app reply event failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - /* - * No need to wipe the create event since the application socket will - * get close on error hence cleaning up everything by itself. - */ - goto error; - } - - DBG3("UST registry event %s with id %" PRId32 " added successfully", - name, event_id); - -error: - pthread_mutex_unlock(®istry->lock); -error_rcu_unlock: - rcu_read_unlock(); - free(sig); - free(fields); - free(model_emf_uri); - return ret; -} - -/* - * Add enum to the UST session registry. Once done, this replies to the - * application with the appropriate error code. - * - * The session UST registry lock is acquired within this function. - * - * On success 0 is returned else a negative value. - */ -static int add_enum_ust_registry(int sock, int sobjd, char *name, - struct lttng_ust_ctl_enum_entry *entries, size_t nr_entries) -{ - int ret = 0, ret_code; - struct ust_app *app; - struct ust_app_session *ua_sess; - struct ust_registry_session *registry; - uint64_t enum_id = -1ULL; - - rcu_read_lock(); - - /* Lookup application. If not found, there is a code flow error. */ - app = find_app_by_notify_sock(sock); - if (!app) { - /* Return an error since this is not an error */ - DBG("Application socket %d is being torn down. Aborting enum registration", - sock); - free(entries); - ret = -1; - goto error_rcu_unlock; - } - - /* Lookup session by UST object descriptor. */ - ua_sess = find_session_by_objd(app, sobjd); - if (!ua_sess) { - /* Return an error since this is not an error */ - DBG("Application session is being torn down (session not found). Aborting enum registration."); - free(entries); - goto error_rcu_unlock; - } - - registry = get_session_registry(ua_sess); - if (!registry) { - DBG("Application session is being torn down (registry not found). Aborting enum registration."); - free(entries); - goto error_rcu_unlock; - } - - pthread_mutex_lock(®istry->lock); - - /* - * From this point on, the callee acquires the ownership of - * entries. The variable entries MUST NOT be read/written after - * call. - */ - ret_code = ust_registry_create_or_find_enum(registry, sobjd, name, - entries, nr_entries, &enum_id); - entries = NULL; - - /* - * The return value is returned to ustctl so in case of an error, the - * application can be notified. In case of an error, it's important not to - * return a negative error or else the application will get closed. - */ - ret = lttng_ust_ctl_reply_register_enum(sock, enum_id, ret_code); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app reply enum failed. Application died: pid = %d, sock = %d", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app reply enum failed. Communication time out: pid = %d, sock = %d", - app->pid, app->sock); - } else { - ERR("UST app reply enum failed with ret %d: pid = %d, sock = %d", - ret, app->pid, app->sock); - } - /* - * No need to wipe the create enum since the application socket will - * get close on error hence cleaning up everything by itself. - */ - goto error; - } - - DBG3("UST registry enum %s added successfully or already found", name); - -error: - pthread_mutex_unlock(®istry->lock); -error_rcu_unlock: - rcu_read_unlock(); - return ret; -} - -/* - * Handle application notification through the given notify socket. - * - * Return 0 on success or else a negative value. - */ -int ust_app_recv_notify(int sock) -{ - int ret; - enum lttng_ust_ctl_notify_cmd cmd; - - DBG3("UST app receiving notify from sock %d", sock); - - ret = lttng_ust_ctl_recv_notify(sock, &cmd); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app recv notify failed. Application died: sock = %d", - sock); - } else if (ret == -EAGAIN) { - WARN("UST app recv notify failed. Communication time out: sock = %d", - sock); - } else { - ERR("UST app recv notify failed with ret %d: sock = %d", - ret, sock); - } - goto error; - } - - switch (cmd) { - case LTTNG_UST_CTL_NOTIFY_CMD_EVENT: - { - int sobjd, cobjd, loglevel_value; - char name[LTTNG_UST_ABI_SYM_NAME_LEN], *sig, *model_emf_uri; - size_t nr_fields; - struct lttng_ust_ctl_field *fields; - - DBG2("UST app ustctl register event received"); - - ret = lttng_ust_ctl_recv_register_event(sock, &sobjd, &cobjd, name, - &loglevel_value, &sig, &nr_fields, &fields, - &model_emf_uri); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app recv event failed. Application died: sock = %d", - sock); - } else if (ret == -EAGAIN) { - WARN("UST app recv event failed. Communication time out: sock = %d", - sock); - } else { - ERR("UST app recv event failed with ret %d: sock = %d", - ret, sock); - } - goto error; - } - - /* - * Add event to the UST registry coming from the notify socket. This - * call will free if needed the sig, fields and model_emf_uri. This - * code path loses the ownsership of these variables and transfer them - * to the this function. - */ - ret = add_event_ust_registry(sock, sobjd, cobjd, name, sig, nr_fields, - fields, loglevel_value, model_emf_uri); - if (ret < 0) { - goto error; - } - - break; - } - case LTTNG_UST_CTL_NOTIFY_CMD_CHANNEL: - { - int sobjd, cobjd; - size_t nr_fields; - struct lttng_ust_ctl_field *fields; - - DBG2("UST app ustctl register channel received"); - - ret = lttng_ust_ctl_recv_register_channel(sock, &sobjd, &cobjd, &nr_fields, - &fields); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app recv channel failed. Application died: sock = %d", - sock); - } else if (ret == -EAGAIN) { - WARN("UST app recv channel failed. Communication time out: sock = %d", - sock); - } else { - ERR("UST app recv channel failed with ret %d: sock = %d)", - ret, sock); - } - goto error; - } - - /* - * The fields ownership are transfered to this function call meaning - * that if needed it will be freed. After this, it's invalid to access - * fields or clean it up. - */ - ret = reply_ust_register_channel(sock, cobjd, nr_fields, - fields); - if (ret < 0) { - goto error; - } - - break; - } - case LTTNG_UST_CTL_NOTIFY_CMD_ENUM: - { - int sobjd; - char name[LTTNG_UST_ABI_SYM_NAME_LEN]; - size_t nr_entries; - struct lttng_ust_ctl_enum_entry *entries; - - DBG2("UST app ustctl register enum received"); - - ret = lttng_ust_ctl_recv_register_enum(sock, &sobjd, name, - &entries, &nr_entries); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app recv enum failed. Application died: sock = %d", - sock); - } else if (ret == -EAGAIN) { - WARN("UST app recv enum failed. Communication time out: sock = %d", - sock); - } else { - ERR("UST app recv enum failed with ret %d: sock = %d", - ret, sock); - } - goto error; - } - - /* Callee assumes ownership of entries */ - ret = add_enum_ust_registry(sock, sobjd, name, - entries, nr_entries); - if (ret < 0) { - goto error; - } - - break; - } - default: - /* Should NEVER happen. */ - abort(); - } - -error: - return ret; -} - -/* - * Once the notify socket hangs up, this is called. First, it tries to find the - * corresponding application. On failure, the call_rcu to close the socket is - * executed. If an application is found, it tries to delete it from the notify - * socket hash table. Whathever the result, it proceeds to the call_rcu. - * - * Note that an object needs to be allocated here so on ENOMEM failure, the - * call RCU is not done but the rest of the cleanup is. - */ -void ust_app_notify_sock_unregister(int sock) -{ - int err_enomem = 0; - struct lttng_ht_iter iter; - struct ust_app *app; - struct ust_app_notify_sock_obj *obj; - - LTTNG_ASSERT(sock >= 0); - - rcu_read_lock(); - - obj = zmalloc(sizeof(*obj)); - if (!obj) { - /* - * An ENOMEM is kind of uncool. If this strikes we continue the - * procedure but the call_rcu will not be called. In this case, we - * accept the fd leak rather than possibly creating an unsynchronized - * state between threads. - * - * TODO: The notify object should be created once the notify socket is - * registered and stored independantely from the ust app object. The - * tricky part is to synchronize the teardown of the application and - * this notify object. Let's keep that in mind so we can avoid this - * kind of shenanigans with ENOMEM in the teardown path. - */ - err_enomem = 1; - } else { - obj->fd = sock; - } - - DBG("UST app notify socket unregister %d", sock); - - /* - * Lookup application by notify socket. If this fails, this means that the - * hash table delete has already been done by the application - * unregistration process so we can safely close the notify socket in a - * call RCU. - */ - app = find_app_by_notify_sock(sock); - if (!app) { - goto close_socket; - } - - iter.iter.node = &app->notify_sock_n.node; - - /* - * Whatever happens here either we fail or succeed, in both cases we have - * to close the socket after a grace period to continue to the call RCU - * here. If the deletion is successful, the application is not visible - * anymore by other threads and is it fails it means that it was already - * deleted from the hash table so either way we just have to close the - * socket. - */ - (void) lttng_ht_del(ust_app_ht_by_notify_sock, &iter); - -close_socket: - rcu_read_unlock(); - - /* - * Close socket after a grace period to avoid for the socket to be reused - * before the application object is freed creating potential race between - * threads trying to add unique in the global hash table. - */ - if (!err_enomem) { - call_rcu(&obj->head, close_notify_sock_rcu); - } -} - -/* - * Destroy a ust app data structure and free its memory. - */ -void ust_app_destroy(struct ust_app *app) -{ - if (!app) { - return; - } - - call_rcu(&app->pid_n.head, delete_ust_app_rcu); -} - -/* - * Take a snapshot for a given UST session. The snapshot is sent to the given - * output. - * - * Returns LTTNG_OK on success or a LTTNG_ERR error code. - */ -enum lttng_error_code ust_app_snapshot_record( - const struct ltt_ust_session *usess, - const struct consumer_output *output, int wait, - uint64_t nb_packets_per_stream) -{ - int ret = 0; - enum lttng_error_code status = LTTNG_OK; - struct lttng_ht_iter iter; - struct ust_app *app; - char *trace_path = NULL; - - LTTNG_ASSERT(usess); - LTTNG_ASSERT(output); - - rcu_read_lock(); - - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_UID: - { - struct buffer_reg_uid *reg; - - cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { - struct buffer_reg_channel *buf_reg_chan; - struct consumer_socket *socket; - char pathname[PATH_MAX]; - size_t consumer_path_offset = 0; - - if (!reg->registry->reg.ust->metadata_key) { - /* Skip since no metadata is present */ - continue; - } - - /* Get consumer socket to use to push the metadata.*/ - socket = consumer_find_socket_by_bitness(reg->bits_per_long, - usess->consumer); - if (!socket) { - status = LTTNG_ERR_INVALID; - goto error; - } - - memset(pathname, 0, sizeof(pathname)); - ret = snprintf(pathname, sizeof(pathname), - DEFAULT_UST_TRACE_UID_PATH, - reg->uid, reg->bits_per_long); - if (ret < 0) { - PERROR("snprintf snapshot path"); - status = LTTNG_ERR_INVALID; - goto error; - } - /* Free path allowed on previous iteration. */ - free(trace_path); - trace_path = setup_channel_trace_path(usess->consumer, pathname, - &consumer_path_offset); - if (!trace_path) { - status = LTTNG_ERR_INVALID; - goto error; - } - /* Add the UST default trace dir to path. */ - cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, - buf_reg_chan, node.node) { - status = consumer_snapshot_channel(socket, - buf_reg_chan->consumer_key, - output, 0, usess->uid, - usess->gid, &trace_path[consumer_path_offset], wait, - nb_packets_per_stream); - if (status != LTTNG_OK) { - goto error; - } - } - status = consumer_snapshot_channel(socket, - reg->registry->reg.ust->metadata_key, output, 1, - usess->uid, usess->gid, &trace_path[consumer_path_offset], - wait, 0); - if (status != LTTNG_OK) { - goto error; - } - } - break; - } - case LTTNG_BUFFER_PER_PID: - { - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - struct consumer_socket *socket; - struct lttng_ht_iter chan_iter; - struct ust_app_channel *ua_chan; - struct ust_app_session *ua_sess; - struct ust_registry_session *registry; - char pathname[PATH_MAX]; - size_t consumer_path_offset = 0; - - ua_sess = lookup_session_by_app(usess, app); - if (!ua_sess) { - /* Session not associated with this app. */ - continue; - } - - /* Get the right consumer socket for the application. */ - socket = consumer_find_socket_by_bitness(app->bits_per_long, - output); - if (!socket) { - status = LTTNG_ERR_INVALID; - goto error; - } - - /* Add the UST default trace dir to path. */ - memset(pathname, 0, sizeof(pathname)); - ret = snprintf(pathname, sizeof(pathname), "%s", - ua_sess->path); - if (ret < 0) { - status = LTTNG_ERR_INVALID; - PERROR("snprintf snapshot path"); - goto error; - } - /* Free path allowed on previous iteration. */ - free(trace_path); - trace_path = setup_channel_trace_path(usess->consumer, pathname, - &consumer_path_offset); - if (!trace_path) { - status = LTTNG_ERR_INVALID; - goto error; - } - cds_lfht_for_each_entry(ua_sess->channels->ht, &chan_iter.iter, - ua_chan, node.node) { - status = consumer_snapshot_channel(socket, - ua_chan->key, output, 0, - lttng_credentials_get_uid(&ua_sess->effective_credentials), - lttng_credentials_get_gid(&ua_sess->effective_credentials), - &trace_path[consumer_path_offset], wait, - nb_packets_per_stream); - switch (status) { - case LTTNG_OK: - break; - case LTTNG_ERR_CHAN_NOT_FOUND: - continue; - default: - goto error; - } - } - - registry = get_session_registry(ua_sess); - if (!registry) { - DBG("Application session is being torn down. Skip application."); - continue; - } - status = consumer_snapshot_channel(socket, - registry->metadata_key, output, 1, - lttng_credentials_get_uid(&ua_sess->effective_credentials), - lttng_credentials_get_gid(&ua_sess->effective_credentials), - &trace_path[consumer_path_offset], wait, 0); - switch (status) { - case LTTNG_OK: - break; - case LTTNG_ERR_CHAN_NOT_FOUND: - continue; - default: - goto error; - } - } - break; - } - default: - abort(); - break; - } - -error: - free(trace_path); - rcu_read_unlock(); - return status; -} - -/* - * Return the size taken by one more packet per stream. - */ -uint64_t ust_app_get_size_one_more_packet_per_stream( - const struct ltt_ust_session *usess, uint64_t cur_nr_packets) -{ - uint64_t tot_size = 0; - struct ust_app *app; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(usess); - - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_UID: - { - struct buffer_reg_uid *reg; - - cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { - struct buffer_reg_channel *buf_reg_chan; - - rcu_read_lock(); - cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, - buf_reg_chan, node.node) { - if (cur_nr_packets >= buf_reg_chan->num_subbuf) { - /* - * Don't take channel into account if we - * already grab all its packets. - */ - continue; - } - tot_size += buf_reg_chan->subbuf_size * buf_reg_chan->stream_count; - } - rcu_read_unlock(); - } - break; - } - case LTTNG_BUFFER_PER_PID: - { - rcu_read_lock(); - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - struct ust_app_channel *ua_chan; - struct ust_app_session *ua_sess; - struct lttng_ht_iter chan_iter; - - ua_sess = lookup_session_by_app(usess, app); - if (!ua_sess) { - /* Session not associated with this app. */ - continue; - } - - cds_lfht_for_each_entry(ua_sess->channels->ht, &chan_iter.iter, - ua_chan, node.node) { - if (cur_nr_packets >= ua_chan->attr.num_subbuf) { - /* - * Don't take channel into account if we - * already grab all its packets. - */ - continue; - } - tot_size += ua_chan->attr.subbuf_size * ua_chan->streams.count; - } - } - rcu_read_unlock(); - break; - } - default: - abort(); - break; - } - - return tot_size; -} - -int ust_app_uid_get_channel_runtime_stats(uint64_t ust_session_id, - struct cds_list_head *buffer_reg_uid_list, - struct consumer_output *consumer, uint64_t uchan_id, - int overwrite, uint64_t *discarded, uint64_t *lost) -{ - int ret; - uint64_t consumer_chan_key; - - *discarded = 0; - *lost = 0; - - ret = buffer_reg_uid_consumer_channel_key( - buffer_reg_uid_list, uchan_id, &consumer_chan_key); - if (ret < 0) { - /* Not found */ - ret = 0; - goto end; - } - - if (overwrite) { - ret = consumer_get_lost_packets(ust_session_id, - consumer_chan_key, consumer, lost); - } else { - ret = consumer_get_discarded_events(ust_session_id, - consumer_chan_key, consumer, discarded); - } - -end: - return ret; -} - -int ust_app_pid_get_channel_runtime_stats(struct ltt_ust_session *usess, - struct ltt_ust_channel *uchan, - struct consumer_output *consumer, int overwrite, - uint64_t *discarded, uint64_t *lost) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct lttng_ht_node_str *ua_chan_node; - struct ust_app *app; - struct ust_app_session *ua_sess; - struct ust_app_channel *ua_chan; - - *discarded = 0; - *lost = 0; - - rcu_read_lock(); - /* - * Iterate over every registered applications. Sum counters for - * all applications containing requested session and channel. - */ - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - struct lttng_ht_iter uiter; - - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - continue; - } - - /* Get channel */ - lttng_ht_lookup(ua_sess->channels, (void *) uchan->name, &uiter); - ua_chan_node = lttng_ht_iter_get_node_str(&uiter); - /* If the session is found for the app, the channel must be there */ - LTTNG_ASSERT(ua_chan_node); - - ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); - - if (overwrite) { - uint64_t _lost; - - ret = consumer_get_lost_packets(usess->id, ua_chan->key, - consumer, &_lost); - if (ret < 0) { - break; - } - (*lost) += _lost; - } else { - uint64_t _discarded; - - ret = consumer_get_discarded_events(usess->id, - ua_chan->key, consumer, &_discarded); - if (ret < 0) { - break; - } - (*discarded) += _discarded; - } - } - - rcu_read_unlock(); - return ret; -} - -static -int ust_app_regenerate_statedump(struct ltt_ust_session *usess, - struct ust_app *app) -{ - int ret = 0; - struct ust_app_session *ua_sess; - - DBG("Regenerating the metadata for ust app pid %d", app->pid); - - rcu_read_lock(); - - ua_sess = lookup_session_by_app(usess, app); - if (ua_sess == NULL) { - /* The session is in teardown process. Ignore and continue. */ - goto end; - } - - pthread_mutex_lock(&ua_sess->lock); - - if (ua_sess->deleted) { - goto end_unlock; - } - - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_regenerate_statedump(app->sock, ua_sess->handle); - pthread_mutex_unlock(&app->sock_lock); - -end_unlock: - pthread_mutex_unlock(&ua_sess->lock); - -end: - rcu_read_unlock(); - health_code_update(); - return ret; -} - -/* - * Regenerate the statedump for each app in the session. - */ -int ust_app_regenerate_statedump_all(struct ltt_ust_session *usess) -{ - int ret = 0; - struct lttng_ht_iter iter; - struct ust_app *app; - - DBG("Regenerating the metadata for all UST apps"); - - rcu_read_lock(); - - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - if (!app->compatible) { - continue; - } - - ret = ust_app_regenerate_statedump(usess, app); - if (ret < 0) { - /* Continue to the next app even on error */ - continue; - } - } - - rcu_read_unlock(); - - return 0; -} - -/* - * Rotate all the channels of a session. - * - * Return LTTNG_OK on success or else an LTTng error code. - */ -enum lttng_error_code ust_app_rotate_session(struct ltt_session *session) -{ - int ret; - enum lttng_error_code cmd_ret = LTTNG_OK; - struct lttng_ht_iter iter; - struct ust_app *app; - struct ltt_ust_session *usess = session->ust_session; - - LTTNG_ASSERT(usess); - - rcu_read_lock(); - - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_UID: - { - struct buffer_reg_uid *reg; - - cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { - struct buffer_reg_channel *buf_reg_chan; - struct consumer_socket *socket; - - /* Get consumer socket to use to push the metadata.*/ - socket = consumer_find_socket_by_bitness(reg->bits_per_long, - usess->consumer); - if (!socket) { - cmd_ret = LTTNG_ERR_INVALID; - goto error; - } - - /* Rotate the data channels. */ - cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, - buf_reg_chan, node.node) { - ret = consumer_rotate_channel(socket, - buf_reg_chan->consumer_key, - usess->uid, usess->gid, - usess->consumer, - /* is_metadata_channel */ false); - if (ret < 0) { - cmd_ret = LTTNG_ERR_ROTATION_FAIL_CONSUMER; - goto error; - } - } - - /* - * The metadata channel might not be present. - * - * Consumer stream allocation can be done - * asynchronously and can fail on intermediary - * operations (i.e add context) and lead to data - * channels created with no metadata channel. - */ - if (!reg->registry->reg.ust->metadata_key) { - /* Skip since no metadata is present. */ - continue; - } - - (void) push_metadata(reg->registry->reg.ust, usess->consumer); - - ret = consumer_rotate_channel(socket, - reg->registry->reg.ust->metadata_key, - usess->uid, usess->gid, - usess->consumer, - /* is_metadata_channel */ true); - if (ret < 0) { - cmd_ret = LTTNG_ERR_ROTATION_FAIL_CONSUMER; - goto error; - } - } - break; - } - case LTTNG_BUFFER_PER_PID: - { - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - struct consumer_socket *socket; - struct lttng_ht_iter chan_iter; - struct ust_app_channel *ua_chan; - struct ust_app_session *ua_sess; - struct ust_registry_session *registry; - - ua_sess = lookup_session_by_app(usess, app); - if (!ua_sess) { - /* Session not associated with this app. */ - continue; - } - - /* Get the right consumer socket for the application. */ - socket = consumer_find_socket_by_bitness(app->bits_per_long, - usess->consumer); - if (!socket) { - cmd_ret = LTTNG_ERR_INVALID; - goto error; - } - - registry = get_session_registry(ua_sess); - if (!registry) { - DBG("Application session is being torn down. Skip application."); - continue; - } - - /* Rotate the data channels. */ - cds_lfht_for_each_entry(ua_sess->channels->ht, &chan_iter.iter, - ua_chan, node.node) { - ret = consumer_rotate_channel(socket, - ua_chan->key, - lttng_credentials_get_uid(&ua_sess->effective_credentials), - lttng_credentials_get_gid(&ua_sess->effective_credentials), - ua_sess->consumer, - /* is_metadata_channel */ false); - if (ret < 0) { - /* Per-PID buffer and application going away. */ - if (ret == -LTTNG_ERR_CHAN_NOT_FOUND) - continue; - cmd_ret = LTTNG_ERR_ROTATION_FAIL_CONSUMER; - goto error; - } - } - - /* Rotate the metadata channel. */ - (void) push_metadata(registry, usess->consumer); - ret = consumer_rotate_channel(socket, - registry->metadata_key, - lttng_credentials_get_uid(&ua_sess->effective_credentials), - lttng_credentials_get_gid(&ua_sess->effective_credentials), - ua_sess->consumer, - /* is_metadata_channel */ true); - if (ret < 0) { - /* Per-PID buffer and application going away. */ - if (ret == -LTTNG_ERR_CHAN_NOT_FOUND) - continue; - cmd_ret = LTTNG_ERR_ROTATION_FAIL_CONSUMER; - goto error; - } - } - break; - } - default: - abort(); - break; - } - - cmd_ret = LTTNG_OK; - -error: - rcu_read_unlock(); - return cmd_ret; -} - -enum lttng_error_code ust_app_create_channel_subdirectories( - const struct ltt_ust_session *usess) -{ - enum lttng_error_code ret = LTTNG_OK; - struct lttng_ht_iter iter; - enum lttng_trace_chunk_status chunk_status; - char *pathname_index; - int fmt_ret; - - LTTNG_ASSERT(usess->current_trace_chunk); - rcu_read_lock(); - - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_UID: - { - struct buffer_reg_uid *reg; - - cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { - fmt_ret = asprintf(&pathname_index, - DEFAULT_UST_TRACE_DIR "/" DEFAULT_UST_TRACE_UID_PATH "/" DEFAULT_INDEX_DIR, - reg->uid, reg->bits_per_long); - if (fmt_ret < 0) { - ERR("Failed to format channel index directory"); - ret = LTTNG_ERR_CREATE_DIR_FAIL; - goto error; - } - - /* - * Create the index subdirectory which will take care - * of implicitly creating the channel's path. - */ - chunk_status = lttng_trace_chunk_create_subdirectory( - usess->current_trace_chunk, - pathname_index); - free(pathname_index); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = LTTNG_ERR_CREATE_DIR_FAIL; - goto error; - } - } - break; - } - case LTTNG_BUFFER_PER_PID: - { - struct ust_app *app; - - /* - * Create the toplevel ust/ directory in case no apps are running. - */ - chunk_status = lttng_trace_chunk_create_subdirectory( - usess->current_trace_chunk, - DEFAULT_UST_TRACE_DIR); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = LTTNG_ERR_CREATE_DIR_FAIL; - goto error; - } - - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, - pid_n.node) { - struct ust_app_session *ua_sess; - struct ust_registry_session *registry; - - ua_sess = lookup_session_by_app(usess, app); - if (!ua_sess) { - /* Session not associated with this app. */ - continue; - } - - registry = get_session_registry(ua_sess); - if (!registry) { - DBG("Application session is being torn down. Skip application."); - continue; - } - - fmt_ret = asprintf(&pathname_index, - DEFAULT_UST_TRACE_DIR "/%s/" DEFAULT_INDEX_DIR, - ua_sess->path); - if (fmt_ret < 0) { - ERR("Failed to format channel index directory"); - ret = LTTNG_ERR_CREATE_DIR_FAIL; - goto error; - } - /* - * Create the index subdirectory which will take care - * of implicitly creating the channel's path. - */ - chunk_status = lttng_trace_chunk_create_subdirectory( - usess->current_trace_chunk, - pathname_index); - free(pathname_index); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = LTTNG_ERR_CREATE_DIR_FAIL; - goto error; - } - } - break; - } - default: - abort(); - } - - ret = LTTNG_OK; -error: - rcu_read_unlock(); - return ret; -} - -/* - * Clear all the channels of a session. - * - * Return LTTNG_OK on success or else an LTTng error code. - */ -enum lttng_error_code ust_app_clear_session(struct ltt_session *session) -{ - int ret; - enum lttng_error_code cmd_ret = LTTNG_OK; - struct lttng_ht_iter iter; - struct ust_app *app; - struct ltt_ust_session *usess = session->ust_session; - - LTTNG_ASSERT(usess); - - rcu_read_lock(); - - if (usess->active) { - ERR("Expecting inactive session %s (%" PRIu64 ")", session->name, session->id); - cmd_ret = LTTNG_ERR_FATAL; - goto end; - } - - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_UID: - { - struct buffer_reg_uid *reg; - - cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { - struct buffer_reg_channel *buf_reg_chan; - struct consumer_socket *socket; - - /* Get consumer socket to use to push the metadata.*/ - socket = consumer_find_socket_by_bitness(reg->bits_per_long, - usess->consumer); - if (!socket) { - cmd_ret = LTTNG_ERR_INVALID; - goto error_socket; - } - - /* Clear the data channels. */ - cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, - buf_reg_chan, node.node) { - ret = consumer_clear_channel(socket, - buf_reg_chan->consumer_key); - if (ret < 0) { - goto error; - } - } - - (void) push_metadata(reg->registry->reg.ust, usess->consumer); - - /* - * Clear the metadata channel. - * Metadata channel is not cleared per se but we still need to - * perform a rotation operation on it behind the scene. - */ - ret = consumer_clear_channel(socket, - reg->registry->reg.ust->metadata_key); - if (ret < 0) { - goto error; - } - } - break; - } - case LTTNG_BUFFER_PER_PID: - { - cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { - struct consumer_socket *socket; - struct lttng_ht_iter chan_iter; - struct ust_app_channel *ua_chan; - struct ust_app_session *ua_sess; - struct ust_registry_session *registry; - - ua_sess = lookup_session_by_app(usess, app); - if (!ua_sess) { - /* Session not associated with this app. */ - continue; - } - - /* Get the right consumer socket for the application. */ - socket = consumer_find_socket_by_bitness(app->bits_per_long, - usess->consumer); - if (!socket) { - cmd_ret = LTTNG_ERR_INVALID; - goto error_socket; - } - - registry = get_session_registry(ua_sess); - if (!registry) { - DBG("Application session is being torn down. Skip application."); - continue; - } - - /* Clear the data channels. */ - cds_lfht_for_each_entry(ua_sess->channels->ht, &chan_iter.iter, - ua_chan, node.node) { - ret = consumer_clear_channel(socket, ua_chan->key); - if (ret < 0) { - /* Per-PID buffer and application going away. */ - if (ret == -LTTNG_ERR_CHAN_NOT_FOUND) { - continue; - } - goto error; - } - } - - (void) push_metadata(registry, usess->consumer); - - /* - * Clear the metadata channel. - * Metadata channel is not cleared per se but we still need to - * perform rotation operation on it behind the scene. - */ - ret = consumer_clear_channel(socket, registry->metadata_key); - if (ret < 0) { - /* Per-PID buffer and application going away. */ - if (ret == -LTTNG_ERR_CHAN_NOT_FOUND) { - continue; - } - goto error; - } - } - break; - } - default: - abort(); - break; - } - - cmd_ret = LTTNG_OK; - goto end; - -error: - switch (-ret) { - case LTTCOMM_CONSUMERD_RELAYD_CLEAR_DISALLOWED: - cmd_ret = LTTNG_ERR_CLEAR_RELAY_DISALLOWED; - break; - default: - cmd_ret = LTTNG_ERR_CLEAR_FAIL_CONSUMER; - } - -error_socket: -end: - rcu_read_unlock(); - return cmd_ret; -} - -/* - * This function skips the metadata channel as the begin/end timestamps of a - * metadata packet are useless. - * - * Moreover, opening a packet after a "clear" will cause problems for live - * sessions as it will introduce padding that was not part of the first trace - * chunk. The relay daemon expects the content of the metadata stream of - * successive metadata trace chunks to be strict supersets of one another. - * - * For example, flushing a packet at the beginning of the metadata stream of - * a trace chunk resulting from a "clear" session command will cause the - * size of the metadata stream of the new trace chunk to not match the size of - * the metadata stream of the original chunk. This will confuse the relay - * daemon as the same "offset" in a metadata stream will no longer point - * to the same content. - */ -enum lttng_error_code ust_app_open_packets(struct ltt_session *session) -{ - enum lttng_error_code ret = LTTNG_OK; - struct lttng_ht_iter iter; - struct ltt_ust_session *usess = session->ust_session; - - LTTNG_ASSERT(usess); - - rcu_read_lock(); - - switch (usess->buffer_type) { - case LTTNG_BUFFER_PER_UID: - { - struct buffer_reg_uid *reg; - - cds_list_for_each_entry ( - reg, &usess->buffer_reg_uid_list, lnode) { - struct buffer_reg_channel *buf_reg_chan; - struct consumer_socket *socket; - - socket = consumer_find_socket_by_bitness( - reg->bits_per_long, usess->consumer); - if (!socket) { - ret = LTTNG_ERR_FATAL; - goto error; - } - - cds_lfht_for_each_entry(reg->registry->channels->ht, - &iter.iter, buf_reg_chan, node.node) { - const int open_ret = - consumer_open_channel_packets( - socket, - buf_reg_chan->consumer_key); - - if (open_ret < 0) { - ret = LTTNG_ERR_UNK; - goto error; - } - } - } - break; - } - case LTTNG_BUFFER_PER_PID: - { - struct ust_app *app; - - cds_lfht_for_each_entry ( - ust_app_ht->ht, &iter.iter, app, pid_n.node) { - struct consumer_socket *socket; - struct lttng_ht_iter chan_iter; - struct ust_app_channel *ua_chan; - struct ust_app_session *ua_sess; - struct ust_registry_session *registry; - - ua_sess = lookup_session_by_app(usess, app); - if (!ua_sess) { - /* Session not associated with this app. */ - continue; - } - - /* Get the right consumer socket for the application. */ - socket = consumer_find_socket_by_bitness( - app->bits_per_long, usess->consumer); - if (!socket) { - ret = LTTNG_ERR_FATAL; - goto error; - } - - registry = get_session_registry(ua_sess); - if (!registry) { - DBG("Application session is being torn down. Skip application."); - continue; - } - - cds_lfht_for_each_entry(ua_sess->channels->ht, - &chan_iter.iter, ua_chan, node.node) { - const int open_ret = - consumer_open_channel_packets( - socket, - ua_chan->key); - - if (open_ret < 0) { - /* - * Per-PID buffer and application going - * away. - */ - if (open_ret == -LTTNG_ERR_CHAN_NOT_FOUND) { - continue; - } - - ret = LTTNG_ERR_UNK; - goto error; - } - } - } - break; - } - default: - abort(); - break; - } - -error: - rcu_read_unlock(); - return ret; -} diff --git a/src/bin/lttng-sessiond/ust-app.cpp b/src/bin/lttng-sessiond/ust-app.cpp new file mode 100644 index 000000000..a848d474c --- /dev/null +++ b/src/bin/lttng-sessiond/ust-app.cpp @@ -0,0 +1,7818 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2016 Jérémie Galarneau + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#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 "buffer-registry.h" +#include "condition-internal.h" +#include "fd-limit.h" +#include "health-sessiond.h" +#include "ust-app.h" +#include "ust-consumer.h" +#include "lttng-ust-ctl.h" +#include "lttng-ust-error.h" +#include "utils.h" +#include "session.h" +#include "lttng-sessiond.h" +#include "notification-thread-commands.h" +#include "rotate.h" +#include "event.h" +#include "event-notifier-error-accounting.h" + + +struct lttng_ht *ust_app_ht; +struct lttng_ht *ust_app_ht_by_sock; +struct lttng_ht *ust_app_ht_by_notify_sock; + +static +int ust_app_flush_app_session(struct ust_app *app, struct ust_app_session *ua_sess); + +/* Next available channel key. Access under next_channel_key_lock. */ +static uint64_t _next_channel_key; +static pthread_mutex_t next_channel_key_lock = PTHREAD_MUTEX_INITIALIZER; + +/* Next available session ID. Access under next_session_id_lock. */ +static uint64_t _next_session_id; +static pthread_mutex_t next_session_id_lock = PTHREAD_MUTEX_INITIALIZER; + +/* + * Return the incremented value of next_channel_key. + */ +static uint64_t get_next_channel_key(void) +{ + uint64_t ret; + + pthread_mutex_lock(&next_channel_key_lock); + ret = ++_next_channel_key; + pthread_mutex_unlock(&next_channel_key_lock); + return ret; +} + +/* + * Return the atomically incremented value of next_session_id. + */ +static uint64_t get_next_session_id(void) +{ + uint64_t ret; + + pthread_mutex_lock(&next_session_id_lock); + ret = ++_next_session_id; + pthread_mutex_unlock(&next_session_id_lock); + return ret; +} + +static void copy_channel_attr_to_ustctl( + struct lttng_ust_ctl_consumer_channel_attr *attr, + struct lttng_ust_abi_channel_attr *uattr) +{ + /* Copy event attributes since the layout is different. */ + attr->subbuf_size = uattr->subbuf_size; + attr->num_subbuf = uattr->num_subbuf; + attr->overwrite = uattr->overwrite; + attr->switch_timer_interval = uattr->switch_timer_interval; + attr->read_timer_interval = uattr->read_timer_interval; + attr->output = (lttng_ust_abi_output) uattr->output; + attr->blocking_timeout = uattr->u.s.blocking_timeout; +} + +/* + * Match function for the hash table lookup. + * + * It matches an ust app event based on three attributes which are the event + * name, the filter bytecode and the loglevel. + */ +static int ht_match_ust_app_event(struct cds_lfht_node *node, const void *_key) +{ + struct ust_app_event *event; + const struct ust_app_ht_key *key; + int ev_loglevel_value; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + event = caa_container_of(node, struct ust_app_event, node.node); + key = (ust_app_ht_key *) _key; + ev_loglevel_value = event->attr.loglevel; + + /* Match the 4 elements of the key: name, filter, loglevel, exclusions */ + + /* Event name */ + if (strncmp(event->attr.name, key->name, sizeof(event->attr.name)) != 0) { + goto no_match; + } + + /* Event loglevel. */ + if (ev_loglevel_value != key->loglevel_type) { + if (event->attr.loglevel_type == LTTNG_UST_ABI_LOGLEVEL_ALL + && key->loglevel_type == 0 && + ev_loglevel_value == -1) { + /* + * Match is accepted. This is because on event creation, the + * loglevel is set to -1 if the event loglevel type is ALL so 0 and + * -1 are accepted for this loglevel type since 0 is the one set by + * the API when receiving an enable event. + */ + } else { + goto no_match; + } + } + + /* One of the filters is NULL, fail. */ + if ((key->filter && !event->filter) || (!key->filter && event->filter)) { + goto no_match; + } + + if (key->filter && event->filter) { + /* Both filters exists, check length followed by the bytecode. */ + if (event->filter->len != key->filter->len || + memcmp(event->filter->data, key->filter->data, + event->filter->len) != 0) { + goto no_match; + } + } + + /* One of the exclusions is NULL, fail. */ + if ((key->exclusion && !event->exclusion) || (!key->exclusion && event->exclusion)) { + goto no_match; + } + + if (key->exclusion && event->exclusion) { + /* Both exclusions exists, check count followed by the names. */ + if (event->exclusion->count != key->exclusion->count || + memcmp(event->exclusion->names, key->exclusion->names, + event->exclusion->count * LTTNG_UST_ABI_SYM_NAME_LEN) != 0) { + goto no_match; + } + } + + + /* Match. */ + return 1; + +no_match: + return 0; +} + +/* + * Unique add of an ust app event in the given ht. This uses the custom + * ht_match_ust_app_event match function and the event name as hash. + */ +static void add_unique_ust_app_event(struct ust_app_channel *ua_chan, + struct ust_app_event *event) +{ + struct cds_lfht_node *node_ptr; + struct ust_app_ht_key key; + struct lttng_ht *ht; + + LTTNG_ASSERT(ua_chan); + LTTNG_ASSERT(ua_chan->events); + LTTNG_ASSERT(event); + + ht = ua_chan->events; + key.name = event->attr.name; + key.filter = event->filter; + key.loglevel_type = (lttng_ust_abi_loglevel_type) event->attr.loglevel; + key.exclusion = event->exclusion; + + node_ptr = cds_lfht_add_unique(ht->ht, + ht->hash_fct(event->node.key, lttng_ht_seed), + ht_match_ust_app_event, &key, &event->node.node); + LTTNG_ASSERT(node_ptr == &event->node.node); +} + +/* + * Close the notify socket from the given RCU head object. This MUST be called + * through a call_rcu(). + */ +static void close_notify_sock_rcu(struct rcu_head *head) +{ + int ret; + struct ust_app_notify_sock_obj *obj = + caa_container_of(head, struct ust_app_notify_sock_obj, head); + + /* Must have a valid fd here. */ + LTTNG_ASSERT(obj->fd >= 0); + + ret = close(obj->fd); + if (ret) { + ERR("close notify sock %d RCU", obj->fd); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + + free(obj); +} + +/* + * Return the session registry according to the buffer type of the given + * session. + * + * A registry per UID object MUST exists before calling this function or else + * it LTTNG_ASSERT() if not found. RCU read side lock must be acquired. + */ +static struct ust_registry_session *get_session_registry( + struct ust_app_session *ua_sess) +{ + struct ust_registry_session *registry = NULL; + + LTTNG_ASSERT(ua_sess); + + switch (ua_sess->buffer_type) { + case LTTNG_BUFFER_PER_PID: + { + struct buffer_reg_pid *reg_pid = buffer_reg_pid_find(ua_sess->id); + if (!reg_pid) { + goto error; + } + registry = reg_pid->registry->reg.ust; + break; + } + case LTTNG_BUFFER_PER_UID: + { + struct buffer_reg_uid *reg_uid = buffer_reg_uid_find( + ua_sess->tracing_id, ua_sess->bits_per_long, + lttng_credentials_get_uid(&ua_sess->real_credentials)); + if (!reg_uid) { + goto error; + } + registry = reg_uid->registry->reg.ust; + break; + } + default: + abort(); + }; + +error: + return registry; +} + +/* + * Delete ust context safely. RCU read lock must be held before calling + * this function. + */ +static +void delete_ust_app_ctx(int sock, struct ust_app_ctx *ua_ctx, + struct ust_app *app) +{ + int ret; + + LTTNG_ASSERT(ua_ctx); + + if (ua_ctx->obj) { + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_release_object(sock, ua_ctx->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app release ctx failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app release ctx failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app release ctx obj handle %d failed with ret %d: pid = %d, sock = %d", + ua_ctx->obj->handle, ret, + app->pid, app->sock); + } + } + free(ua_ctx->obj); + } + free(ua_ctx); +} + +/* + * Delete ust app event safely. RCU read lock must be held before calling + * this function. + */ +static +void delete_ust_app_event(int sock, struct ust_app_event *ua_event, + struct ust_app *app) +{ + int ret; + + LTTNG_ASSERT(ua_event); + + free(ua_event->filter); + if (ua_event->exclusion != NULL) + free(ua_event->exclusion); + if (ua_event->obj != NULL) { + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_release_object(sock, ua_event->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app release event failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app release event failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app release event obj failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + } + free(ua_event->obj); + } + free(ua_event); +} + +/* + * Delayed reclaim of a ust_app_event_notifier_rule object. This MUST be called + * through a call_rcu(). + */ +static +void free_ust_app_event_notifier_rule_rcu(struct rcu_head *head) +{ + struct ust_app_event_notifier_rule *obj = caa_container_of( + head, struct ust_app_event_notifier_rule, rcu_head); + + free(obj); +} + +/* + * Delete ust app event notifier rule safely. + */ +static void delete_ust_app_event_notifier_rule(int sock, + struct ust_app_event_notifier_rule *ua_event_notifier_rule, + struct ust_app *app) +{ + int ret; + + LTTNG_ASSERT(ua_event_notifier_rule); + + if (ua_event_notifier_rule->exclusion != NULL) { + free(ua_event_notifier_rule->exclusion); + } + + if (ua_event_notifier_rule->obj != NULL) { + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_release_object(sock, ua_event_notifier_rule->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app release event notifier failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app release event notifier failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app release event notifier failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + } + + free(ua_event_notifier_rule->obj); + } + + lttng_trigger_put(ua_event_notifier_rule->trigger); + call_rcu(&ua_event_notifier_rule->rcu_head, + free_ust_app_event_notifier_rule_rcu); +} + +/* + * Release ust data object of the given stream. + * + * Return 0 on success or else a negative value. + */ +static int release_ust_app_stream(int sock, struct ust_app_stream *stream, + struct ust_app *app) +{ + int ret = 0; + + LTTNG_ASSERT(stream); + + if (stream->obj) { + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_release_object(sock, stream->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app release stream failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app release stream failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app release stream obj failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + } + lttng_fd_put(LTTNG_FD_APPS, 2); + free(stream->obj); + } + + return ret; +} + +/* + * Delete ust app stream safely. RCU read lock must be held before calling + * this function. + */ +static +void delete_ust_app_stream(int sock, struct ust_app_stream *stream, + struct ust_app *app) +{ + LTTNG_ASSERT(stream); + + (void) release_ust_app_stream(sock, stream, app); + free(stream); +} + +/* + * We need to execute ht_destroy outside of RCU read-side critical + * section and outside of call_rcu thread, so we postpone its execution + * using ht_cleanup_push. It is simpler than to change the semantic of + * the many callers of delete_ust_app_session(). + */ +static +void delete_ust_app_channel_rcu(struct rcu_head *head) +{ + struct ust_app_channel *ua_chan = + caa_container_of(head, struct ust_app_channel, rcu_head); + + ht_cleanup_push(ua_chan->ctx); + ht_cleanup_push(ua_chan->events); + free(ua_chan); +} + +/* + * Extract the lost packet or discarded events counter when the channel is + * being deleted and store the value in the parent channel so we can + * access it from lttng list and at stop/destroy. + * + * The session list lock must be held by the caller. + */ +static +void save_per_pid_lost_discarded_counters(struct ust_app_channel *ua_chan) +{ + uint64_t discarded = 0, lost = 0; + struct ltt_session *session; + struct ltt_ust_channel *uchan; + + if (ua_chan->attr.type != LTTNG_UST_ABI_CHAN_PER_CPU) { + return; + } + + rcu_read_lock(); + session = session_find_by_id(ua_chan->session->tracing_id); + if (!session || !session->ust_session) { + /* + * Not finding the session is not an error because there are + * multiple ways the channels can be torn down. + * + * 1) The session daemon can initiate the destruction of the + * ust app session after receiving a destroy command or + * during its shutdown/teardown. + * 2) The application, since we are in per-pid tracing, is + * unregistering and tearing down its ust app session. + * + * Both paths are protected by the session list lock which + * ensures that the accounting of lost packets and discarded + * events is done exactly once. The session is then unpublished + * from the session list, resulting in this condition. + */ + goto end; + } + + if (ua_chan->attr.overwrite) { + consumer_get_lost_packets(ua_chan->session->tracing_id, + ua_chan->key, session->ust_session->consumer, + &lost); + } else { + consumer_get_discarded_events(ua_chan->session->tracing_id, + ua_chan->key, session->ust_session->consumer, + &discarded); + } + uchan = trace_ust_find_channel_by_name( + session->ust_session->domain_global.channels, + ua_chan->name); + if (!uchan) { + ERR("Missing UST channel to store discarded counters"); + goto end; + } + + uchan->per_pid_closed_app_discarded += discarded; + uchan->per_pid_closed_app_lost += lost; + +end: + rcu_read_unlock(); + if (session) { + session_put(session); + } +} + +/* + * Delete ust app channel safely. RCU read lock must be held before calling + * this function. + * + * The session list lock must be held by the caller. + */ +static +void delete_ust_app_channel(int sock, struct ust_app_channel *ua_chan, + struct ust_app *app) +{ + int ret; + struct lttng_ht_iter iter; + struct ust_app_event *ua_event; + struct ust_app_ctx *ua_ctx; + struct ust_app_stream *stream, *stmp; + struct ust_registry_session *registry; + + LTTNG_ASSERT(ua_chan); + + DBG3("UST app deleting channel %s", ua_chan->name); + + /* Wipe stream */ + cds_list_for_each_entry_safe(stream, stmp, &ua_chan->streams.head, list) { + cds_list_del(&stream->list); + delete_ust_app_stream(sock, stream, app); + } + + /* Wipe context */ + cds_lfht_for_each_entry(ua_chan->ctx->ht, &iter.iter, ua_ctx, node.node) { + cds_list_del(&ua_ctx->list); + ret = lttng_ht_del(ua_chan->ctx, &iter); + LTTNG_ASSERT(!ret); + delete_ust_app_ctx(sock, ua_ctx, app); + } + + /* Wipe events */ + cds_lfht_for_each_entry(ua_chan->events->ht, &iter.iter, ua_event, + node.node) { + ret = lttng_ht_del(ua_chan->events, &iter); + LTTNG_ASSERT(!ret); + delete_ust_app_event(sock, ua_event, app); + } + + if (ua_chan->session->buffer_type == LTTNG_BUFFER_PER_PID) { + /* Wipe and free registry from session registry. */ + registry = get_session_registry(ua_chan->session); + if (registry) { + ust_registry_channel_del_free(registry, ua_chan->key, + sock >= 0); + } + /* + * A negative socket can be used by the caller when + * cleaning-up a ua_chan in an error path. Skip the + * accounting in this case. + */ + if (sock >= 0) { + save_per_pid_lost_discarded_counters(ua_chan); + } + } + + if (ua_chan->obj != NULL) { + /* Remove channel from application UST object descriptor. */ + iter.iter.node = &ua_chan->ust_objd_node.node; + ret = lttng_ht_del(app->ust_objd, &iter); + LTTNG_ASSERT(!ret); + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_release_object(sock, ua_chan->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app channel %s release failed. Application is dead: pid = %d, sock = %d", + ua_chan->name, app->pid, + app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app channel %s release failed. Communication time out: pid = %d, sock = %d", + ua_chan->name, app->pid, + app->sock); + } else { + ERR("UST app channel %s release failed with ret %d: pid = %d, sock = %d", + ua_chan->name, ret, app->pid, + app->sock); + } + } + lttng_fd_put(LTTNG_FD_APPS, 1); + free(ua_chan->obj); + } + call_rcu(&ua_chan->rcu_head, delete_ust_app_channel_rcu); +} + +int ust_app_register_done(struct ust_app *app) +{ + int ret; + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_register_done(app->sock); + pthread_mutex_unlock(&app->sock_lock); + return ret; +} + +int ust_app_release_object(struct ust_app *app, struct lttng_ust_abi_object_data *data) +{ + int ret, sock; + + if (app) { + pthread_mutex_lock(&app->sock_lock); + sock = app->sock; + } else { + sock = -1; + } + ret = lttng_ust_ctl_release_object(sock, data); + if (app) { + pthread_mutex_unlock(&app->sock_lock); + } + return ret; +} + +/* + * Push metadata to consumer socket. + * + * RCU read-side lock must be held to guarantee existance of socket. + * Must be called with the ust app session lock held. + * Must be called with the registry lock held. + * + * On success, return the len of metadata pushed or else a negative value. + * Returning a -EPIPE return value means we could not send the metadata, + * but it can be caused by recoverable errors (e.g. the application has + * terminated concurrently). + */ +ssize_t ust_app_push_metadata(struct ust_registry_session *registry, + struct consumer_socket *socket, int send_zero_data) +{ + int ret; + char *metadata_str = NULL; + size_t len, offset, new_metadata_len_sent; + ssize_t ret_val; + uint64_t metadata_key, metadata_version; + + LTTNG_ASSERT(registry); + LTTNG_ASSERT(socket); + + metadata_key = registry->metadata_key; + + /* + * Means that no metadata was assigned to the session. This can + * happens if no start has been done previously. + */ + if (!metadata_key) { + return 0; + } + + offset = registry->metadata_len_sent; + len = registry->metadata_len - registry->metadata_len_sent; + new_metadata_len_sent = registry->metadata_len; + metadata_version = registry->metadata_version; + if (len == 0) { + DBG3("No metadata to push for metadata key %" PRIu64, + registry->metadata_key); + ret_val = len; + if (send_zero_data) { + DBG("No metadata to push"); + goto push_data; + } + goto end; + } + + /* Allocate only what we have to send. */ + metadata_str = (char *) zmalloc(len); + if (!metadata_str) { + PERROR("zmalloc ust app metadata string"); + ret_val = -ENOMEM; + goto error; + } + /* Copy what we haven't sent out. */ + memcpy(metadata_str, registry->metadata + offset, len); + +push_data: + pthread_mutex_unlock(®istry->lock); + /* + * We need to unlock the registry while we push metadata to + * break a circular dependency between the consumerd metadata + * lock and the sessiond registry lock. Indeed, pushing metadata + * to the consumerd awaits that it gets pushed all the way to + * relayd, but doing so requires grabbing the metadata lock. If + * a concurrent metadata request is being performed by + * consumerd, this can try to grab the registry lock on the + * sessiond while holding the metadata lock on the consumer + * daemon. Those push and pull schemes are performed on two + * different bidirectionnal communication sockets. + */ + ret = consumer_push_metadata(socket, metadata_key, + metadata_str, len, offset, metadata_version); + pthread_mutex_lock(®istry->lock); + if (ret < 0) { + /* + * There is an acceptable race here between the registry + * metadata key assignment and the creation on the + * consumer. The session daemon can concurrently push + * metadata for this registry while being created on the + * consumer since the metadata key of the registry is + * assigned *before* it is setup to avoid the consumer + * to ask for metadata that could possibly be not found + * in the session daemon. + * + * The metadata will get pushed either by the session + * being stopped or the consumer requesting metadata if + * that race is triggered. + */ + if (ret == -LTTCOMM_CONSUMERD_CHANNEL_FAIL) { + ret = 0; + } else { + ERR("Error pushing metadata to consumer"); + } + ret_val = ret; + goto error_push; + } else { + /* + * Metadata may have been concurrently pushed, since + * we're not holding the registry lock while pushing to + * consumer. This is handled by the fact that we send + * the metadata content, size, and the offset at which + * that metadata belongs. This may arrive out of order + * on the consumer side, and the consumer is able to + * deal with overlapping fragments. The consumer + * supports overlapping fragments, which must be + * contiguous starting from offset 0. We keep the + * largest metadata_len_sent value of the concurrent + * send. + */ + registry->metadata_len_sent = + std::max(registry->metadata_len_sent, + new_metadata_len_sent); + } + free(metadata_str); + return len; + +end: +error: + if (ret_val) { + /* + * On error, flag the registry that the metadata is + * closed. We were unable to push anything and this + * means that either the consumer is not responding or + * the metadata cache has been destroyed on the + * consumer. + */ + registry->metadata_closed = 1; + } +error_push: + free(metadata_str); + return ret_val; +} + +/* + * For a given application and session, push metadata to consumer. + * Either sock or consumer is required : if sock is NULL, the default + * socket to send the metadata is retrieved from consumer, if sock + * is not NULL we use it to send the metadata. + * RCU read-side lock must be held while calling this function, + * therefore ensuring existance of registry. It also ensures existance + * of socket throughout this function. + * + * Return 0 on success else a negative error. + * Returning a -EPIPE return value means we could not send the metadata, + * but it can be caused by recoverable errors (e.g. the application has + * terminated concurrently). + */ +static int push_metadata(struct ust_registry_session *registry, + struct consumer_output *consumer) +{ + int ret_val; + ssize_t ret; + struct consumer_socket *socket; + + LTTNG_ASSERT(registry); + LTTNG_ASSERT(consumer); + + pthread_mutex_lock(®istry->lock); + if (registry->metadata_closed) { + ret_val = -EPIPE; + goto error; + } + + /* Get consumer socket to use to push the metadata.*/ + socket = consumer_find_socket_by_bitness(registry->bits_per_long, + consumer); + if (!socket) { + ret_val = -1; + goto error; + } + + ret = ust_app_push_metadata(registry, socket, 0); + if (ret < 0) { + ret_val = ret; + goto error; + } + pthread_mutex_unlock(®istry->lock); + return 0; + +error: + pthread_mutex_unlock(®istry->lock); + return ret_val; +} + +/* + * Send to the consumer a close metadata command for the given session. Once + * done, the metadata channel is deleted and the session metadata pointer is + * nullified. The session lock MUST be held unless the application is + * in the destroy path. + * + * Do not hold the registry lock while communicating with the consumerd, because + * doing so causes inter-process deadlocks between consumerd and sessiond with + * the metadata request notification. + * + * Return 0 on success else a negative value. + */ +static int close_metadata(struct ust_registry_session *registry, + struct consumer_output *consumer) +{ + int ret; + struct consumer_socket *socket; + uint64_t metadata_key; + bool registry_was_already_closed; + + LTTNG_ASSERT(registry); + LTTNG_ASSERT(consumer); + + rcu_read_lock(); + + pthread_mutex_lock(®istry->lock); + metadata_key = registry->metadata_key; + registry_was_already_closed = registry->metadata_closed; + if (metadata_key != 0) { + /* + * Metadata closed. Even on error this means that the consumer + * is not responding or not found so either way a second close + * should NOT be emit for this registry. + */ + registry->metadata_closed = 1; + } + pthread_mutex_unlock(®istry->lock); + + if (metadata_key == 0 || registry_was_already_closed) { + ret = 0; + goto end; + } + + /* Get consumer socket to use to push the metadata.*/ + socket = consumer_find_socket_by_bitness(registry->bits_per_long, + consumer); + if (!socket) { + ret = -1; + goto end; + } + + ret = consumer_close_metadata(socket, metadata_key); + if (ret < 0) { + goto end; + } + +end: + rcu_read_unlock(); + return ret; +} + +/* + * We need to execute ht_destroy outside of RCU read-side critical + * section and outside of call_rcu thread, so we postpone its execution + * using ht_cleanup_push. It is simpler than to change the semantic of + * the many callers of delete_ust_app_session(). + */ +static +void delete_ust_app_session_rcu(struct rcu_head *head) +{ + struct ust_app_session *ua_sess = + caa_container_of(head, struct ust_app_session, rcu_head); + + ht_cleanup_push(ua_sess->channels); + free(ua_sess); +} + +/* + * Delete ust app session safely. RCU read lock must be held before calling + * this function. + * + * The session list lock must be held by the caller. + */ +static +void delete_ust_app_session(int sock, struct ust_app_session *ua_sess, + struct ust_app *app) +{ + int ret; + struct lttng_ht_iter iter; + struct ust_app_channel *ua_chan; + struct ust_registry_session *registry; + + LTTNG_ASSERT(ua_sess); + + pthread_mutex_lock(&ua_sess->lock); + + LTTNG_ASSERT(!ua_sess->deleted); + ua_sess->deleted = true; + + registry = get_session_registry(ua_sess); + /* Registry can be null on error path during initialization. */ + if (registry) { + /* Push metadata for application before freeing the application. */ + (void) push_metadata(registry, ua_sess->consumer); + + /* + * Don't ask to close metadata for global per UID buffers. Close + * metadata only on destroy trace session in this case. Also, the + * previous push metadata could have flag the metadata registry to + * close so don't send a close command if closed. + */ + if (ua_sess->buffer_type != LTTNG_BUFFER_PER_UID) { + /* And ask to close it for this session registry. */ + (void) close_metadata(registry, ua_sess->consumer); + } + } + + cds_lfht_for_each_entry(ua_sess->channels->ht, &iter.iter, ua_chan, + node.node) { + ret = lttng_ht_del(ua_sess->channels, &iter); + LTTNG_ASSERT(!ret); + delete_ust_app_channel(sock, ua_chan, app); + } + + /* In case of per PID, the registry is kept in the session. */ + if (ua_sess->buffer_type == LTTNG_BUFFER_PER_PID) { + struct buffer_reg_pid *reg_pid = buffer_reg_pid_find(ua_sess->id); + if (reg_pid) { + /* + * Registry can be null on error path during + * initialization. + */ + buffer_reg_pid_remove(reg_pid); + buffer_reg_pid_destroy(reg_pid); + } + } + + if (ua_sess->handle != -1) { + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_release_handle(sock, ua_sess->handle); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app release session handle failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app release session handle failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app release session handle failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + } + + /* Remove session from application UST object descriptor. */ + iter.iter.node = &ua_sess->ust_objd_node.node; + ret = lttng_ht_del(app->ust_sessions_objd, &iter); + LTTNG_ASSERT(!ret); + } + + pthread_mutex_unlock(&ua_sess->lock); + + consumer_output_put(ua_sess->consumer); + + call_rcu(&ua_sess->rcu_head, delete_ust_app_session_rcu); +} + +/* + * Delete a traceable application structure from the global list. Never call + * this function outside of a call_rcu call. + * + * RCU read side lock should _NOT_ be held when calling this function. + */ +static +void delete_ust_app(struct ust_app *app) +{ + int ret, sock; + struct ust_app_session *ua_sess, *tmp_ua_sess; + struct lttng_ht_iter iter; + struct ust_app_event_notifier_rule *event_notifier_rule; + bool event_notifier_write_fd_is_open; + + /* + * The session list lock must be held during this function to guarantee + * the existence of ua_sess. + */ + session_lock_list(); + /* Delete ust app sessions info */ + sock = app->sock; + app->sock = -1; + + /* Wipe sessions */ + cds_list_for_each_entry_safe(ua_sess, tmp_ua_sess, &app->teardown_head, + teardown_node) { + /* Free every object in the session and the session. */ + rcu_read_lock(); + delete_ust_app_session(sock, ua_sess, app); + rcu_read_unlock(); + } + + /* Remove the event notifier rules associated with this app. */ + rcu_read_lock(); + cds_lfht_for_each_entry (app->token_to_event_notifier_rule_ht->ht, + &iter.iter, event_notifier_rule, node.node) { + ret = lttng_ht_del(app->token_to_event_notifier_rule_ht, &iter); + LTTNG_ASSERT(!ret); + + delete_ust_app_event_notifier_rule( + app->sock, event_notifier_rule, app); + } + + rcu_read_unlock(); + + ht_cleanup_push(app->sessions); + ht_cleanup_push(app->ust_sessions_objd); + ht_cleanup_push(app->ust_objd); + ht_cleanup_push(app->token_to_event_notifier_rule_ht); + + /* + * This could be NULL if the event notifier setup failed (e.g the app + * was killed or the tracer does not support this feature). + */ + if (app->event_notifier_group.object) { + enum lttng_error_code ret_code; + enum event_notifier_error_accounting_status status; + + const int event_notifier_read_fd = lttng_pipe_get_readfd( + app->event_notifier_group.event_pipe); + + ret_code = notification_thread_command_remove_tracer_event_source( + the_notification_thread_handle, + event_notifier_read_fd); + if (ret_code != LTTNG_OK) { + ERR("Failed to remove application tracer event source from notification thread"); + } + + status = event_notifier_error_accounting_unregister_app(app); + if (status != EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK) { + ERR("Error unregistering app from event notifier error accounting"); + } + + lttng_ust_ctl_release_object(sock, app->event_notifier_group.object); + free(app->event_notifier_group.object); + } + + event_notifier_write_fd_is_open = lttng_pipe_is_write_open( + app->event_notifier_group.event_pipe); + lttng_pipe_destroy(app->event_notifier_group.event_pipe); + /* + * Release the file descriptors reserved for the event notifier pipe. + * The app could be destroyed before the write end of the pipe could be + * passed to the application (and closed). In that case, both file + * descriptors must be released. + */ + lttng_fd_put(LTTNG_FD_APPS, event_notifier_write_fd_is_open ? 2 : 1); + + /* + * Wait until we have deleted the application from the sock hash table + * before closing this socket, otherwise an application could re-use the + * socket ID and race with the teardown, using the same hash table entry. + * + * It's OK to leave the close in call_rcu. We want it to stay unique for + * all RCU readers that could run concurrently with unregister app, + * therefore we _need_ to only close that socket after a grace period. So + * it should stay in this RCU callback. + * + * This close() is a very important step of the synchronization model so + * every modification to this function must be carefully reviewed. + */ + ret = close(sock); + if (ret) { + PERROR("close"); + } + lttng_fd_put(LTTNG_FD_APPS, 1); + + DBG2("UST app pid %d deleted", app->pid); + free(app); + session_unlock_list(); +} + +/* + * URCU intermediate call to delete an UST app. + */ +static +void delete_ust_app_rcu(struct rcu_head *head) +{ + struct lttng_ht_node_ulong *node = + caa_container_of(head, struct lttng_ht_node_ulong, head); + struct ust_app *app = + caa_container_of(node, struct ust_app, pid_n); + + DBG3("Call RCU deleting app PID %d", app->pid); + delete_ust_app(app); +} + +/* + * Delete the session from the application ht and delete the data structure by + * freeing every object inside and releasing them. + * + * The session list lock must be held by the caller. + */ +static void destroy_app_session(struct ust_app *app, + struct ust_app_session *ua_sess) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(ua_sess); + + iter.iter.node = &ua_sess->node.node; + ret = lttng_ht_del(app->sessions, &iter); + if (ret) { + /* Already scheduled for teardown. */ + goto end; + } + + /* Once deleted, free the data structure. */ + delete_ust_app_session(app->sock, ua_sess, app); + +end: + return; +} + +/* + * Alloc new UST app session. + */ +static +struct ust_app_session *alloc_ust_app_session(void) +{ + struct ust_app_session *ua_sess; + + /* Init most of the default value by allocating and zeroing */ + ua_sess = (ust_app_session *) zmalloc(sizeof(struct ust_app_session)); + if (ua_sess == NULL) { + PERROR("malloc"); + goto error_free; + } + + ua_sess->handle = -1; + ua_sess->channels = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); + ua_sess->metadata_attr.type = LTTNG_UST_ABI_CHAN_METADATA; + pthread_mutex_init(&ua_sess->lock, NULL); + + return ua_sess; + +error_free: + return NULL; +} + +/* + * Alloc new UST app channel. + */ +static +struct ust_app_channel *alloc_ust_app_channel(const char *name, + struct ust_app_session *ua_sess, + struct lttng_ust_abi_channel_attr *attr) +{ + struct ust_app_channel *ua_chan; + + /* Init most of the default value by allocating and zeroing */ + ua_chan = (ust_app_channel *) zmalloc(sizeof(struct ust_app_channel)); + if (ua_chan == NULL) { + PERROR("malloc"); + goto error; + } + + /* Setup channel name */ + strncpy(ua_chan->name, name, sizeof(ua_chan->name)); + ua_chan->name[sizeof(ua_chan->name) - 1] = '\0'; + + ua_chan->enabled = 1; + ua_chan->handle = -1; + ua_chan->session = ua_sess; + ua_chan->key = get_next_channel_key(); + ua_chan->ctx = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); + ua_chan->events = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); + lttng_ht_node_init_str(&ua_chan->node, ua_chan->name); + + CDS_INIT_LIST_HEAD(&ua_chan->streams.head); + CDS_INIT_LIST_HEAD(&ua_chan->ctx_list); + + /* Copy attributes */ + if (attr) { + /* Translate from lttng_ust_channel to lttng_ust_ctl_consumer_channel_attr. */ + ua_chan->attr.subbuf_size = attr->subbuf_size; + ua_chan->attr.num_subbuf = attr->num_subbuf; + ua_chan->attr.overwrite = attr->overwrite; + ua_chan->attr.switch_timer_interval = attr->switch_timer_interval; + ua_chan->attr.read_timer_interval = attr->read_timer_interval; + ua_chan->attr.output = (lttng_ust_abi_output) attr->output; + ua_chan->attr.blocking_timeout = attr->u.s.blocking_timeout; + } + /* By default, the channel is a per cpu channel. */ + ua_chan->attr.type = LTTNG_UST_ABI_CHAN_PER_CPU; + + DBG3("UST app channel %s allocated", ua_chan->name); + + return ua_chan; + +error: + return NULL; +} + +/* + * Allocate and initialize a UST app stream. + * + * Return newly allocated stream pointer or NULL on error. + */ +struct ust_app_stream *ust_app_alloc_stream(void) +{ + struct ust_app_stream *stream = NULL; + + stream = (ust_app_stream *) zmalloc(sizeof(*stream)); + if (stream == NULL) { + PERROR("zmalloc ust app stream"); + goto error; + } + + /* Zero could be a valid value for a handle so flag it to -1. */ + stream->handle = -1; + +error: + return stream; +} + +/* + * Alloc new UST app event. + */ +static +struct ust_app_event *alloc_ust_app_event(char *name, + struct lttng_ust_abi_event *attr) +{ + struct ust_app_event *ua_event; + + /* Init most of the default value by allocating and zeroing */ + ua_event = (ust_app_event *) zmalloc(sizeof(struct ust_app_event)); + if (ua_event == NULL) { + PERROR("Failed to allocate ust_app_event structure"); + goto error; + } + + ua_event->enabled = 1; + strncpy(ua_event->name, name, sizeof(ua_event->name)); + ua_event->name[sizeof(ua_event->name) - 1] = '\0'; + lttng_ht_node_init_str(&ua_event->node, ua_event->name); + + /* Copy attributes */ + if (attr) { + memcpy(&ua_event->attr, attr, sizeof(ua_event->attr)); + } + + DBG3("UST app event %s allocated", ua_event->name); + + return ua_event; + +error: + return NULL; +} + +/* + * Allocate a new UST app event notifier rule. + */ +static struct ust_app_event_notifier_rule *alloc_ust_app_event_notifier_rule( + struct lttng_trigger *trigger) +{ + enum lttng_event_rule_generate_exclusions_status + generate_exclusion_status; + enum lttng_condition_status cond_status; + struct ust_app_event_notifier_rule *ua_event_notifier_rule; + struct lttng_condition *condition = NULL; + const struct lttng_event_rule *event_rule = NULL; + + ua_event_notifier_rule = (ust_app_event_notifier_rule *) zmalloc(sizeof(struct ust_app_event_notifier_rule)); + if (ua_event_notifier_rule == NULL) { + PERROR("Failed to allocate ust_app_event_notifier_rule structure"); + goto error; + } + + ua_event_notifier_rule->enabled = 1; + ua_event_notifier_rule->token = lttng_trigger_get_tracer_token(trigger); + lttng_ht_node_init_u64(&ua_event_notifier_rule->node, + ua_event_notifier_rule->token); + + condition = lttng_trigger_get_condition(trigger); + LTTNG_ASSERT(condition); + LTTNG_ASSERT(lttng_condition_get_type(condition) == + LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); + + cond_status = lttng_condition_event_rule_matches_get_rule( + condition, &event_rule); + LTTNG_ASSERT(cond_status == LTTNG_CONDITION_STATUS_OK); + LTTNG_ASSERT(event_rule); + + ua_event_notifier_rule->error_counter_index = + lttng_condition_event_rule_matches_get_error_counter_index(condition); + /* Acquire the event notifier's reference to the trigger. */ + lttng_trigger_get(trigger); + + ua_event_notifier_rule->trigger = trigger; + ua_event_notifier_rule->filter = lttng_event_rule_get_filter_bytecode(event_rule); + generate_exclusion_status = lttng_event_rule_generate_exclusions( + event_rule, &ua_event_notifier_rule->exclusion); + switch (generate_exclusion_status) { + case LTTNG_EVENT_RULE_GENERATE_EXCLUSIONS_STATUS_OK: + case LTTNG_EVENT_RULE_GENERATE_EXCLUSIONS_STATUS_NONE: + break; + default: + /* Error occurred. */ + ERR("Failed to generate exclusions from trigger while allocating an event notifier rule"); + goto error_put_trigger; + } + + DBG3("UST app event notifier rule allocated: token = %" PRIu64, + ua_event_notifier_rule->token); + + return ua_event_notifier_rule; + +error_put_trigger: + lttng_trigger_put(trigger); +error: + free(ua_event_notifier_rule); + return NULL; +} + +/* + * Alloc new UST app context. + */ +static +struct ust_app_ctx *alloc_ust_app_ctx(struct lttng_ust_context_attr *uctx) +{ + struct ust_app_ctx *ua_ctx; + + ua_ctx = (ust_app_ctx *) zmalloc(sizeof(struct ust_app_ctx)); + if (ua_ctx == NULL) { + goto error; + } + + CDS_INIT_LIST_HEAD(&ua_ctx->list); + + if (uctx) { + memcpy(&ua_ctx->ctx, uctx, sizeof(ua_ctx->ctx)); + if (uctx->ctx == LTTNG_UST_ABI_CONTEXT_APP_CONTEXT) { + char *provider_name = NULL, *ctx_name = NULL; + + provider_name = strdup(uctx->u.app_ctx.provider_name); + ctx_name = strdup(uctx->u.app_ctx.ctx_name); + if (!provider_name || !ctx_name) { + free(provider_name); + free(ctx_name); + goto error; + } + + ua_ctx->ctx.u.app_ctx.provider_name = provider_name; + ua_ctx->ctx.u.app_ctx.ctx_name = ctx_name; + } + } + + DBG3("UST app context %d allocated", ua_ctx->ctx.ctx); + return ua_ctx; +error: + free(ua_ctx); + return NULL; +} + +/* + * Create a liblttng-ust filter bytecode from given bytecode. + * + * Return allocated filter or NULL on error. + */ +static struct lttng_ust_abi_filter_bytecode *create_ust_filter_bytecode_from_bytecode( + const struct lttng_bytecode *orig_f) +{ + struct lttng_ust_abi_filter_bytecode *filter = NULL; + + /* Copy filter bytecode. */ + filter = (lttng_ust_abi_filter_bytecode *) zmalloc(sizeof(*filter) + orig_f->len); + if (!filter) { + PERROR("Failed to allocate lttng_ust_filter_bytecode: bytecode len = %" PRIu32 " bytes", orig_f->len); + goto error; + } + + LTTNG_ASSERT(sizeof(struct lttng_bytecode) == + sizeof(struct lttng_ust_abi_filter_bytecode)); + memcpy(filter, orig_f, sizeof(*filter) + orig_f->len); +error: + return filter; +} + +/* + * Create a liblttng-ust capture bytecode from given bytecode. + * + * Return allocated filter or NULL on error. + */ +static struct lttng_ust_abi_capture_bytecode * +create_ust_capture_bytecode_from_bytecode(const struct lttng_bytecode *orig_f) +{ + struct lttng_ust_abi_capture_bytecode *capture = NULL; + + /* Copy capture bytecode. */ + capture = (lttng_ust_abi_capture_bytecode *) zmalloc(sizeof(*capture) + orig_f->len); + if (!capture) { + PERROR("Failed to allocate lttng_ust_abi_capture_bytecode: bytecode len = %" PRIu32 " bytes", orig_f->len); + goto error; + } + + LTTNG_ASSERT(sizeof(struct lttng_bytecode) == + sizeof(struct lttng_ust_abi_capture_bytecode)); + memcpy(capture, orig_f, sizeof(*capture) + orig_f->len); +error: + return capture; +} + +/* + * Find an ust_app using the sock and return it. RCU read side lock must be + * held before calling this helper function. + */ +struct ust_app *ust_app_find_by_sock(int sock) +{ + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter iter; + + lttng_ht_lookup(ust_app_ht_by_sock, (void *)((unsigned long) sock), &iter); + node = lttng_ht_iter_get_node_ulong(&iter); + if (node == NULL) { + DBG2("UST app find by sock %d not found", sock); + goto error; + } + + return caa_container_of(node, struct ust_app, sock_n); + +error: + return NULL; +} + +/* + * Find an ust_app using the notify sock and return it. RCU read side lock must + * be held before calling this helper function. + */ +static struct ust_app *find_app_by_notify_sock(int sock) +{ + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter iter; + + lttng_ht_lookup(ust_app_ht_by_notify_sock, (void *)((unsigned long) sock), + &iter); + node = lttng_ht_iter_get_node_ulong(&iter); + if (node == NULL) { + DBG2("UST app find by notify sock %d not found", sock); + goto error; + } + + return caa_container_of(node, struct ust_app, notify_sock_n); + +error: + return NULL; +} + +/* + * Lookup for an ust app event based on event name, filter bytecode and the + * event loglevel. + * + * Return an ust_app_event object or NULL on error. + */ +static struct ust_app_event *find_ust_app_event(struct lttng_ht *ht, + const char *name, const struct lttng_bytecode *filter, + int loglevel_value, + const struct lttng_event_exclusion *exclusion) +{ + struct lttng_ht_iter iter; + struct lttng_ht_node_str *node; + struct ust_app_event *event = NULL; + struct ust_app_ht_key key; + + LTTNG_ASSERT(name); + LTTNG_ASSERT(ht); + + /* Setup key for event lookup. */ + key.name = name; + key.filter = filter; + key.loglevel_type = (lttng_ust_abi_loglevel_type) loglevel_value; + /* lttng_event_exclusion and lttng_ust_event_exclusion structures are similar */ + key.exclusion = exclusion; + + /* Lookup using the event name as hash and a custom match fct. */ + cds_lfht_lookup(ht->ht, ht->hash_fct((void *) name, lttng_ht_seed), + ht_match_ust_app_event, &key, &iter.iter); + node = lttng_ht_iter_get_node_str(&iter); + if (node == NULL) { + goto end; + } + + event = caa_container_of(node, struct ust_app_event, node); + +end: + return event; +} + +/* + * Look-up an event notifier rule based on its token id. + * + * Must be called with the RCU read lock held. + * Return an ust_app_event_notifier_rule object or NULL on error. + */ +static struct ust_app_event_notifier_rule *find_ust_app_event_notifier_rule( + struct lttng_ht *ht, uint64_t token) +{ + struct lttng_ht_iter iter; + struct lttng_ht_node_u64 *node; + struct ust_app_event_notifier_rule *event_notifier_rule = NULL; + + LTTNG_ASSERT(ht); + + lttng_ht_lookup(ht, &token, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (node == NULL) { + DBG2("UST app event notifier rule token not found: token = %" PRIu64, + token); + goto end; + } + + event_notifier_rule = caa_container_of( + node, struct ust_app_event_notifier_rule, node); +end: + return event_notifier_rule; +} + +/* + * Create the channel context on the tracer. + * + * Called with UST app session lock held. + */ +static +int create_ust_channel_context(struct ust_app_channel *ua_chan, + struct ust_app_ctx *ua_ctx, struct ust_app *app) +{ + int ret; + + health_code_update(); + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_add_context(app->sock, &ua_ctx->ctx, + ua_chan->obj, &ua_ctx->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app create channel context failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app create channel context failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app create channel context failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + goto error; + } + + ua_ctx->handle = ua_ctx->obj->handle; + + DBG2("UST app context handle %d created successfully for channel %s", + ua_ctx->handle, ua_chan->name); + +error: + health_code_update(); + return ret; +} + +/* + * Set the filter on the tracer. + */ +static int set_ust_object_filter(struct ust_app *app, + const struct lttng_bytecode *bytecode, + struct lttng_ust_abi_object_data *ust_object) +{ + int ret; + struct lttng_ust_abi_filter_bytecode *ust_bytecode = NULL; + + health_code_update(); + + ust_bytecode = create_ust_filter_bytecode_from_bytecode(bytecode); + if (!ust_bytecode) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_set_filter(app->sock, ust_bytecode, + ust_object); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app set filter failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app set filter failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app set filter failed with ret %d: pid = %d, sock = %d, object = %p", + ret, app->pid, app->sock, ust_object); + } + goto error; + } + + DBG2("UST filter successfully set: object = %p", ust_object); + +error: + health_code_update(); + free(ust_bytecode); + return ret; +} + +/* + * Set a capture bytecode for the passed object. + * The sequence number enforces the ordering at runtime and on reception of + * the captured payloads. + */ +static int set_ust_capture(struct ust_app *app, + const struct lttng_bytecode *bytecode, + unsigned int capture_seqnum, + struct lttng_ust_abi_object_data *ust_object) +{ + int ret; + struct lttng_ust_abi_capture_bytecode *ust_bytecode = NULL; + + health_code_update(); + + ust_bytecode = create_ust_capture_bytecode_from_bytecode(bytecode); + if (!ust_bytecode) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + + /* + * Set the sequence number to ensure the capture of fields is ordered. + */ + ust_bytecode->seqnum = capture_seqnum; + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_set_capture(app->sock, ust_bytecode, + ust_object); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app set capture failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + DBG3("UST app set capture failed. Communication timeout: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app event set capture failed with ret %d: pid = %d, sock = %d", + ret, app->pid, + app->sock); + } + + goto error; + } + + DBG2("UST capture successfully set: object = %p", ust_object); + +error: + health_code_update(); + free(ust_bytecode); + return ret; +} + +static +struct lttng_ust_abi_event_exclusion *create_ust_exclusion_from_exclusion( + const struct lttng_event_exclusion *exclusion) +{ + struct lttng_ust_abi_event_exclusion *ust_exclusion = NULL; + size_t exclusion_alloc_size = sizeof(struct lttng_ust_abi_event_exclusion) + + LTTNG_UST_ABI_SYM_NAME_LEN * exclusion->count; + + ust_exclusion = (lttng_ust_abi_event_exclusion *) zmalloc(exclusion_alloc_size); + if (!ust_exclusion) { + PERROR("malloc"); + goto end; + } + + LTTNG_ASSERT(sizeof(struct lttng_event_exclusion) == + sizeof(struct lttng_ust_abi_event_exclusion)); + memcpy(ust_exclusion, exclusion, exclusion_alloc_size); +end: + return ust_exclusion; +} + +/* + * Set event exclusions on the tracer. + */ +static int set_ust_object_exclusions(struct ust_app *app, + const struct lttng_event_exclusion *exclusions, + struct lttng_ust_abi_object_data *ust_object) +{ + int ret; + struct lttng_ust_abi_event_exclusion *ust_exclusions = NULL; + + LTTNG_ASSERT(exclusions && exclusions->count > 0); + + health_code_update(); + + ust_exclusions = create_ust_exclusion_from_exclusion( + exclusions); + if (!ust_exclusions) { + ret = -LTTNG_ERR_NOMEM; + goto error; + } + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_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) { + ret = 0; + DBG3("UST app event exclusion failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app event exclusion failed. Communication time out(pid: %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app event exclusions failed with ret %d: pid = %d, sock = %d, object = %p", + ret, app->pid, app->sock, ust_object); + } + goto error; + } + + DBG2("UST exclusions set successfully for object %p", ust_object); + +error: + health_code_update(); + free(ust_exclusions); + return ret; +} + +/* + * Disable the specified event on to UST tracer for the UST session. + */ +static int disable_ust_object(struct ust_app *app, + struct lttng_ust_abi_object_data *object) +{ + int ret; + + health_code_update(); + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_disable(app->sock, object); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app disable object failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app disable object failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app disable object failed with ret %d: pid = %d, sock = %d, object = %p", + ret, app->pid, app->sock, object); + } + goto error; + } + + DBG2("UST app object %p disabled successfully for app: pid = %d", + object, app->pid); + +error: + health_code_update(); + return ret; +} + +/* + * Disable the specified channel on to UST tracer for the UST session. + */ +static int disable_ust_channel(struct ust_app *app, + struct ust_app_session *ua_sess, struct ust_app_channel *ua_chan) +{ + int ret; + + health_code_update(); + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_disable(app->sock, ua_chan->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app disable channel failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app disable channel failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app channel %s disable failed, session handle %d, with ret %d: pid = %d, sock = %d", + ua_chan->name, ua_sess->handle, ret, + app->pid, app->sock); + } + goto error; + } + + DBG2("UST app channel %s disabled successfully for app: pid = %d", + ua_chan->name, app->pid); + +error: + health_code_update(); + return ret; +} + +/* + * Enable the specified channel on to UST tracer for the UST session. + */ +static int enable_ust_channel(struct ust_app *app, + struct ust_app_session *ua_sess, struct ust_app_channel *ua_chan) +{ + int ret; + + health_code_update(); + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_enable(app->sock, ua_chan->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app channel %s enable failed. Application is dead: pid = %d, sock = %d", + ua_chan->name, app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app channel %s enable failed. Communication time out: pid = %d, sock = %d", + ua_chan->name, app->pid, app->sock); + } else { + ERR("UST app channel %s enable failed, session handle %d, with ret %d: pid = %d, sock = %d", + ua_chan->name, ua_sess->handle, ret, + app->pid, app->sock); + } + goto error; + } + + ua_chan->enabled = 1; + + DBG2("UST app channel %s enabled successfully for app: pid = %d", + ua_chan->name, app->pid); + +error: + health_code_update(); + return ret; +} + +/* + * Enable the specified event on to UST tracer for the UST session. + */ +static int enable_ust_object( + struct ust_app *app, struct lttng_ust_abi_object_data *ust_object) +{ + int ret; + + health_code_update(); + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_enable(app->sock, ust_object); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app enable object failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app enable object failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app enable object failed with ret %d: pid = %d, sock = %d, object = %p", + ret, app->pid, app->sock, ust_object); + } + goto error; + } + + DBG2("UST app object %p enabled successfully for app: pid = %d", + ust_object, app->pid); + +error: + health_code_update(); + return ret; +} + +/* + * Send channel and stream buffer to application. + * + * Return 0 on success. On error, a negative value is returned. + */ +static int send_channel_pid_to_ust(struct ust_app *app, + struct ust_app_session *ua_sess, struct ust_app_channel *ua_chan) +{ + int ret; + struct ust_app_stream *stream, *stmp; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(ua_chan); + + health_code_update(); + + DBG("UST app sending channel %s to UST app sock %d", ua_chan->name, + app->sock); + + /* Send channel to the application. */ + ret = ust_consumer_send_channel_to_ust(app, ua_sess, ua_chan); + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = -ENOTCONN; /* Caused by app exiting. */ + goto error; + } else if (ret == -EAGAIN) { + /* Caused by timeout. */ + WARN("Communication with application %d timed out on send_channel for channel \"%s\" of session \"%" PRIu64 "\".", + app->pid, ua_chan->name, ua_sess->tracing_id); + /* Treat this the same way as an application that is exiting. */ + ret = -ENOTCONN; + goto error; + } else if (ret < 0) { + goto error; + } + + health_code_update(); + + /* Send all streams to application. */ + cds_list_for_each_entry_safe(stream, stmp, &ua_chan->streams.head, list) { + ret = ust_consumer_send_stream_to_ust(app, ua_chan, stream); + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = -ENOTCONN; /* Caused by app exiting. */ + goto error; + } else if (ret == -EAGAIN) { + /* Caused by timeout. */ + WARN("Communication with application %d timed out on send_stream for stream \"%s\" of channel \"%s\" of session \"%" PRIu64 "\".", + app->pid, stream->name, ua_chan->name, + ua_sess->tracing_id); + /* + * Treat this the same way as an application that is + * exiting. + */ + ret = -ENOTCONN; + } else if (ret < 0) { + goto error; + } + /* We don't need the stream anymore once sent to the tracer. */ + cds_list_del(&stream->list); + delete_ust_app_stream(-1, stream, app); + } + /* Flag the channel that it is sent to the application. */ + ua_chan->is_sent = 1; + +error: + health_code_update(); + return ret; +} + +/* + * Create the specified event onto the UST tracer for a UST session. + * + * Should be called with session mutex held. + */ +static +int create_ust_event(struct ust_app *app, struct ust_app_session *ua_sess, + struct ust_app_channel *ua_chan, struct ust_app_event *ua_event) +{ + int ret = 0; + + health_code_update(); + + /* Create UST event on tracer */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_create_event(app->sock, &ua_event->attr, ua_chan->obj, + &ua_event->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app create event failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app create event failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app create event '%s' failed with ret %d: pid = %d, sock = %d", + ua_event->attr.name, ret, app->pid, + app->sock); + } + goto error; + } + + ua_event->handle = ua_event->obj->handle; + + 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_object_filter(app, ua_event->filter, ua_event->obj); + if (ret < 0) { + goto error; + } + } + + /* Set exclusions for the event */ + if (ua_event->exclusion) { + ret = set_ust_object_exclusions(app, ua_event->exclusion, ua_event->obj); + if (ret < 0) { + goto error; + } + } + + /* If event not enabled, disable it on the tracer */ + if (ua_event->enabled) { + /* + * We now need to explicitly enable the event, since it + * is now disabled at creation. + */ + ret = enable_ust_object(app, ua_event->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 */ + abort(); + case -LTTNG_UST_ERR_EXIST: + /* It's OK for our use case. */ + ret = 0; + break; + default: + break; + } + goto error; + } + } + +error: + health_code_update(); + return ret; +} + +static int init_ust_event_notifier_from_event_rule( + const struct lttng_event_rule *rule, + struct lttng_ust_abi_event_notifier *event_notifier) +{ + enum lttng_event_rule_status status; + enum lttng_ust_abi_loglevel_type ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_ALL; + int loglevel = -1, ret = 0; + const char *pattern; + + + memset(event_notifier, 0, sizeof(*event_notifier)); + + if (lttng_event_rule_targets_agent_domain(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_ABI_LOGLEVEL_ALL; + } else { + const struct lttng_log_level_rule *log_level_rule; + + LTTNG_ASSERT(lttng_event_rule_get_type(rule) == + LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT); + + status = lttng_event_rule_user_tracepoint_get_name_pattern(rule, &pattern); + if (status != LTTNG_EVENT_RULE_STATUS_OK) { + /* At this point, this is a fatal error. */ + abort(); + } + + status = lttng_event_rule_user_tracepoint_get_log_level_rule( + rule, &log_level_rule); + if (status == LTTNG_EVENT_RULE_STATUS_UNSET) { + ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_ALL; + } else if (status == LTTNG_EVENT_RULE_STATUS_OK) { + enum lttng_log_level_rule_status llr_status; + + switch (lttng_log_level_rule_get_type(log_level_rule)) { + case LTTNG_LOG_LEVEL_RULE_TYPE_EXACTLY: + ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_SINGLE; + llr_status = lttng_log_level_rule_exactly_get_level( + log_level_rule, &loglevel); + break; + case LTTNG_LOG_LEVEL_RULE_TYPE_AT_LEAST_AS_SEVERE_AS: + ust_loglevel_type = LTTNG_UST_ABI_LOGLEVEL_RANGE; + llr_status = lttng_log_level_rule_at_least_as_severe_as_get_level( + log_level_rule, &loglevel); + break; + default: + abort(); + } + + LTTNG_ASSERT(llr_status == LTTNG_LOG_LEVEL_RULE_STATUS_OK); + } else { + /* At this point this is a fatal error. */ + abort(); + } + } + + event_notifier->event.instrumentation = LTTNG_UST_ABI_TRACEPOINT; + ret = lttng_strncpy(event_notifier->event.name, pattern, + LTTNG_UST_ABI_SYM_NAME_LEN - 1); + if (ret) { + ERR("Failed to copy event rule pattern to notifier: pattern = '%s' ", + pattern); + goto end; + } + + event_notifier->event.loglevel_type = ust_loglevel_type; + event_notifier->event.loglevel = loglevel; +end: + return ret; +} + +/* + * Create the specified event notifier against the user space tracer of a + * given application. + */ +static int create_ust_event_notifier(struct ust_app *app, + struct ust_app_event_notifier_rule *ua_event_notifier_rule) +{ + int ret = 0; + enum lttng_condition_status condition_status; + const struct lttng_condition *condition = NULL; + struct lttng_ust_abi_event_notifier event_notifier; + const struct lttng_event_rule *event_rule = NULL; + unsigned int capture_bytecode_count = 0, i; + enum lttng_condition_status cond_status; + enum lttng_event_rule_type event_rule_type; + + health_code_update(); + LTTNG_ASSERT(app->event_notifier_group.object); + + condition = lttng_trigger_get_const_condition( + ua_event_notifier_rule->trigger); + LTTNG_ASSERT(condition); + LTTNG_ASSERT(lttng_condition_get_type(condition) == + LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES); + + condition_status = lttng_condition_event_rule_matches_get_rule( + condition, &event_rule); + LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); + + LTTNG_ASSERT(event_rule); + + event_rule_type = lttng_event_rule_get_type(event_rule); + LTTNG_ASSERT(event_rule_type == LTTNG_EVENT_RULE_TYPE_USER_TRACEPOINT || + event_rule_type == LTTNG_EVENT_RULE_TYPE_JUL_LOGGING || + event_rule_type == + LTTNG_EVENT_RULE_TYPE_LOG4J_LOGGING || + event_rule_type == + LTTNG_EVENT_RULE_TYPE_PYTHON_LOGGING); + + init_ust_event_notifier_from_event_rule(event_rule, &event_notifier); + event_notifier.event.token = ua_event_notifier_rule->token; + event_notifier.error_counter_index = ua_event_notifier_rule->error_counter_index; + + /* Create UST event notifier against the tracer. */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_create_event_notifier(app->sock, &event_notifier, + app->event_notifier_group.object, + &ua_event_notifier_rule->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app create event notifier failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app create event notifier failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app create event notifier '%s' failed with ret %d: pid = %d, sock = %d", + event_notifier.event.name, ret, app->pid, + app->sock); + } + goto error; + } + + ua_event_notifier_rule->handle = ua_event_notifier_rule->obj->handle; + + DBG2("UST app event notifier %s created successfully: app = '%s': pid = %d), object = %p", + event_notifier.event.name, app->name, app->pid, + ua_event_notifier_rule->obj); + + health_code_update(); + + /* Set filter if one is present. */ + if (ua_event_notifier_rule->filter) { + ret = set_ust_object_filter(app, ua_event_notifier_rule->filter, + ua_event_notifier_rule->obj); + if (ret < 0) { + goto error; + } + } + + /* Set exclusions for the event. */ + if (ua_event_notifier_rule->exclusion) { + ret = set_ust_object_exclusions(app, + ua_event_notifier_rule->exclusion, + ua_event_notifier_rule->obj); + if (ret < 0) { + goto error; + } + } + + /* Set the capture bytecodes. */ + cond_status = lttng_condition_event_rule_matches_get_capture_descriptor_count( + condition, &capture_bytecode_count); + LTTNG_ASSERT(cond_status == LTTNG_CONDITION_STATUS_OK); + + for (i = 0; i < capture_bytecode_count; i++) { + const struct lttng_bytecode *capture_bytecode = + lttng_condition_event_rule_matches_get_capture_bytecode_at_index( + condition, i); + + ret = set_ust_capture(app, capture_bytecode, i, + ua_event_notifier_rule->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_event_notifier_rule->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. */ + abort(); + case -LTTNG_UST_ERR_EXIST: + /* It's OK for our use case. */ + ret = 0; + break; + default: + break; + } + + goto error; + } + + ua_event_notifier_rule->enabled = true; + +error: + health_code_update(); + return ret; +} + +/* + * Copy data between an UST app event and a LTT event. + */ +static void shadow_copy_event(struct ust_app_event *ua_event, + struct ltt_ust_event *uevent) +{ + size_t exclusion_alloc_size; + + strncpy(ua_event->name, uevent->attr.name, sizeof(ua_event->name)); + ua_event->name[sizeof(ua_event->name) - 1] = '\0'; + + ua_event->enabled = uevent->enabled; + + /* Copy event attributes */ + memcpy(&ua_event->attr, &uevent->attr, sizeof(ua_event->attr)); + + /* Copy filter bytecode */ + if (uevent->filter) { + ua_event->filter = lttng_bytecode_copy(uevent->filter); + /* Filter might be NULL here in case of ENONEM. */ + } + + /* Copy exclusion data */ + if (uevent->exclusion) { + exclusion_alloc_size = sizeof(struct lttng_event_exclusion) + + LTTNG_UST_ABI_SYM_NAME_LEN * uevent->exclusion->count; + ua_event->exclusion = (lttng_event_exclusion *) zmalloc(exclusion_alloc_size); + if (ua_event->exclusion == NULL) { + PERROR("malloc"); + } else { + memcpy(ua_event->exclusion, uevent->exclusion, + exclusion_alloc_size); + } + } +} + +/* + * Copy data between an UST app channel and a LTT channel. + */ +static void shadow_copy_channel(struct ust_app_channel *ua_chan, + struct ltt_ust_channel *uchan) +{ + DBG2("UST app shadow copy of channel %s started", ua_chan->name); + + strncpy(ua_chan->name, uchan->name, sizeof(ua_chan->name)); + ua_chan->name[sizeof(ua_chan->name) - 1] = '\0'; + + ua_chan->tracefile_size = uchan->tracefile_size; + ua_chan->tracefile_count = uchan->tracefile_count; + + /* Copy event attributes since the layout is different. */ + ua_chan->attr.subbuf_size = uchan->attr.subbuf_size; + ua_chan->attr.num_subbuf = uchan->attr.num_subbuf; + ua_chan->attr.overwrite = uchan->attr.overwrite; + ua_chan->attr.switch_timer_interval = uchan->attr.switch_timer_interval; + ua_chan->attr.read_timer_interval = uchan->attr.read_timer_interval; + ua_chan->monitor_timer_interval = uchan->monitor_timer_interval; + ua_chan->attr.output = (lttng_ust_abi_output) uchan->attr.output; + ua_chan->attr.blocking_timeout = uchan->attr.u.s.blocking_timeout; + + /* + * Note that the attribute channel type is not set since the channel on the + * tracing registry side does not have this information. + */ + + ua_chan->enabled = uchan->enabled; + ua_chan->tracing_channel_id = uchan->id; + + DBG3("UST app shadow copy of channel %s done", ua_chan->name); +} + +/* + * Copy data between a UST app session and a regular LTT session. + */ +static void shadow_copy_session(struct ust_app_session *ua_sess, + struct ltt_ust_session *usess, struct ust_app *app) +{ + struct tm *timeinfo; + char datetime[16]; + int ret; + char tmp_shm_path[PATH_MAX]; + + timeinfo = localtime(&app->registration_time); + strftime(datetime, sizeof(datetime), "%Y%m%d-%H%M%S", timeinfo); + + DBG2("Shadow copy of session handle %d", ua_sess->handle); + + ua_sess->tracing_id = usess->id; + ua_sess->id = get_next_session_id(); + LTTNG_OPTIONAL_SET(&ua_sess->real_credentials.uid, app->uid); + LTTNG_OPTIONAL_SET(&ua_sess->real_credentials.gid, app->gid); + LTTNG_OPTIONAL_SET(&ua_sess->effective_credentials.uid, usess->uid); + LTTNG_OPTIONAL_SET(&ua_sess->effective_credentials.gid, usess->gid); + ua_sess->buffer_type = usess->buffer_type; + ua_sess->bits_per_long = app->bits_per_long; + + /* There is only one consumer object per session possible. */ + consumer_output_get(usess->consumer); + ua_sess->consumer = usess->consumer; + + ua_sess->output_traces = usess->output_traces; + ua_sess->live_timer_interval = usess->live_timer_interval; + copy_channel_attr_to_ustctl(&ua_sess->metadata_attr, + &usess->metadata_attr); + + switch (ua_sess->buffer_type) { + case LTTNG_BUFFER_PER_PID: + ret = snprintf(ua_sess->path, sizeof(ua_sess->path), + DEFAULT_UST_TRACE_PID_PATH "/%s-%d-%s", app->name, app->pid, + datetime); + break; + case LTTNG_BUFFER_PER_UID: + ret = snprintf(ua_sess->path, sizeof(ua_sess->path), + DEFAULT_UST_TRACE_UID_PATH, + lttng_credentials_get_uid(&ua_sess->real_credentials), + app->bits_per_long); + break; + default: + abort(); + goto error; + } + if (ret < 0) { + PERROR("asprintf UST shadow copy session"); + abort(); + goto error; + } + + strncpy(ua_sess->root_shm_path, usess->root_shm_path, + sizeof(ua_sess->root_shm_path)); + ua_sess->root_shm_path[sizeof(ua_sess->root_shm_path) - 1] = '\0'; + strncpy(ua_sess->shm_path, usess->shm_path, + sizeof(ua_sess->shm_path)); + ua_sess->shm_path[sizeof(ua_sess->shm_path) - 1] = '\0'; + if (ua_sess->shm_path[0]) { + switch (ua_sess->buffer_type) { + case LTTNG_BUFFER_PER_PID: + ret = snprintf(tmp_shm_path, sizeof(tmp_shm_path), + "/" DEFAULT_UST_TRACE_PID_PATH "/%s-%d-%s", + app->name, app->pid, datetime); + break; + case LTTNG_BUFFER_PER_UID: + ret = snprintf(tmp_shm_path, sizeof(tmp_shm_path), + "/" DEFAULT_UST_TRACE_UID_PATH, + app->uid, app->bits_per_long); + break; + default: + abort(); + goto error; + } + if (ret < 0) { + PERROR("sprintf UST shadow copy session"); + abort(); + goto error; + } + strncat(ua_sess->shm_path, tmp_shm_path, + sizeof(ua_sess->shm_path) - strlen(ua_sess->shm_path) - 1); + ua_sess->shm_path[sizeof(ua_sess->shm_path) - 1] = '\0'; + } + return; + +error: + consumer_output_put(ua_sess->consumer); +} + +/* + * Lookup sesison wrapper. + */ +static +void __lookup_session_by_app(const struct ltt_ust_session *usess, + struct ust_app *app, struct lttng_ht_iter *iter) +{ + /* Get right UST app session from app */ + lttng_ht_lookup(app->sessions, &usess->id, iter); +} + +/* + * Return ust app session from the app session hashtable using the UST session + * id. + */ +static struct ust_app_session *lookup_session_by_app( + const struct ltt_ust_session *usess, struct ust_app *app) +{ + struct lttng_ht_iter iter; + struct lttng_ht_node_u64 *node; + + __lookup_session_by_app(usess, app, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (node == NULL) { + goto error; + } + + return caa_container_of(node, struct ust_app_session, node); + +error: + return NULL; +} + +/* + * Setup buffer registry per PID for the given session and application. If none + * is found, a new one is created, added to the global registry and + * initialized. If regp is valid, it's set with the newly created object. + * + * Return 0 on success or else a negative value. + */ +static int setup_buffer_reg_pid(struct ust_app_session *ua_sess, + struct ust_app *app, struct buffer_reg_pid **regp) +{ + int ret = 0; + struct buffer_reg_pid *reg_pid; + + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(app); + + rcu_read_lock(); + + reg_pid = buffer_reg_pid_find(ua_sess->id); + if (!reg_pid) { + /* + * This is the create channel path meaning that if there is NO + * registry available, we have to create one for this session. + */ + ret = buffer_reg_pid_create(ua_sess->id, ®_pid, + ua_sess->root_shm_path, ua_sess->shm_path); + if (ret < 0) { + goto error; + } + } else { + goto end; + } + + /* Initialize registry. */ + ret = ust_registry_session_init(®_pid->registry->reg.ust, app, + app->bits_per_long, app->uint8_t_alignment, + app->uint16_t_alignment, app->uint32_t_alignment, + app->uint64_t_alignment, app->long_alignment, + app->byte_order, app->version.major, app->version.minor, + reg_pid->root_shm_path, reg_pid->shm_path, + lttng_credentials_get_uid(&ua_sess->effective_credentials), + lttng_credentials_get_gid(&ua_sess->effective_credentials), + ua_sess->tracing_id, + app->uid); + if (ret < 0) { + /* + * reg_pid->registry->reg.ust is NULL upon error, so we need to + * destroy the buffer registry, because it is always expected + * that if the buffer registry can be found, its ust registry is + * non-NULL. + */ + buffer_reg_pid_destroy(reg_pid); + goto error; + } + + buffer_reg_pid_add(reg_pid); + + DBG3("UST app buffer registry per PID created successfully"); + +end: + if (regp) { + *regp = reg_pid; + } +error: + rcu_read_unlock(); + return ret; +} + +/* + * Setup buffer registry per UID for the given session and application. If none + * is found, a new one is created, added to the global registry and + * initialized. If regp is valid, it's set with the newly created object. + * + * Return 0 on success or else a negative value. + */ +static int setup_buffer_reg_uid(struct ltt_ust_session *usess, + struct ust_app_session *ua_sess, + struct ust_app *app, struct buffer_reg_uid **regp) +{ + int ret = 0; + struct buffer_reg_uid *reg_uid; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(app); + + rcu_read_lock(); + + reg_uid = buffer_reg_uid_find(usess->id, app->bits_per_long, app->uid); + if (!reg_uid) { + /* + * This is the create channel path meaning that if there is NO + * registry available, we have to create one for this session. + */ + ret = buffer_reg_uid_create(usess->id, app->bits_per_long, app->uid, + LTTNG_DOMAIN_UST, ®_uid, + ua_sess->root_shm_path, ua_sess->shm_path); + if (ret < 0) { + goto error; + } + } else { + goto end; + } + + /* Initialize registry. */ + ret = ust_registry_session_init(®_uid->registry->reg.ust, NULL, + app->bits_per_long, app->uint8_t_alignment, + app->uint16_t_alignment, app->uint32_t_alignment, + app->uint64_t_alignment, app->long_alignment, + app->byte_order, app->version.major, + app->version.minor, reg_uid->root_shm_path, + reg_uid->shm_path, usess->uid, usess->gid, + ua_sess->tracing_id, app->uid); + if (ret < 0) { + /* + * reg_uid->registry->reg.ust is NULL upon error, so we need to + * destroy the buffer registry, because it is always expected + * that if the buffer registry can be found, its ust registry is + * non-NULL. + */ + buffer_reg_uid_destroy(reg_uid, NULL); + goto error; + } + /* Add node to teardown list of the session. */ + cds_list_add(®_uid->lnode, &usess->buffer_reg_uid_list); + + buffer_reg_uid_add(reg_uid); + + DBG3("UST app buffer registry per UID created successfully"); +end: + if (regp) { + *regp = reg_uid; + } +error: + rcu_read_unlock(); + return ret; +} + +/* + * Create a session on the tracer side for the given app. + * + * On success, ua_sess_ptr is populated with the session pointer or else left + * untouched. If the session was created, is_created is set to 1. On error, + * it's left untouched. Note that ua_sess_ptr is mandatory but is_created can + * be NULL. + * + * Returns 0 on success or else a negative code which is either -ENOMEM or + * -ENOTCONN which is the default code if the lttng_ust_ctl_create_session fails. + */ +static int find_or_create_ust_app_session(struct ltt_ust_session *usess, + struct ust_app *app, struct ust_app_session **ua_sess_ptr, + int *is_created) +{ + int ret, created = 0; + struct ust_app_session *ua_sess; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(app); + LTTNG_ASSERT(ua_sess_ptr); + + health_code_update(); + + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + DBG2("UST app pid: %d session id %" PRIu64 " not found, creating it", + app->pid, usess->id); + ua_sess = alloc_ust_app_session(); + if (ua_sess == NULL) { + /* Only malloc can failed so something is really wrong */ + ret = -ENOMEM; + goto error; + } + shadow_copy_session(ua_sess, usess, app); + created = 1; + } + + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_PID: + /* Init local registry. */ + ret = setup_buffer_reg_pid(ua_sess, app, NULL); + if (ret < 0) { + delete_ust_app_session(-1, ua_sess, app); + goto error; + } + break; + case LTTNG_BUFFER_PER_UID: + /* Look for a global registry. If none exists, create one. */ + ret = setup_buffer_reg_uid(usess, ua_sess, app, NULL); + if (ret < 0) { + delete_ust_app_session(-1, ua_sess, app); + goto error; + } + break; + default: + abort(); + ret = -EINVAL; + goto error; + } + + health_code_update(); + + if (ua_sess->handle == -1) { + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_create_session(app->sock); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG("UST app creating session failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + ret = 0; + } else if (ret == -EAGAIN) { + DBG("UST app creating session failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + ret = 0; + } else { + ERR("UST app creating session failed with ret %d: pid = %d, sock =%d", + ret, app->pid, app->sock); + } + delete_ust_app_session(-1, ua_sess, app); + if (ret != -ENOMEM) { + /* + * Tracer is probably gone or got an internal error so let's + * behave like it will soon unregister or not usable. + */ + ret = -ENOTCONN; + } + goto error; + } + + ua_sess->handle = ret; + + /* Add ust app session to app's HT */ + lttng_ht_node_init_u64(&ua_sess->node, + ua_sess->tracing_id); + lttng_ht_add_unique_u64(app->sessions, &ua_sess->node); + lttng_ht_node_init_ulong(&ua_sess->ust_objd_node, ua_sess->handle); + lttng_ht_add_unique_ulong(app->ust_sessions_objd, + &ua_sess->ust_objd_node); + + DBG2("UST app session created successfully with handle %d", ret); + } + + *ua_sess_ptr = ua_sess; + if (is_created) { + *is_created = created; + } + + /* Everything went well. */ + ret = 0; + +error: + health_code_update(); + return ret; +} + +/* + * Match function for a hash table lookup of ust_app_ctx. + * + * It matches an ust app context based on the context type and, in the case + * of perf counters, their name. + */ +static int ht_match_ust_app_ctx(struct cds_lfht_node *node, const void *_key) +{ + struct ust_app_ctx *ctx; + const struct lttng_ust_context_attr *key; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + ctx = caa_container_of(node, struct ust_app_ctx, node.node); + key = (lttng_ust_context_attr *) _key; + + /* Context type */ + if (ctx->ctx.ctx != key->ctx) { + goto no_match; + } + + switch(key->ctx) { + case LTTNG_UST_ABI_CONTEXT_PERF_THREAD_COUNTER: + if (strncmp(key->u.perf_counter.name, + ctx->ctx.u.perf_counter.name, + sizeof(key->u.perf_counter.name))) { + goto no_match; + } + break; + case LTTNG_UST_ABI_CONTEXT_APP_CONTEXT: + if (strcmp(key->u.app_ctx.provider_name, + ctx->ctx.u.app_ctx.provider_name) || + strcmp(key->u.app_ctx.ctx_name, + ctx->ctx.u.app_ctx.ctx_name)) { + goto no_match; + } + break; + default: + break; + } + + /* Match. */ + return 1; + +no_match: + return 0; +} + +/* + * Lookup for an ust app context from an lttng_ust_context. + * + * Must be called while holding RCU read side lock. + * Return an ust_app_ctx object or NULL on error. + */ +static +struct ust_app_ctx *find_ust_app_context(struct lttng_ht *ht, + struct lttng_ust_context_attr *uctx) +{ + struct lttng_ht_iter iter; + struct lttng_ht_node_ulong *node; + struct ust_app_ctx *app_ctx = NULL; + + LTTNG_ASSERT(uctx); + LTTNG_ASSERT(ht); + + /* Lookup using the lttng_ust_context_type and a custom match fct. */ + cds_lfht_lookup(ht->ht, ht->hash_fct((void *) uctx->ctx, lttng_ht_seed), + ht_match_ust_app_ctx, uctx, &iter.iter); + node = lttng_ht_iter_get_node_ulong(&iter); + if (!node) { + goto end; + } + + app_ctx = caa_container_of(node, struct ust_app_ctx, node); + +end: + return app_ctx; +} + +/* + * Create a context for the channel on the tracer. + * + * Called with UST app session lock held and a RCU read side lock. + */ +static +int create_ust_app_channel_context(struct ust_app_channel *ua_chan, + struct lttng_ust_context_attr *uctx, + struct ust_app *app) +{ + int ret = 0; + struct ust_app_ctx *ua_ctx; + + DBG2("UST app adding context to channel %s", ua_chan->name); + + ua_ctx = find_ust_app_context(ua_chan->ctx, uctx); + if (ua_ctx) { + ret = -EEXIST; + goto error; + } + + ua_ctx = alloc_ust_app_ctx(uctx); + if (ua_ctx == NULL) { + /* malloc failed */ + ret = -ENOMEM; + goto error; + } + + lttng_ht_node_init_ulong(&ua_ctx->node, (unsigned long) ua_ctx->ctx.ctx); + lttng_ht_add_ulong(ua_chan->ctx, &ua_ctx->node); + cds_list_add_tail(&ua_ctx->list, &ua_chan->ctx_list); + + ret = create_ust_channel_context(ua_chan, ua_ctx, app); + if (ret < 0) { + goto error; + } + +error: + return ret; +} + +/* + * Enable on the tracer side a ust app event for the session and channel. + * + * Called with UST app session lock held. + */ +static +int enable_ust_app_event(struct ust_app_session *ua_sess, + struct ust_app_event *ua_event, struct ust_app *app) +{ + int ret; + + ret = enable_ust_object(app, ua_event->obj); + if (ret < 0) { + goto error; + } + + ua_event->enabled = 1; + +error: + return ret; +} + +/* + * Disable on the tracer side a ust app event for the session and channel. + */ +static int disable_ust_app_event(struct ust_app_session *ua_sess, + struct ust_app_event *ua_event, struct ust_app *app) +{ + int ret; + + ret = disable_ust_object(app, ua_event->obj); + if (ret < 0) { + goto error; + } + + ua_event->enabled = 0; + +error: + return ret; +} + +/* + * Lookup ust app channel for session and disable it on the tracer side. + */ +static +int disable_ust_app_channel(struct ust_app_session *ua_sess, + struct ust_app_channel *ua_chan, struct ust_app *app) +{ + int ret; + + ret = disable_ust_channel(app, ua_sess, ua_chan); + if (ret < 0) { + goto error; + } + + ua_chan->enabled = 0; + +error: + return ret; +} + +/* + * Lookup ust app channel for session and enable it on the tracer side. This + * MUST be called with a RCU read side lock acquired. + */ +static int enable_ust_app_channel(struct ust_app_session *ua_sess, + struct ltt_ust_channel *uchan, struct ust_app *app) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct lttng_ht_node_str *ua_chan_node; + struct ust_app_channel *ua_chan; + + lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &iter); + ua_chan_node = lttng_ht_iter_get_node_str(&iter); + if (ua_chan_node == NULL) { + DBG2("Unable to find channel %s in ust session id %" PRIu64, + uchan->name, ua_sess->tracing_id); + goto error; + } + + ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); + + ret = enable_ust_channel(app, ua_sess, ua_chan); + if (ret < 0) { + goto error; + } + +error: + return ret; +} + +/* + * Ask the consumer to create a channel and get it if successful. + * + * Called with UST app session lock held. + * + * Return 0 on success or else a negative value. + */ +static int do_consumer_create_channel(struct ltt_ust_session *usess, + struct ust_app_session *ua_sess, struct ust_app_channel *ua_chan, + int bitness, struct ust_registry_session *registry, + uint64_t trace_archive_id) +{ + int ret; + unsigned int nb_fd = 0; + struct consumer_socket *socket; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(ua_chan); + LTTNG_ASSERT(registry); + + rcu_read_lock(); + health_code_update(); + + /* Get the right consumer socket for the application. */ + socket = consumer_find_socket_by_bitness(bitness, usess->consumer); + if (!socket) { + ret = -EINVAL; + goto error; + } + + health_code_update(); + + /* Need one fd for the channel. */ + ret = lttng_fd_get(LTTNG_FD_APPS, 1); + if (ret < 0) { + ERR("Exhausted number of available FD upon create channel"); + goto error; + } + + /* + * Ask consumer to create channel. The consumer will return the number of + * stream we have to expect. + */ + ret = ust_consumer_ask_channel(ua_sess, ua_chan, usess->consumer, socket, + registry, usess->current_trace_chunk); + if (ret < 0) { + goto error_ask; + } + + /* + * Compute the number of fd needed before receiving them. It must be 2 per + * stream (2 being the default value here). + */ + nb_fd = DEFAULT_UST_STREAM_FD_NUM * ua_chan->expected_stream_count; + + /* Reserve the amount of file descriptor we need. */ + ret = lttng_fd_get(LTTNG_FD_APPS, nb_fd); + if (ret < 0) { + ERR("Exhausted number of available FD upon create channel"); + goto error_fd_get_stream; + } + + health_code_update(); + + /* + * Now get the channel from the consumer. This call will populate the stream + * list of that channel and set the ust objects. + */ + if (usess->consumer->enabled) { + ret = ust_consumer_get_channel(socket, ua_chan); + if (ret < 0) { + goto error_destroy; + } + } + + rcu_read_unlock(); + return 0; + +error_destroy: + lttng_fd_put(LTTNG_FD_APPS, nb_fd); +error_fd_get_stream: + /* + * Initiate a destroy channel on the consumer since we had an error + * handling it on our side. The return value is of no importance since we + * already have a ret value set by the previous error that we need to + * return. + */ + (void) ust_consumer_destroy_channel(socket, ua_chan); +error_ask: + lttng_fd_put(LTTNG_FD_APPS, 1); +error: + health_code_update(); + rcu_read_unlock(); + return ret; +} + +/* + * Duplicate the ust data object of the ust app stream and save it in the + * buffer registry stream. + * + * Return 0 on success or else a negative value. + */ +static int duplicate_stream_object(struct buffer_reg_stream *reg_stream, + struct ust_app_stream *stream) +{ + int ret; + + LTTNG_ASSERT(reg_stream); + LTTNG_ASSERT(stream); + + /* Duplicating a stream requires 2 new fds. Reserve them. */ + ret = lttng_fd_get(LTTNG_FD_APPS, 2); + if (ret < 0) { + ERR("Exhausted number of available FD upon duplicate stream"); + goto error; + } + + /* Duplicate object for stream once the original is in the registry. */ + ret = lttng_ust_ctl_duplicate_ust_object_data(&stream->obj, + reg_stream->obj.ust); + if (ret < 0) { + ERR("Duplicate stream obj from %p to %p failed with ret %d", + reg_stream->obj.ust, stream->obj, ret); + lttng_fd_put(LTTNG_FD_APPS, 2); + goto error; + } + stream->handle = stream->obj->handle; + +error: + return ret; +} + +/* + * Duplicate the ust data object of the ust app. channel and save it in the + * buffer registry channel. + * + * Return 0 on success or else a negative value. + */ +static int duplicate_channel_object(struct buffer_reg_channel *buf_reg_chan, + struct ust_app_channel *ua_chan) +{ + int ret; + + LTTNG_ASSERT(buf_reg_chan); + LTTNG_ASSERT(ua_chan); + + /* Duplicating a channel requires 1 new fd. Reserve it. */ + ret = lttng_fd_get(LTTNG_FD_APPS, 1); + if (ret < 0) { + ERR("Exhausted number of available FD upon duplicate channel"); + goto error_fd_get; + } + + /* Duplicate object for stream once the original is in the registry. */ + ret = lttng_ust_ctl_duplicate_ust_object_data(&ua_chan->obj, buf_reg_chan->obj.ust); + if (ret < 0) { + ERR("Duplicate channel obj from %p to %p failed with ret: %d", + buf_reg_chan->obj.ust, ua_chan->obj, ret); + goto error; + } + ua_chan->handle = ua_chan->obj->handle; + + return 0; + +error: + lttng_fd_put(LTTNG_FD_APPS, 1); +error_fd_get: + return ret; +} + +/* + * For a given channel buffer registry, setup all streams of the given ust + * application channel. + * + * Return 0 on success or else a negative value. + */ +static int setup_buffer_reg_streams(struct buffer_reg_channel *buf_reg_chan, + struct ust_app_channel *ua_chan, + struct ust_app *app) +{ + int ret = 0; + struct ust_app_stream *stream, *stmp; + + LTTNG_ASSERT(buf_reg_chan); + LTTNG_ASSERT(ua_chan); + + DBG2("UST app setup buffer registry stream"); + + /* Send all streams to application. */ + cds_list_for_each_entry_safe(stream, stmp, &ua_chan->streams.head, list) { + struct buffer_reg_stream *reg_stream; + + ret = buffer_reg_stream_create(®_stream); + if (ret < 0) { + goto error; + } + + /* + * Keep original pointer and nullify it in the stream so the delete + * stream call does not release the object. + */ + reg_stream->obj.ust = stream->obj; + stream->obj = NULL; + buffer_reg_stream_add(reg_stream, buf_reg_chan); + + /* We don't need the streams anymore. */ + cds_list_del(&stream->list); + delete_ust_app_stream(-1, stream, app); + } + +error: + return ret; +} + +/* + * Create a buffer registry channel for the given session registry and + * application channel object. If regp pointer is valid, it's set with the + * created object. Important, the created object is NOT added to the session + * registry hash table. + * + * Return 0 on success else a negative value. + */ +static int create_buffer_reg_channel(struct buffer_reg_session *reg_sess, + struct ust_app_channel *ua_chan, struct buffer_reg_channel **regp) +{ + int ret; + struct buffer_reg_channel *buf_reg_chan = NULL; + + LTTNG_ASSERT(reg_sess); + LTTNG_ASSERT(ua_chan); + + DBG2("UST app creating buffer registry channel for %s", ua_chan->name); + + /* Create buffer registry channel. */ + ret = buffer_reg_channel_create(ua_chan->tracing_channel_id, &buf_reg_chan); + if (ret < 0) { + goto error_create; + } + LTTNG_ASSERT(buf_reg_chan); + buf_reg_chan->consumer_key = ua_chan->key; + buf_reg_chan->subbuf_size = ua_chan->attr.subbuf_size; + buf_reg_chan->num_subbuf = ua_chan->attr.num_subbuf; + + /* Create and add a channel registry to session. */ + ret = ust_registry_channel_add(reg_sess->reg.ust, + ua_chan->tracing_channel_id); + if (ret < 0) { + goto error; + } + buffer_reg_channel_add(reg_sess, buf_reg_chan); + + if (regp) { + *regp = buf_reg_chan; + } + + return 0; + +error: + /* Safe because the registry channel object was not added to any HT. */ + buffer_reg_channel_destroy(buf_reg_chan, LTTNG_DOMAIN_UST); +error_create: + return ret; +} + +/* + * Setup buffer registry channel for the given session registry and application + * channel object. If regp pointer is valid, it's set with the created object. + * + * Return 0 on success else a negative value. + */ +static int setup_buffer_reg_channel(struct buffer_reg_session *reg_sess, + struct ust_app_channel *ua_chan, struct buffer_reg_channel *buf_reg_chan, + struct ust_app *app) +{ + int ret; + + LTTNG_ASSERT(reg_sess); + LTTNG_ASSERT(buf_reg_chan); + LTTNG_ASSERT(ua_chan); + LTTNG_ASSERT(ua_chan->obj); + + DBG2("UST app setup buffer registry channel for %s", ua_chan->name); + + /* Setup all streams for the registry. */ + ret = setup_buffer_reg_streams(buf_reg_chan, ua_chan, app); + if (ret < 0) { + goto error; + } + + buf_reg_chan->obj.ust = ua_chan->obj; + ua_chan->obj = NULL; + + return 0; + +error: + buffer_reg_channel_remove(reg_sess, buf_reg_chan); + buffer_reg_channel_destroy(buf_reg_chan, LTTNG_DOMAIN_UST); + return ret; +} + +/* + * Send buffer registry channel to the application. + * + * Return 0 on success else a negative value. + */ +static int send_channel_uid_to_ust(struct buffer_reg_channel *buf_reg_chan, + struct ust_app *app, struct ust_app_session *ua_sess, + struct ust_app_channel *ua_chan) +{ + int ret; + struct buffer_reg_stream *reg_stream; + + LTTNG_ASSERT(buf_reg_chan); + LTTNG_ASSERT(app); + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(ua_chan); + + DBG("UST app sending buffer registry channel to ust sock %d", app->sock); + + ret = duplicate_channel_object(buf_reg_chan, ua_chan); + if (ret < 0) { + goto error; + } + + /* Send channel to the application. */ + ret = ust_consumer_send_channel_to_ust(app, ua_sess, ua_chan); + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = -ENOTCONN; /* Caused by app exiting. */ + goto error; + } else if (ret == -EAGAIN) { + /* Caused by timeout. */ + WARN("Communication with application %d timed out on send_channel for channel \"%s\" of session \"%" PRIu64 "\".", + app->pid, ua_chan->name, ua_sess->tracing_id); + /* Treat this the same way as an application that is exiting. */ + ret = -ENOTCONN; + goto error; + } else if (ret < 0) { + goto error; + } + + health_code_update(); + + /* Send all streams to application. */ + pthread_mutex_lock(&buf_reg_chan->stream_list_lock); + cds_list_for_each_entry(reg_stream, &buf_reg_chan->streams, lnode) { + struct ust_app_stream stream; + + ret = duplicate_stream_object(reg_stream, &stream); + if (ret < 0) { + goto error_stream_unlock; + } + + ret = ust_consumer_send_stream_to_ust(app, ua_chan, &stream); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = -ENOTCONN; /* Caused by app exiting. */ + } else if (ret == -EAGAIN) { + /* + * Caused by timeout. + * Treat this the same way as an application + * that is exiting. + */ + WARN("Communication with application %d timed out on send_stream for stream \"%s\" of channel \"%s\" of session \"%" PRIu64 "\".", + app->pid, stream.name, + ua_chan->name, + ua_sess->tracing_id); + ret = -ENOTCONN; + } + (void) release_ust_app_stream(-1, &stream, app); + goto error_stream_unlock; + } + + /* + * The return value is not important here. This function will output an + * error if needed. + */ + (void) release_ust_app_stream(-1, &stream, app); + } + ua_chan->is_sent = 1; + +error_stream_unlock: + pthread_mutex_unlock(&buf_reg_chan->stream_list_lock); +error: + return ret; +} + +/* + * Create and send to the application the created buffers with per UID buffers. + * + * This MUST be called with a RCU read side lock acquired. + * The session list lock and the session's lock must be acquired. + * + * Return 0 on success else a negative value. + */ +static int create_channel_per_uid(struct ust_app *app, + struct ltt_ust_session *usess, struct ust_app_session *ua_sess, + struct ust_app_channel *ua_chan) +{ + int ret; + struct buffer_reg_uid *reg_uid; + struct buffer_reg_channel *buf_reg_chan; + struct ltt_session *session = NULL; + enum lttng_error_code notification_ret; + struct ust_registry_channel *ust_reg_chan; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(usess); + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(ua_chan); + + DBG("UST app creating channel %s with per UID buffers", ua_chan->name); + + reg_uid = buffer_reg_uid_find(usess->id, app->bits_per_long, app->uid); + /* + * The session creation handles the creation of this global registry + * object. If none can be find, there is a code flow problem or a + * teardown race. + */ + LTTNG_ASSERT(reg_uid); + + buf_reg_chan = buffer_reg_channel_find(ua_chan->tracing_channel_id, + reg_uid); + if (buf_reg_chan) { + goto send_channel; + } + + /* Create the buffer registry channel object. */ + ret = create_buffer_reg_channel(reg_uid->registry, ua_chan, &buf_reg_chan); + if (ret < 0) { + ERR("Error creating the UST channel \"%s\" registry instance", + ua_chan->name); + goto error; + } + + session = session_find_by_id(ua_sess->tracing_id); + LTTNG_ASSERT(session); + LTTNG_ASSERT(pthread_mutex_trylock(&session->lock)); + LTTNG_ASSERT(session_trylock_list()); + + /* + * Create the buffers on the consumer side. This call populates the + * ust app channel object with all streams and data object. + */ + ret = do_consumer_create_channel(usess, ua_sess, ua_chan, + app->bits_per_long, reg_uid->registry->reg.ust, + session->most_recent_chunk_id.value); + if (ret < 0) { + ERR("Error creating UST channel \"%s\" on the consumer daemon", + ua_chan->name); + + /* + * Let's remove the previously created buffer registry channel so + * it's not visible anymore in the session registry. + */ + ust_registry_channel_del_free(reg_uid->registry->reg.ust, + ua_chan->tracing_channel_id, false); + buffer_reg_channel_remove(reg_uid->registry, buf_reg_chan); + buffer_reg_channel_destroy(buf_reg_chan, LTTNG_DOMAIN_UST); + goto error; + } + + /* + * Setup the streams and add it to the session registry. + */ + ret = setup_buffer_reg_channel(reg_uid->registry, + ua_chan, buf_reg_chan, app); + if (ret < 0) { + ERR("Error setting up UST channel \"%s\"", ua_chan->name); + goto error; + } + + /* Notify the notification subsystem of the channel's creation. */ + pthread_mutex_lock(®_uid->registry->reg.ust->lock); + ust_reg_chan = ust_registry_channel_find(reg_uid->registry->reg.ust, + ua_chan->tracing_channel_id); + LTTNG_ASSERT(ust_reg_chan); + ust_reg_chan->consumer_key = ua_chan->key; + ust_reg_chan = NULL; + pthread_mutex_unlock(®_uid->registry->reg.ust->lock); + + notification_ret = notification_thread_command_add_channel( + the_notification_thread_handle, session->name, + lttng_credentials_get_uid( + &ua_sess->effective_credentials), + lttng_credentials_get_gid( + &ua_sess->effective_credentials), + ua_chan->name, ua_chan->key, LTTNG_DOMAIN_UST, + ua_chan->attr.subbuf_size * ua_chan->attr.num_subbuf); + if (notification_ret != LTTNG_OK) { + ret = - (int) notification_ret; + ERR("Failed to add channel to notification thread"); + goto error; + } + +send_channel: + /* Send buffers to the application. */ + ret = send_channel_uid_to_ust(buf_reg_chan, app, ua_sess, ua_chan); + if (ret < 0) { + if (ret != -ENOTCONN) { + ERR("Error sending channel to application"); + } + goto error; + } + +error: + if (session) { + session_put(session); + } + return ret; +} + +/* + * Create and send to the application the created buffers with per PID buffers. + * + * Called with UST app session lock held. + * The session list lock and the session's lock must be acquired. + * + * Return 0 on success else a negative value. + */ +static int create_channel_per_pid(struct ust_app *app, + struct ltt_ust_session *usess, struct ust_app_session *ua_sess, + struct ust_app_channel *ua_chan) +{ + int ret; + struct ust_registry_session *registry; + enum lttng_error_code cmd_ret; + struct ltt_session *session = NULL; + uint64_t chan_reg_key; + struct ust_registry_channel *ust_reg_chan; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(usess); + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(ua_chan); + + DBG("UST app creating channel %s with per PID buffers", ua_chan->name); + + rcu_read_lock(); + + registry = get_session_registry(ua_sess); + /* The UST app session lock is held, registry shall not be null. */ + LTTNG_ASSERT(registry); + + /* Create and add a new channel registry to session. */ + ret = ust_registry_channel_add(registry, ua_chan->key); + if (ret < 0) { + ERR("Error creating the UST channel \"%s\" registry instance", + ua_chan->name); + goto error; + } + + session = session_find_by_id(ua_sess->tracing_id); + LTTNG_ASSERT(session); + + LTTNG_ASSERT(pthread_mutex_trylock(&session->lock)); + LTTNG_ASSERT(session_trylock_list()); + + /* Create and get channel on the consumer side. */ + ret = do_consumer_create_channel(usess, ua_sess, ua_chan, + app->bits_per_long, registry, + session->most_recent_chunk_id.value); + if (ret < 0) { + ERR("Error creating UST channel \"%s\" on the consumer daemon", + ua_chan->name); + goto error_remove_from_registry; + } + + ret = send_channel_pid_to_ust(app, ua_sess, ua_chan); + if (ret < 0) { + if (ret != -ENOTCONN) { + ERR("Error sending channel to application"); + } + goto error_remove_from_registry; + } + + chan_reg_key = ua_chan->key; + pthread_mutex_lock(®istry->lock); + ust_reg_chan = ust_registry_channel_find(registry, chan_reg_key); + LTTNG_ASSERT(ust_reg_chan); + ust_reg_chan->consumer_key = ua_chan->key; + pthread_mutex_unlock(®istry->lock); + + cmd_ret = notification_thread_command_add_channel( + the_notification_thread_handle, session->name, + lttng_credentials_get_uid( + &ua_sess->effective_credentials), + lttng_credentials_get_gid( + &ua_sess->effective_credentials), + ua_chan->name, ua_chan->key, LTTNG_DOMAIN_UST, + ua_chan->attr.subbuf_size * ua_chan->attr.num_subbuf); + if (cmd_ret != LTTNG_OK) { + ret = - (int) cmd_ret; + ERR("Failed to add channel to notification thread"); + goto error_remove_from_registry; + } + +error_remove_from_registry: + if (ret) { + ust_registry_channel_del_free(registry, ua_chan->key, false); + } +error: + rcu_read_unlock(); + if (session) { + session_put(session); + } + return ret; +} + +/* + * From an already allocated ust app channel, create the channel buffers if + * needed and send them to the application. This MUST be called with a RCU read + * side lock acquired. + * + * Called with UST app session lock held. + * + * Return 0 on success or else a negative value. Returns -ENOTCONN if + * the application exited concurrently. + */ +static int ust_app_channel_send(struct ust_app *app, + struct ltt_ust_session *usess, struct ust_app_session *ua_sess, + struct ust_app_channel *ua_chan) +{ + int ret; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(usess); + LTTNG_ASSERT(usess->active); + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(ua_chan); + + /* Handle buffer type before sending the channel to the application. */ + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_UID: + { + ret = create_channel_per_uid(app, usess, ua_sess, ua_chan); + if (ret < 0) { + goto error; + } + break; + } + case LTTNG_BUFFER_PER_PID: + { + ret = create_channel_per_pid(app, usess, ua_sess, ua_chan); + if (ret < 0) { + goto error; + } + break; + } + default: + abort(); + ret = -EINVAL; + goto error; + } + + /* Initialize ust objd object using the received handle and add it. */ + lttng_ht_node_init_ulong(&ua_chan->ust_objd_node, ua_chan->handle); + lttng_ht_add_unique_ulong(app->ust_objd, &ua_chan->ust_objd_node); + + /* If channel is not enabled, disable it on the tracer */ + if (!ua_chan->enabled) { + ret = disable_ust_channel(app, ua_sess, ua_chan); + if (ret < 0) { + goto error; + } + } + +error: + return ret; +} + +/* + * Create UST app channel and return it through ua_chanp if not NULL. + * + * Called with UST app session lock and RCU read-side lock held. + * + * Return 0 on success or else a negative value. + */ +static int ust_app_channel_allocate(struct ust_app_session *ua_sess, + struct ltt_ust_channel *uchan, + enum lttng_ust_abi_chan_type type, struct ltt_ust_session *usess, + struct ust_app_channel **ua_chanp) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct lttng_ht_node_str *ua_chan_node; + struct ust_app_channel *ua_chan; + + /* Lookup channel in the ust app session */ + lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &iter); + ua_chan_node = lttng_ht_iter_get_node_str(&iter); + if (ua_chan_node != NULL) { + ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); + goto end; + } + + ua_chan = alloc_ust_app_channel(uchan->name, ua_sess, &uchan->attr); + if (ua_chan == NULL) { + /* Only malloc can fail here */ + ret = -ENOMEM; + goto error; + } + shadow_copy_channel(ua_chan, uchan); + + /* Set channel type. */ + ua_chan->attr.type = type; + + /* Only add the channel if successful on the tracer side. */ + lttng_ht_add_unique_str(ua_sess->channels, &ua_chan->node); +end: + if (ua_chanp) { + *ua_chanp = ua_chan; + } + + /* Everything went well. */ + return 0; + +error: + return ret; +} + +/* + * Create UST app event and create it on the tracer side. + * + * Must be called with the RCU read side lock held. + * Called with ust app session mutex held. + */ +static +int create_ust_app_event(struct ust_app_session *ua_sess, + struct ust_app_channel *ua_chan, struct ltt_ust_event *uevent, + struct ust_app *app) +{ + int ret = 0; + struct ust_app_event *ua_event; + + ua_event = alloc_ust_app_event(uevent->attr.name, &uevent->attr); + if (ua_event == NULL) { + /* Only failure mode of alloc_ust_app_event(). */ + ret = -ENOMEM; + goto end; + } + shadow_copy_event(ua_event, uevent); + + /* Create it on the tracer side */ + ret = create_ust_event(app, ua_sess, ua_chan, ua_event); + 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 an event being created already existed: " + "event_name = \"%s\", pid = %d, ppid = %d, uid = %d, gid = %d", + uevent->attr.name, + app->pid, app->ppid, app->uid, + app->gid); + } + goto error; + } + + add_unique_ust_app_event(ua_chan, ua_event); + + DBG2("UST app create event completed: app = '%s' pid = %d", + app->name, app->pid); + +end: + return ret; + +error: + /* Valid. Calling here is already in a read side lock */ + delete_ust_app_event(-1, ua_event, app); + return ret; +} + +/* + * Create UST app event notifier rule and create it on the tracer side. + * + * Must be called with the RCU read side lock held. + * Called with ust app session mutex held. + */ +static +int create_ust_app_event_notifier_rule(struct lttng_trigger *trigger, + struct ust_app *app) +{ + int ret = 0; + struct ust_app_event_notifier_rule *ua_event_notifier_rule; + + ua_event_notifier_rule = alloc_ust_app_event_notifier_rule(trigger); + if (ua_event_notifier_rule == NULL) { + ret = -ENOMEM; + goto end; + } + + /* Create it on the tracer side. */ + ret = create_ust_event_notifier(app, ua_event_notifier_rule); + 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 an event notifier being created already exists: " + "token = \"%" PRIu64 "\", pid = %d, ppid = %d, uid = %d, gid = %d", + lttng_trigger_get_tracer_token(trigger), + app->pid, app->ppid, app->uid, + app->gid); + } + goto error; + } + + lttng_ht_add_unique_u64(app->token_to_event_notifier_rule_ht, + &ua_event_notifier_rule->node); + + DBG2("UST app create token event rule completed: app = '%s', pid = %d), token = %" PRIu64, + app->name, app->pid, lttng_trigger_get_tracer_token(trigger)); + + goto end; + +error: + /* The RCU read side lock is already being held by the caller. */ + delete_ust_app_event_notifier_rule(-1, ua_event_notifier_rule, app); +end: + return ret; +} + +/* + * Create UST metadata and open it on the tracer side. + * + * Called with UST app session lock held and RCU read side lock. + */ +static int create_ust_app_metadata(struct ust_app_session *ua_sess, + struct ust_app *app, struct consumer_output *consumer) +{ + int ret = 0; + struct ust_app_channel *metadata; + struct consumer_socket *socket; + struct ust_registry_session *registry; + struct ltt_session *session = NULL; + + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(app); + LTTNG_ASSERT(consumer); + + registry = get_session_registry(ua_sess); + /* The UST app session is held registry shall not be null. */ + LTTNG_ASSERT(registry); + + pthread_mutex_lock(®istry->lock); + + /* Metadata already exists for this registry or it was closed previously */ + if (registry->metadata_key || registry->metadata_closed) { + ret = 0; + goto error; + } + + /* Allocate UST metadata */ + metadata = alloc_ust_app_channel(DEFAULT_METADATA_NAME, ua_sess, NULL); + if (!metadata) { + /* malloc() failed */ + ret = -ENOMEM; + goto error; + } + + memcpy(&metadata->attr, &ua_sess->metadata_attr, sizeof(metadata->attr)); + + /* Need one fd for the channel. */ + ret = lttng_fd_get(LTTNG_FD_APPS, 1); + if (ret < 0) { + ERR("Exhausted number of available FD upon create metadata"); + goto error; + } + + /* Get the right consumer socket for the application. */ + socket = consumer_find_socket_by_bitness(app->bits_per_long, consumer); + if (!socket) { + ret = -EINVAL; + goto error_consumer; + } + + /* + * Keep metadata key so we can identify it on the consumer side. Assign it + * to the registry *before* we ask the consumer so we avoid the race of the + * consumer requesting the metadata and the ask_channel call on our side + * did not returned yet. + */ + registry->metadata_key = metadata->key; + + session = session_find_by_id(ua_sess->tracing_id); + LTTNG_ASSERT(session); + + LTTNG_ASSERT(pthread_mutex_trylock(&session->lock)); + LTTNG_ASSERT(session_trylock_list()); + + /* + * Ask the metadata channel creation to the consumer. The metadata object + * will be created by the consumer and kept their. However, the stream is + * never added or monitored until we do a first push metadata to the + * consumer. + */ + ret = ust_consumer_ask_channel(ua_sess, metadata, consumer, socket, + registry, session->current_trace_chunk); + if (ret < 0) { + /* Nullify the metadata key so we don't try to close it later on. */ + registry->metadata_key = 0; + goto error_consumer; + } + + /* + * The setup command will make the metadata stream be sent to the relayd, + * if applicable, and the thread managing the metadatas. This is important + * because after this point, if an error occurs, the only way the stream + * can be deleted is to be monitored in the consumer. + */ + ret = consumer_setup_metadata(socket, metadata->key); + if (ret < 0) { + /* Nullify the metadata key so we don't try to close it later on. */ + registry->metadata_key = 0; + goto error_consumer; + } + + DBG2("UST metadata with key %" PRIu64 " created for app pid %d", + metadata->key, app->pid); + +error_consumer: + lttng_fd_put(LTTNG_FD_APPS, 1); + delete_ust_app_channel(-1, metadata, app); +error: + pthread_mutex_unlock(®istry->lock); + if (session) { + session_put(session); + } + return ret; +} + +/* + * Return ust app pointer or NULL if not found. RCU read side lock MUST be + * acquired before calling this function. + */ +struct ust_app *ust_app_find_by_pid(pid_t pid) +{ + struct ust_app *app = NULL; + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter iter; + + lttng_ht_lookup(ust_app_ht, (void *)((unsigned long) pid), &iter); + node = lttng_ht_iter_get_node_ulong(&iter); + if (node == NULL) { + DBG2("UST app no found with pid %d", pid); + goto error; + } + + DBG2("Found UST app by pid %d", pid); + + app = caa_container_of(node, struct ust_app, pid_n); + +error: + return app; +} + +/* + * Allocate and init an UST app object using the registration information and + * the command socket. This is called when the command socket connects to the + * session daemon. + * + * The object is returned on success or else NULL. + */ +struct ust_app *ust_app_create(struct ust_register_msg *msg, int sock) +{ + int ret; + struct ust_app *lta = NULL; + struct lttng_pipe *event_notifier_event_source_pipe = NULL; + + LTTNG_ASSERT(msg); + LTTNG_ASSERT(sock >= 0); + + DBG3("UST app creating application for socket %d", sock); + + if ((msg->bits_per_long == 64 && + (uatomic_read(&the_ust_consumerd64_fd) == + -EINVAL)) || + (msg->bits_per_long == 32 && + (uatomic_read(&the_ust_consumerd32_fd) == + -EINVAL))) { + ERR("Registration failed: application \"%s\" (pid: %d) has " + "%d-bit long, but no consumerd for this size is available.\n", + msg->name, msg->pid, msg->bits_per_long); + goto error; + } + + /* + * Reserve the two file descriptors of the event source pipe. The write + * end will be closed once it is passed to the application, at which + * point a single 'put' will be performed. + */ + ret = lttng_fd_get(LTTNG_FD_APPS, 2); + if (ret) { + ERR("Failed to reserve two file descriptors for the event source pipe while creating a new application instance: app = '%s', pid = %d", + msg->name, (int) msg->pid); + goto error; + } + + event_notifier_event_source_pipe = lttng_pipe_open(FD_CLOEXEC); + if (!event_notifier_event_source_pipe) { + PERROR("Failed to open application event source pipe: '%s' (pid = %d)", + msg->name, msg->pid); + goto error; + } + + lta = (ust_app *) zmalloc(sizeof(struct ust_app)); + if (lta == NULL) { + PERROR("malloc"); + goto error_free_pipe; + } + + lta->event_notifier_group.event_pipe = event_notifier_event_source_pipe; + + lta->ppid = msg->ppid; + lta->uid = msg->uid; + lta->gid = msg->gid; + + lta->bits_per_long = msg->bits_per_long; + lta->uint8_t_alignment = msg->uint8_t_alignment; + lta->uint16_t_alignment = msg->uint16_t_alignment; + lta->uint32_t_alignment = msg->uint32_t_alignment; + lta->uint64_t_alignment = msg->uint64_t_alignment; + lta->long_alignment = msg->long_alignment; + lta->byte_order = msg->byte_order; + + lta->v_major = msg->major; + lta->v_minor = msg->minor; + lta->sessions = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + 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->token_to_event_notifier_rule_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)); + lta->name[UST_APP_PROCNAME_LEN] = '\0'; + + /* + * Before this can be called, when receiving the registration information, + * the application compatibility is checked. So, at this point, the + * application can work with this session daemon. + */ + lta->compatible = 1; + + lta->pid = msg->pid; + lttng_ht_node_init_ulong(<a->pid_n, (unsigned long) lta->pid); + lta->sock = sock; + pthread_mutex_init(<a->sock_lock, NULL); + lttng_ht_node_init_ulong(<a->sock_n, (unsigned long) lta->sock); + + CDS_INIT_LIST_HEAD(<a->teardown_head); + return lta; + +error_free_pipe: + lttng_pipe_destroy(event_notifier_event_source_pipe); + lttng_fd_put(LTTNG_FD_APPS, 2); +error: + return NULL; +} + +/* + * For a given application object, add it to every hash table. + */ +void ust_app_add(struct ust_app *app) +{ + LTTNG_ASSERT(app); + LTTNG_ASSERT(app->notify_sock >= 0); + + app->registration_time = time(NULL); + + rcu_read_lock(); + + /* + * On a re-registration, we want to kick out the previous registration of + * that pid + */ + lttng_ht_add_replace_ulong(ust_app_ht, &app->pid_n); + + /* + * The socket _should_ be unique until _we_ call close. So, a add_unique + * for the ust_app_ht_by_sock is used which asserts fail if the entry was + * already in the table. + */ + lttng_ht_add_unique_ulong(ust_app_ht_by_sock, &app->sock_n); + + /* Add application to the notify socket hash table. */ + lttng_ht_node_init_ulong(&app->notify_sock_n, app->notify_sock); + lttng_ht_add_unique_ulong(ust_app_ht_by_notify_sock, &app->notify_sock_n); + + DBG("App registered with pid:%d ppid:%d uid:%d gid:%d sock =%d name:%s " + "notify_sock =%d (version %d.%d)", app->pid, app->ppid, app->uid, + app->gid, app->sock, app->name, app->notify_sock, app->v_major, + app->v_minor); + + rcu_read_unlock(); +} + +/* + * Set the application version into the object. + * + * Return 0 on success else a negative value either an errno code or a + * LTTng-UST error code. + */ +int ust_app_version(struct ust_app *app) +{ + int ret; + + LTTNG_ASSERT(app); + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_tracer_version(app->sock, &app->version); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -LTTNG_UST_ERR_EXITING || ret == -EPIPE) { + DBG3("UST app version failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app version failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app version failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + } + + return ret; +} + +bool ust_app_supports_notifiers(const struct ust_app *app) +{ + return app->v_major >= 9; +} + +bool ust_app_supports_counters(const struct ust_app *app) +{ + return app->v_major >= 9; +} + +/* + * Setup the base event notifier group. + * + * Return 0 on success else a negative value either an errno code or a + * LTTng-UST error code. + */ +int ust_app_setup_event_notifier_group(struct ust_app *app) +{ + int ret; + int event_pipe_write_fd; + struct lttng_ust_abi_object_data *event_notifier_group = NULL; + enum lttng_error_code lttng_ret; + enum event_notifier_error_accounting_status event_notifier_error_accounting_status; + + LTTNG_ASSERT(app); + + if (!ust_app_supports_notifiers(app)) { + ret = -ENOSYS; + goto error; + } + + /* Get the write side of the pipe. */ + event_pipe_write_fd = lttng_pipe_get_writefd( + app->event_notifier_group.event_pipe); + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_create_event_notifier_group(app->sock, + event_pipe_write_fd, &event_notifier_group); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + ret = 0; + DBG3("UST app create event notifier group failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + ret = 0; + WARN("UST app create event notifier group failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app create event notifier group failed with ret %d: pid = %d, sock = %d, event_pipe_write_fd: %d", + ret, app->pid, app->sock, event_pipe_write_fd); + } + goto error; + } + + ret = lttng_pipe_write_close(app->event_notifier_group.event_pipe); + if (ret) { + ERR("Failed to close write end of the application's event source pipe: app = '%s' (pid = %d)", + app->name, app->pid); + goto error; + } + + /* + * Release the file descriptor that was reserved for the write-end of + * the pipe. + */ + lttng_fd_put(LTTNG_FD_APPS, 1); + + lttng_ret = notification_thread_command_add_tracer_event_source( + the_notification_thread_handle, + lttng_pipe_get_readfd( + app->event_notifier_group.event_pipe), + LTTNG_DOMAIN_UST); + if (lttng_ret != LTTNG_OK) { + ERR("Failed to add tracer event source to notification thread"); + ret = - 1; + goto error; + } + + /* Assign handle only when the complete setup is valid. */ + app->event_notifier_group.object = event_notifier_group; + + event_notifier_error_accounting_status = + event_notifier_error_accounting_register_app(app); + switch (event_notifier_error_accounting_status) { + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_OK: + break; + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_UNSUPPORTED: + DBG3("Failed to setup event notifier error accounting (application does not support notifier error accounting): app socket fd = %d, app name = '%s', app pid = %d", + app->sock, app->name, (int) app->pid); + ret = 0; + goto error_accounting; + case EVENT_NOTIFIER_ERROR_ACCOUNTING_STATUS_APP_DEAD: + DBG3("Failed to setup event notifier error accounting (application is dead): app socket fd = %d, app name = '%s', app pid = %d", + app->sock, app->name, (int) app->pid); + ret = 0; + goto error_accounting; + default: + ERR("Failed to setup event notifier error accounting for app"); + ret = -1; + goto error_accounting; + } + + return ret; + +error_accounting: + lttng_ret = notification_thread_command_remove_tracer_event_source( + the_notification_thread_handle, + lttng_pipe_get_readfd( + app->event_notifier_group.event_pipe)); + if (lttng_ret != LTTNG_OK) { + ERR("Failed to remove application tracer event source from notification thread"); + } + +error: + lttng_ust_ctl_release_object(app->sock, app->event_notifier_group.object); + free(app->event_notifier_group.object); + app->event_notifier_group.object = NULL; + return ret; +} + +/* + * Unregister app by removing it from the global traceable app list and freeing + * the data struct. + * + * The socket is already closed at this point so no close to sock. + */ +void ust_app_unregister(int sock) +{ + struct ust_app *lta; + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter ust_app_sock_iter; + struct lttng_ht_iter iter; + struct ust_app_session *ua_sess; + int ret; + + rcu_read_lock(); + + /* Get the node reference for a call_rcu */ + lttng_ht_lookup(ust_app_ht_by_sock, (void *)((unsigned long) sock), &ust_app_sock_iter); + node = lttng_ht_iter_get_node_ulong(&ust_app_sock_iter); + LTTNG_ASSERT(node); + + lta = caa_container_of(node, struct ust_app, sock_n); + DBG("PID %d unregistering with sock %d", lta->pid, sock); + + /* + * For per-PID buffers, perform "push metadata" and flush all + * application streams before removing app from hash tables, + * ensuring proper behavior of data_pending check. + * Remove sessions so they are not visible during deletion. + */ + cds_lfht_for_each_entry(lta->sessions->ht, &iter.iter, ua_sess, + node.node) { + struct ust_registry_session *registry; + + ret = lttng_ht_del(lta->sessions, &iter); + if (ret) { + /* The session was already removed so scheduled for teardown. */ + continue; + } + + if (ua_sess->buffer_type == LTTNG_BUFFER_PER_PID) { + (void) ust_app_flush_app_session(lta, ua_sess); + } + + /* + * Add session to list for teardown. This is safe since at this point we + * are the only one using this list. + */ + pthread_mutex_lock(&ua_sess->lock); + + if (ua_sess->deleted) { + pthread_mutex_unlock(&ua_sess->lock); + continue; + } + + /* + * Normally, this is done in the delete session process which is + * executed in the call rcu below. However, upon registration we can't + * afford to wait for the grace period before pushing data or else the + * data pending feature can race between the unregistration and stop + * command where the data pending command is sent *before* the grace + * period ended. + * + * The close metadata below nullifies the metadata pointer in the + * session so the delete session will NOT push/close a second time. + */ + registry = get_session_registry(ua_sess); + if (registry) { + /* Push metadata for application before freeing the application. */ + (void) push_metadata(registry, ua_sess->consumer); + + /* + * Don't ask to close metadata for global per UID buffers. Close + * metadata only on destroy trace session in this case. Also, the + * previous push metadata could have flag the metadata registry to + * close so don't send a close command if closed. + */ + if (ua_sess->buffer_type != LTTNG_BUFFER_PER_UID) { + /* And ask to close it for this session registry. */ + (void) close_metadata(registry, ua_sess->consumer); + } + } + cds_list_add(&ua_sess->teardown_node, <a->teardown_head); + + pthread_mutex_unlock(&ua_sess->lock); + } + + /* Remove application from PID hash table */ + ret = lttng_ht_del(ust_app_ht_by_sock, &ust_app_sock_iter); + LTTNG_ASSERT(!ret); + + /* + * Remove application from notify hash table. The thread handling the + * notify socket could have deleted the node so ignore on error because + * either way it's valid. The close of that socket is handled by the + * apps_notify_thread. + */ + iter.iter.node = <a->notify_sock_n.node; + (void) lttng_ht_del(ust_app_ht_by_notify_sock, &iter); + + /* + * Ignore return value since the node might have been removed before by an + * add replace during app registration because the PID can be reassigned by + * the OS. + */ + iter.iter.node = <a->pid_n.node; + ret = lttng_ht_del(ust_app_ht, &iter); + if (ret) { + DBG3("Unregister app by PID %d failed. This can happen on pid reuse", + lta->pid); + } + + /* Free memory */ + call_rcu(<a->pid_n.head, delete_ust_app_rcu); + + rcu_read_unlock(); + return; +} + +/* + * Fill events array with all events name of all registered apps. + */ +int ust_app_list_events(struct lttng_event **events) +{ + int ret, handle; + size_t nbmem, count = 0; + struct lttng_ht_iter iter; + struct ust_app *app; + struct lttng_event *tmp_event; + + nbmem = UST_APP_EVENT_LIST_SIZE; + tmp_event = (lttng_event *) zmalloc(nbmem * sizeof(struct lttng_event)); + if (tmp_event == NULL) { + PERROR("zmalloc ust app events"); + ret = -ENOMEM; + goto error; + } + + rcu_read_lock(); + + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + struct lttng_ust_abi_tracepoint_iter uiter; + + health_code_update(); + + if (!app->compatible) { + /* + * TODO: In time, we should notice the caller of this error by + * telling him that this is a version error. + */ + continue; + } + pthread_mutex_lock(&app->sock_lock); + handle = lttng_ust_ctl_tracepoint_list(app->sock); + if (handle < 0) { + if (handle != -EPIPE && handle != -LTTNG_UST_ERR_EXITING) { + ERR("UST app list events getting handle failed for app pid %d", + app->pid); + } + pthread_mutex_unlock(&app->sock_lock); + continue; + } + + while ((ret = lttng_ust_ctl_tracepoint_list_get(app->sock, handle, + &uiter)) != -LTTNG_UST_ERR_NOENT) { + /* Handle ustctl error. */ + if (ret < 0) { + int release_ret; + + if (ret != -LTTNG_UST_ERR_EXITING && ret != -EPIPE) { + ERR("UST app tp list get failed for app %d with ret %d", + app->sock, ret); + } else { + DBG3("UST app tp list get failed. Application is dead"); + break; + } + free(tmp_event); + release_ret = lttng_ust_ctl_release_handle(app->sock, handle); + if (release_ret < 0 && + release_ret != -LTTNG_UST_ERR_EXITING && + release_ret != -EPIPE) { + ERR("Error releasing app handle for app %d with ret %d", app->sock, release_ret); + } + pthread_mutex_unlock(&app->sock_lock); + goto rcu_error; + } + + health_code_update(); + if (count >= nbmem) { + /* In case the realloc fails, we free the memory */ + struct lttng_event *new_tmp_event; + size_t new_nbmem; + + new_nbmem = nbmem << 1; + DBG2("Reallocating event list from %zu to %zu entries", + nbmem, new_nbmem); + new_tmp_event = (lttng_event *) realloc(tmp_event, + new_nbmem * sizeof(struct lttng_event)); + if (new_tmp_event == NULL) { + int release_ret; + + PERROR("realloc ust app events"); + free(tmp_event); + ret = -ENOMEM; + release_ret = lttng_ust_ctl_release_handle(app->sock, handle); + if (release_ret < 0 && + release_ret != -LTTNG_UST_ERR_EXITING && + release_ret != -EPIPE) { + ERR("Error releasing app handle for app %d with ret %d", app->sock, release_ret); + } + pthread_mutex_unlock(&app->sock_lock); + goto rcu_error; + } + /* Zero the new memory */ + memset(new_tmp_event + nbmem, 0, + (new_nbmem - nbmem) * sizeof(struct lttng_event)); + nbmem = new_nbmem; + tmp_event = new_tmp_event; + } + memcpy(tmp_event[count].name, uiter.name, LTTNG_UST_ABI_SYM_NAME_LEN); + tmp_event[count].loglevel = uiter.loglevel; + tmp_event[count].type = (enum lttng_event_type) LTTNG_UST_ABI_TRACEPOINT; + tmp_event[count].pid = app->pid; + tmp_event[count].enabled = -1; + count++; + } + ret = lttng_ust_ctl_release_handle(app->sock, handle); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("Error releasing app handle. Application died: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("Error releasing app handle. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("Error releasing app handle with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + } + } + + ret = count; + *events = tmp_event; + + DBG2("UST app list events done (%zu events)", count); + +rcu_error: + rcu_read_unlock(); +error: + health_code_update(); + return ret; +} + +/* + * Fill events array with all events name of all registered apps. + */ +int ust_app_list_event_fields(struct lttng_event_field **fields) +{ + int ret, handle; + size_t nbmem, count = 0; + struct lttng_ht_iter iter; + struct ust_app *app; + struct lttng_event_field *tmp_event; + + nbmem = UST_APP_EVENT_LIST_SIZE; + tmp_event = (lttng_event_field *) zmalloc(nbmem * sizeof(struct lttng_event_field)); + if (tmp_event == NULL) { + PERROR("zmalloc ust app event fields"); + ret = -ENOMEM; + goto error; + } + + rcu_read_lock(); + + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + struct lttng_ust_abi_field_iter uiter; + + health_code_update(); + + if (!app->compatible) { + /* + * TODO: In time, we should notice the caller of this error by + * telling him that this is a version error. + */ + continue; + } + pthread_mutex_lock(&app->sock_lock); + handle = lttng_ust_ctl_tracepoint_field_list(app->sock); + if (handle < 0) { + if (handle != -EPIPE && handle != -LTTNG_UST_ERR_EXITING) { + ERR("UST app list field getting handle failed for app pid %d", + app->pid); + } + pthread_mutex_unlock(&app->sock_lock); + continue; + } + + while ((ret = lttng_ust_ctl_tracepoint_field_list_get(app->sock, handle, + &uiter)) != -LTTNG_UST_ERR_NOENT) { + /* Handle ustctl error. */ + if (ret < 0) { + int release_ret; + + if (ret != -LTTNG_UST_ERR_EXITING && ret != -EPIPE) { + ERR("UST app tp list field failed for app %d with ret %d", + app->sock, ret); + } else { + DBG3("UST app tp list field failed. Application is dead"); + break; + } + free(tmp_event); + release_ret = lttng_ust_ctl_release_handle(app->sock, handle); + pthread_mutex_unlock(&app->sock_lock); + if (release_ret < 0 && + release_ret != -LTTNG_UST_ERR_EXITING && + release_ret != -EPIPE) { + ERR("Error releasing app handle for app %d with ret %d", app->sock, release_ret); + } + goto rcu_error; + } + + health_code_update(); + if (count >= nbmem) { + /* In case the realloc fails, we free the memory */ + struct lttng_event_field *new_tmp_event; + size_t new_nbmem; + + new_nbmem = nbmem << 1; + DBG2("Reallocating event field list from %zu to %zu entries", + nbmem, new_nbmem); + new_tmp_event = (lttng_event_field *) realloc(tmp_event, + new_nbmem * sizeof(struct lttng_event_field)); + if (new_tmp_event == NULL) { + int release_ret; + + PERROR("realloc ust app event fields"); + free(tmp_event); + ret = -ENOMEM; + release_ret = lttng_ust_ctl_release_handle(app->sock, handle); + pthread_mutex_unlock(&app->sock_lock); + if (release_ret && + release_ret != -LTTNG_UST_ERR_EXITING && + release_ret != -EPIPE) { + ERR("Error releasing app handle for app %d with ret %d", app->sock, release_ret); + } + goto rcu_error; + } + /* Zero the new memory */ + memset(new_tmp_event + nbmem, 0, + (new_nbmem - nbmem) * sizeof(struct lttng_event_field)); + nbmem = new_nbmem; + tmp_event = new_tmp_event; + } + + memcpy(tmp_event[count].field_name, uiter.field_name, LTTNG_UST_ABI_SYM_NAME_LEN); + /* Mapping between these enums matches 1 to 1. */ + tmp_event[count].type = (enum lttng_event_field_type) uiter.type; + tmp_event[count].nowrite = uiter.nowrite; + + memcpy(tmp_event[count].event.name, uiter.event_name, LTTNG_UST_ABI_SYM_NAME_LEN); + tmp_event[count].event.loglevel = uiter.loglevel; + tmp_event[count].event.type = LTTNG_EVENT_TRACEPOINT; + tmp_event[count].event.pid = app->pid; + tmp_event[count].event.enabled = -1; + count++; + } + ret = lttng_ust_ctl_release_handle(app->sock, handle); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0 && + ret != -LTTNG_UST_ERR_EXITING && + ret != -EPIPE) { + ERR("Error releasing app handle for app %d with ret %d", app->sock, ret); + } + } + + ret = count; + *fields = tmp_event; + + DBG2("UST app list event fields done (%zu events)", count); + +rcu_error: + rcu_read_unlock(); +error: + health_code_update(); + return ret; +} + +/* + * Free and clean all traceable apps of the global list. + * + * Should _NOT_ be called with RCU read-side lock held. + */ +void ust_app_clean_list(void) +{ + int ret; + struct ust_app *app; + struct lttng_ht_iter iter; + + DBG2("UST app cleaning registered apps hash table"); + + rcu_read_lock(); + + /* Cleanup notify socket hash table */ + if (ust_app_ht_by_notify_sock) { + cds_lfht_for_each_entry(ust_app_ht_by_notify_sock->ht, &iter.iter, app, + notify_sock_n.node) { + /* + * Assert that all notifiers are gone as all triggers + * are unregistered prior to this clean-up. + */ + LTTNG_ASSERT(lttng_ht_get_count(app->token_to_event_notifier_rule_ht) == 0); + + ust_app_notify_sock_unregister(app->notify_sock); + } + } + + if (ust_app_ht) { + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + ret = lttng_ht_del(ust_app_ht, &iter); + LTTNG_ASSERT(!ret); + call_rcu(&app->pid_n.head, delete_ust_app_rcu); + } + } + + /* Cleanup socket hash table */ + if (ust_app_ht_by_sock) { + cds_lfht_for_each_entry(ust_app_ht_by_sock->ht, &iter.iter, app, + sock_n.node) { + ret = lttng_ht_del(ust_app_ht_by_sock, &iter); + LTTNG_ASSERT(!ret); + } + } + + rcu_read_unlock(); + + /* Destroy is done only when the ht is empty */ + if (ust_app_ht) { + ht_cleanup_push(ust_app_ht); + } + if (ust_app_ht_by_sock) { + ht_cleanup_push(ust_app_ht_by_sock); + } + if (ust_app_ht_by_notify_sock) { + ht_cleanup_push(ust_app_ht_by_notify_sock); + } +} + +/* + * Init UST app hash table. + */ +int ust_app_ht_alloc(void) +{ + ust_app_ht = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); + if (!ust_app_ht) { + return -1; + } + ust_app_ht_by_sock = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); + if (!ust_app_ht_by_sock) { + return -1; + } + ust_app_ht_by_notify_sock = lttng_ht_new(0, LTTNG_HT_TYPE_ULONG); + if (!ust_app_ht_by_notify_sock) { + return -1; + } + return 0; +} + +/* + * For a specific UST session, disable the channel for all registered apps. + */ +int ust_app_disable_channel_glb(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct lttng_ht_node_str *ua_chan_node; + struct ust_app *app; + struct ust_app_session *ua_sess; + struct ust_app_channel *ua_chan; + + LTTNG_ASSERT(usess->active); + DBG2("UST app disabling channel %s from global domain for session id %" PRIu64, + uchan->name, usess->id); + + rcu_read_lock(); + + /* For every registered applications */ + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + struct lttng_ht_iter uiter; + if (!app->compatible) { + /* + * TODO: In time, we should notice the caller of this error by + * telling him that this is a version error. + */ + continue; + } + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + continue; + } + + /* Get channel */ + lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); + ua_chan_node = lttng_ht_iter_get_node_str(&uiter); + /* If the session if found for the app, the channel must be there */ + LTTNG_ASSERT(ua_chan_node); + + ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); + /* The channel must not be already disabled */ + LTTNG_ASSERT(ua_chan->enabled == 1); + + /* Disable channel onto application */ + ret = disable_ust_app_channel(ua_sess, ua_chan, app); + if (ret < 0) { + /* XXX: We might want to report this error at some point... */ + continue; + } + } + + rcu_read_unlock(); + return ret; +} + +/* + * For a specific UST session, enable the channel for all registered apps. + */ +int ust_app_enable_channel_glb(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct ust_app *app; + struct ust_app_session *ua_sess; + + LTTNG_ASSERT(usess->active); + DBG2("UST app enabling channel %s to global domain for session id %" PRIu64, + uchan->name, usess->id); + + rcu_read_lock(); + + /* For every registered applications */ + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + if (!app->compatible) { + /* + * TODO: In time, we should notice the caller of this error by + * telling him that this is a version error. + */ + continue; + } + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + continue; + } + + /* Enable channel onto application */ + ret = enable_ust_app_channel(ua_sess, uchan, app); + if (ret < 0) { + /* XXX: We might want to report this error at some point... */ + continue; + } + } + + rcu_read_unlock(); + return ret; +} + +/* + * Disable an event in a channel and for a specific session. + */ +int ust_app_disable_event_glb(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan, struct ltt_ust_event *uevent) +{ + int ret = 0; + struct lttng_ht_iter iter, uiter; + struct lttng_ht_node_str *ua_chan_node; + struct ust_app *app; + struct ust_app_session *ua_sess; + struct ust_app_channel *ua_chan; + struct ust_app_event *ua_event; + + LTTNG_ASSERT(usess->active); + DBG("UST app disabling event %s for all apps in channel " + "%s for session id %" PRIu64, + uevent->attr.name, uchan->name, usess->id); + + rcu_read_lock(); + + /* For all registered applications */ + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + if (!app->compatible) { + /* + * TODO: In time, we should notice the caller of this error by + * telling him that this is a version error. + */ + continue; + } + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + /* Next app */ + continue; + } + + /* Lookup channel in the ust app session */ + lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); + ua_chan_node = lttng_ht_iter_get_node_str(&uiter); + if (ua_chan_node == NULL) { + DBG2("Channel %s not found in session id %" PRIu64 " for app pid %d." + "Skipping", uchan->name, usess->id, app->pid); + continue; + } + ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); + + ua_event = find_ust_app_event(ua_chan->events, uevent->attr.name, + uevent->filter, uevent->attr.loglevel, + uevent->exclusion); + if (ua_event == NULL) { + DBG2("Event %s not found in channel %s for app pid %d." + "Skipping", uevent->attr.name, uchan->name, app->pid); + continue; + } + + ret = disable_ust_app_event(ua_sess, ua_event, app); + if (ret < 0) { + /* XXX: Report error someday... */ + continue; + } + } + + rcu_read_unlock(); + return ret; +} + +/* The ua_sess lock must be held by the caller. */ +static +int ust_app_channel_create(struct ltt_ust_session *usess, + struct ust_app_session *ua_sess, + struct ltt_ust_channel *uchan, struct ust_app *app, + struct ust_app_channel **_ua_chan) +{ + int ret = 0; + struct ust_app_channel *ua_chan = NULL; + + LTTNG_ASSERT(ua_sess); + ASSERT_LOCKED(ua_sess->lock); + + if (!strncmp(uchan->name, DEFAULT_METADATA_NAME, + sizeof(uchan->name))) { + copy_channel_attr_to_ustctl(&ua_sess->metadata_attr, + &uchan->attr); + ret = 0; + } else { + struct ltt_ust_context *uctx = NULL; + + /* + * Create channel onto application and synchronize its + * configuration. + */ + ret = ust_app_channel_allocate(ua_sess, uchan, + LTTNG_UST_ABI_CHAN_PER_CPU, usess, + &ua_chan); + if (ret < 0) { + goto error; + } + + ret = ust_app_channel_send(app, usess, + ua_sess, ua_chan); + if (ret) { + goto error; + } + + /* Add contexts. */ + cds_list_for_each_entry(uctx, &uchan->ctx_list, list) { + ret = create_ust_app_channel_context(ua_chan, + &uctx->ctx, app); + if (ret) { + goto error; + } + } + } + +error: + if (ret < 0) { + switch (ret) { + case -ENOTCONN: + /* + * The application's socket is not valid. Either a bad socket + * or a timeout on it. We can't inform the caller that for a + * specific app, the session failed so lets continue here. + */ + ret = 0; /* Not an error. */ + break; + case -ENOMEM: + default: + break; + } + } + + if (ret == 0 && _ua_chan) { + /* + * Only return the application's channel on success. Note + * that the channel can still be part of the application's + * channel hashtable on error. + */ + *_ua_chan = ua_chan; + } + return ret; +} + +/* + * Enable event for a specific session and channel on the tracer. + */ +int ust_app_enable_event_glb(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan, struct ltt_ust_event *uevent) +{ + int ret = 0; + struct lttng_ht_iter iter, uiter; + struct lttng_ht_node_str *ua_chan_node; + struct ust_app *app; + struct ust_app_session *ua_sess; + struct ust_app_channel *ua_chan; + struct ust_app_event *ua_event; + + LTTNG_ASSERT(usess->active); + DBG("UST app enabling event %s for all apps for session id %" PRIu64, + uevent->attr.name, usess->id); + + /* + * NOTE: At this point, this function is called only if the session and + * channel passed are already created for all apps. and enabled on the + * tracer also. + */ + + rcu_read_lock(); + + /* For all registered applications */ + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + if (!app->compatible) { + /* + * TODO: In time, we should notice the caller of this error by + * telling him that this is a version error. + */ + continue; + } + ua_sess = lookup_session_by_app(usess, app); + if (!ua_sess) { + /* The application has problem or is probably dead. */ + continue; + } + + pthread_mutex_lock(&ua_sess->lock); + + if (ua_sess->deleted) { + pthread_mutex_unlock(&ua_sess->lock); + continue; + } + + /* Lookup channel in the ust app session */ + lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); + ua_chan_node = lttng_ht_iter_get_node_str(&uiter); + /* + * It is possible that the channel cannot be found is + * the channel/event creation occurs concurrently with + * an application exit. + */ + if (!ua_chan_node) { + pthread_mutex_unlock(&ua_sess->lock); + continue; + } + + ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); + + /* Get event node */ + ua_event = find_ust_app_event(ua_chan->events, uevent->attr.name, + uevent->filter, uevent->attr.loglevel, uevent->exclusion); + if (ua_event == NULL) { + DBG3("UST app enable event %s not found for app PID %d." + "Skipping app", uevent->attr.name, app->pid); + goto next_app; + } + + ret = enable_ust_app_event(ua_sess, ua_event, app); + if (ret < 0) { + pthread_mutex_unlock(&ua_sess->lock); + goto error; + } + next_app: + pthread_mutex_unlock(&ua_sess->lock); + } + +error: + rcu_read_unlock(); + return ret; +} + +/* + * For a specific existing UST session and UST channel, creates the event for + * all registered apps. + */ +int ust_app_create_event_glb(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan, struct ltt_ust_event *uevent) +{ + int ret = 0; + struct lttng_ht_iter iter, uiter; + struct lttng_ht_node_str *ua_chan_node; + struct ust_app *app; + struct ust_app_session *ua_sess; + struct ust_app_channel *ua_chan; + + LTTNG_ASSERT(usess->active); + DBG("UST app creating event %s for all apps for session id %" PRIu64, + uevent->attr.name, usess->id); + + rcu_read_lock(); + + /* For all registered applications */ + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + if (!app->compatible) { + /* + * TODO: In time, we should notice the caller of this error by + * telling him that this is a version error. + */ + continue; + } + ua_sess = lookup_session_by_app(usess, app); + if (!ua_sess) { + /* The application has problem or is probably dead. */ + continue; + } + + pthread_mutex_lock(&ua_sess->lock); + + if (ua_sess->deleted) { + pthread_mutex_unlock(&ua_sess->lock); + continue; + } + + /* Lookup channel in the ust app session */ + lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); + ua_chan_node = lttng_ht_iter_get_node_str(&uiter); + /* If the channel is not found, there is a code flow error */ + LTTNG_ASSERT(ua_chan_node); + + ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); + + ret = create_ust_app_event(ua_sess, ua_chan, uevent, app); + pthread_mutex_unlock(&ua_sess->lock); + if (ret < 0) { + if (ret != -LTTNG_UST_ERR_EXIST) { + /* Possible value at this point: -ENOMEM. If so, we stop! */ + break; + } + DBG2("UST app event %s already exist on app PID %d", + uevent->attr.name, app->pid); + continue; + } + } + + rcu_read_unlock(); + return ret; +} + +/* + * Start tracing for a specific UST session and app. + * + * Called with UST app session lock held. + * + */ +static +int ust_app_start_trace(struct ltt_ust_session *usess, struct ust_app *app) +{ + int ret = 0; + struct ust_app_session *ua_sess; + + DBG("Starting tracing for ust app pid %d", app->pid); + + rcu_read_lock(); + + if (!app->compatible) { + goto end; + } + + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + /* The session is in teardown process. Ignore and continue. */ + goto end; + } + + pthread_mutex_lock(&ua_sess->lock); + + if (ua_sess->deleted) { + pthread_mutex_unlock(&ua_sess->lock); + goto end; + } + + if (ua_sess->enabled) { + pthread_mutex_unlock(&ua_sess->lock); + goto end; + } + + /* Upon restart, we skip the setup, already done */ + if (ua_sess->started) { + goto skip_setup; + } + + health_code_update(); + +skip_setup: + /* This starts the UST tracing */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_start_session(app->sock, ua_sess->handle); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app start session failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + pthread_mutex_unlock(&ua_sess->lock); + goto end; + } else if (ret == -EAGAIN) { + WARN("UST app start session failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + pthread_mutex_unlock(&ua_sess->lock); + goto end; + + } else { + ERR("UST app start session failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + goto error_unlock; + } + + /* Indicate that the session has been started once */ + ua_sess->started = 1; + ua_sess->enabled = 1; + + pthread_mutex_unlock(&ua_sess->lock); + + health_code_update(); + + /* Quiescent wait after starting trace */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_wait_quiescent(app->sock); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app wait quiescent failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app wait quiescent failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app wait quiescent failed with ret %d: pid %d, sock = %d", + ret, app->pid, app->sock); + } + } + +end: + rcu_read_unlock(); + health_code_update(); + return 0; + +error_unlock: + pthread_mutex_unlock(&ua_sess->lock); + rcu_read_unlock(); + health_code_update(); + return -1; +} + +/* + * Stop tracing for a specific UST session and app. + */ +static +int ust_app_stop_trace(struct ltt_ust_session *usess, struct ust_app *app) +{ + int ret = 0; + struct ust_app_session *ua_sess; + struct ust_registry_session *registry; + + DBG("Stopping tracing for ust app pid %d", app->pid); + + rcu_read_lock(); + + if (!app->compatible) { + goto end_no_session; + } + + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + goto end_no_session; + } + + pthread_mutex_lock(&ua_sess->lock); + + if (ua_sess->deleted) { + pthread_mutex_unlock(&ua_sess->lock); + goto end_no_session; + } + + /* + * If started = 0, it means that stop trace has been called for a session + * that was never started. It's possible since we can have a fail start + * from either the application manager thread or the command thread. Simply + * indicate that this is a stop error. + */ + if (!ua_sess->started) { + goto error_rcu_unlock; + } + + health_code_update(); + + /* This inhibits UST tracing */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_stop_session(app->sock, ua_sess->handle); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app stop session failed. Application is dead: pid = %d, sock = %d", + app->pid, app->sock); + goto end_unlock; + } else if (ret == -EAGAIN) { + WARN("UST app stop session failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + goto end_unlock; + + } else { + ERR("UST app stop session failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + goto error_rcu_unlock; + } + + health_code_update(); + ua_sess->enabled = 0; + + /* Quiescent wait after stopping trace */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_wait_quiescent(app->sock); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app wait quiescent failed. Application is dead: pid= %d, sock = %d)", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app wait quiescent failed. Communication time out: pid= %d, sock = %d)", + app->pid, app->sock); + } else { + ERR("UST app wait quiescent failed with ret %d: pid= %d, sock = %d)", + ret, app->pid, app->sock); + } + } + + health_code_update(); + + registry = get_session_registry(ua_sess); + + /* The UST app session is held registry shall not be null. */ + LTTNG_ASSERT(registry); + + /* Push metadata for application before freeing the application. */ + (void) push_metadata(registry, ua_sess->consumer); + +end_unlock: + pthread_mutex_unlock(&ua_sess->lock); +end_no_session: + rcu_read_unlock(); + health_code_update(); + return 0; + +error_rcu_unlock: + pthread_mutex_unlock(&ua_sess->lock); + rcu_read_unlock(); + health_code_update(); + return -1; +} + +static +int ust_app_flush_app_session(struct ust_app *app, + struct ust_app_session *ua_sess) +{ + int ret, retval = 0; + struct lttng_ht_iter iter; + struct ust_app_channel *ua_chan; + struct consumer_socket *socket; + + DBG("Flushing app session buffers for ust app pid %d", app->pid); + + rcu_read_lock(); + + if (!app->compatible) { + goto end_not_compatible; + } + + pthread_mutex_lock(&ua_sess->lock); + + if (ua_sess->deleted) { + goto end_deleted; + } + + health_code_update(); + + /* Flushing buffers */ + socket = consumer_find_socket_by_bitness(app->bits_per_long, + ua_sess->consumer); + + /* Flush buffers and push metadata. */ + switch (ua_sess->buffer_type) { + case LTTNG_BUFFER_PER_PID: + cds_lfht_for_each_entry(ua_sess->channels->ht, &iter.iter, ua_chan, + node.node) { + health_code_update(); + ret = consumer_flush_channel(socket, ua_chan->key); + if (ret) { + ERR("Error flushing consumer channel"); + retval = -1; + continue; + } + } + break; + case LTTNG_BUFFER_PER_UID: + default: + abort(); + break; + } + + health_code_update(); + +end_deleted: + pthread_mutex_unlock(&ua_sess->lock); + +end_not_compatible: + rcu_read_unlock(); + health_code_update(); + return retval; +} + +/* + * Flush buffers for all applications for a specific UST session. + * Called with UST session lock held. + */ +static +int ust_app_flush_session(struct ltt_ust_session *usess) + +{ + int ret = 0; + + DBG("Flushing session buffers for all ust apps"); + + rcu_read_lock(); + + /* Flush buffers and push metadata. */ + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_UID: + { + struct buffer_reg_uid *reg; + struct lttng_ht_iter iter; + + /* Flush all per UID buffers associated to that session. */ + cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { + struct ust_registry_session *ust_session_reg; + struct buffer_reg_channel *buf_reg_chan; + struct consumer_socket *socket; + + /* Get consumer socket to use to push the metadata.*/ + socket = consumer_find_socket_by_bitness(reg->bits_per_long, + usess->consumer); + if (!socket) { + /* Ignore request if no consumer is found for the session. */ + continue; + } + + cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, + buf_reg_chan, node.node) { + /* + * The following call will print error values so the return + * code is of little importance because whatever happens, we + * have to try them all. + */ + (void) consumer_flush_channel(socket, buf_reg_chan->consumer_key); + } + + ust_session_reg = reg->registry->reg.ust; + /* Push metadata. */ + (void) push_metadata(ust_session_reg, usess->consumer); + } + break; + } + case LTTNG_BUFFER_PER_PID: + { + struct ust_app_session *ua_sess; + struct lttng_ht_iter iter; + struct ust_app *app; + + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + continue; + } + (void) ust_app_flush_app_session(app, ua_sess); + } + break; + } + default: + ret = -1; + abort(); + break; + } + + rcu_read_unlock(); + health_code_update(); + return ret; +} + +static +int ust_app_clear_quiescent_app_session(struct ust_app *app, + struct ust_app_session *ua_sess) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct ust_app_channel *ua_chan; + struct consumer_socket *socket; + + DBG("Clearing stream quiescent state for ust app pid %d", app->pid); + + rcu_read_lock(); + + if (!app->compatible) { + goto end_not_compatible; + } + + pthread_mutex_lock(&ua_sess->lock); + + if (ua_sess->deleted) { + goto end_unlock; + } + + health_code_update(); + + socket = consumer_find_socket_by_bitness(app->bits_per_long, + ua_sess->consumer); + if (!socket) { + ERR("Failed to find consumer (%" PRIu32 ") socket", + app->bits_per_long); + ret = -1; + goto end_unlock; + } + + /* Clear quiescent state. */ + switch (ua_sess->buffer_type) { + case LTTNG_BUFFER_PER_PID: + cds_lfht_for_each_entry(ua_sess->channels->ht, &iter.iter, + ua_chan, node.node) { + health_code_update(); + ret = consumer_clear_quiescent_channel(socket, + ua_chan->key); + if (ret) { + ERR("Error clearing quiescent state for consumer channel"); + ret = -1; + continue; + } + } + break; + case LTTNG_BUFFER_PER_UID: + default: + abort(); + ret = -1; + break; + } + + health_code_update(); + +end_unlock: + pthread_mutex_unlock(&ua_sess->lock); + +end_not_compatible: + rcu_read_unlock(); + health_code_update(); + return ret; +} + +/* + * Clear quiescent state in each stream for all applications for a + * specific UST session. + * Called with UST session lock held. + */ +static +int ust_app_clear_quiescent_session(struct ltt_ust_session *usess) + +{ + int ret = 0; + + DBG("Clearing stream quiescent state for all ust apps"); + + rcu_read_lock(); + + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_UID: + { + struct lttng_ht_iter iter; + struct buffer_reg_uid *reg; + + /* + * Clear quiescent for all per UID buffers associated to + * that session. + */ + cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { + struct consumer_socket *socket; + struct buffer_reg_channel *buf_reg_chan; + + /* Get associated consumer socket.*/ + socket = consumer_find_socket_by_bitness( + reg->bits_per_long, usess->consumer); + if (!socket) { + /* + * Ignore request if no consumer is found for + * the session. + */ + continue; + } + + cds_lfht_for_each_entry(reg->registry->channels->ht, + &iter.iter, buf_reg_chan, node.node) { + /* + * The following call will print error values so + * the return code is of little importance + * because whatever happens, we have to try them + * all. + */ + (void) consumer_clear_quiescent_channel(socket, + buf_reg_chan->consumer_key); + } + } + break; + } + case LTTNG_BUFFER_PER_PID: + { + struct ust_app_session *ua_sess; + struct lttng_ht_iter iter; + struct ust_app *app; + + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, + pid_n.node) { + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + continue; + } + (void) ust_app_clear_quiescent_app_session(app, + ua_sess); + } + break; + } + default: + ret = -1; + abort(); + break; + } + + rcu_read_unlock(); + health_code_update(); + return ret; +} + +/* + * Destroy a specific UST session in apps. + */ +static int destroy_trace(struct ltt_ust_session *usess, struct ust_app *app) +{ + int ret; + struct ust_app_session *ua_sess; + struct lttng_ht_iter iter; + struct lttng_ht_node_u64 *node; + + DBG("Destroy tracing for ust app pid %d", app->pid); + + rcu_read_lock(); + + if (!app->compatible) { + goto end; + } + + __lookup_session_by_app(usess, app, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (node == NULL) { + /* Session is being or is deleted. */ + goto end; + } + ua_sess = caa_container_of(node, struct ust_app_session, node); + + health_code_update(); + destroy_app_session(app, ua_sess); + + health_code_update(); + + /* Quiescent wait after stopping trace */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_wait_quiescent(app->sock); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app wait quiescent failed. Application is dead: pid= %d, sock = %d)", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app wait quiescent failed. Communication time out: pid= %d, sock = %d)", + app->pid, app->sock); + } else { + ERR("UST app wait quiescent failed with ret %d: pid= %d, sock = %d)", + ret, app->pid, app->sock); + } + } +end: + rcu_read_unlock(); + health_code_update(); + return 0; +} + +/* + * Start tracing for the UST session. + */ +int ust_app_start_trace_all(struct ltt_ust_session *usess) +{ + struct lttng_ht_iter iter; + struct ust_app *app; + + DBG("Starting all UST traces"); + + /* + * Even though the start trace might fail, flag this session active so + * other application coming in are started by default. + */ + usess->active = 1; + + rcu_read_lock(); + + /* + * In a start-stop-start use-case, we need to clear the quiescent state + * of each channel set by the prior stop command, thus ensuring that a + * following stop or destroy is sure to grab a timestamp_end near those + * operations, even if the packet is empty. + */ + (void) ust_app_clear_quiescent_session(usess); + + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + ust_app_global_update(usess, app); + } + + rcu_read_unlock(); + + return 0; +} + +/* + * Start tracing for the UST session. + * Called with UST session lock held. + */ +int ust_app_stop_trace_all(struct ltt_ust_session *usess) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct ust_app *app; + + DBG("Stopping all UST traces"); + + /* + * Even though the stop trace might fail, flag this session inactive so + * other application coming in are not started by default. + */ + usess->active = 0; + + rcu_read_lock(); + + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + ret = ust_app_stop_trace(usess, app); + if (ret < 0) { + /* Continue to next apps even on error */ + continue; + } + } + + (void) ust_app_flush_session(usess); + + rcu_read_unlock(); + + return 0; +} + +/* + * Destroy app UST session. + */ +int ust_app_destroy_trace_all(struct ltt_ust_session *usess) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct ust_app *app; + + DBG("Destroy all UST traces"); + + rcu_read_lock(); + + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + ret = destroy_trace(usess, app); + if (ret < 0) { + /* Continue to next apps even on error */ + continue; + } + } + + rcu_read_unlock(); + + return 0; +} + +/* The ua_sess lock must be held by the caller. */ +static +int find_or_create_ust_app_channel( + struct ltt_ust_session *usess, + struct ust_app_session *ua_sess, + struct ust_app *app, + struct ltt_ust_channel *uchan, + struct ust_app_channel **ua_chan) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct lttng_ht_node_str *ua_chan_node; + + lttng_ht_lookup(ua_sess->channels, (void *) uchan->name, &iter); + ua_chan_node = lttng_ht_iter_get_node_str(&iter); + if (ua_chan_node) { + *ua_chan = caa_container_of(ua_chan_node, + struct ust_app_channel, node); + goto end; + } + + ret = ust_app_channel_create(usess, ua_sess, uchan, app, ua_chan); + if (ret) { + goto end; + } +end: + return ret; +} + +static +int ust_app_channel_synchronize_event(struct ust_app_channel *ua_chan, + struct ltt_ust_event *uevent, struct ust_app_session *ua_sess, + struct ust_app *app) +{ + int ret = 0; + struct ust_app_event *ua_event = NULL; + + ua_event = find_ust_app_event(ua_chan->events, uevent->attr.name, + uevent->filter, uevent->attr.loglevel, uevent->exclusion); + if (!ua_event) { + ret = create_ust_app_event(ua_sess, ua_chan, uevent, app); + if (ret < 0) { + goto end; + } + } else { + if (ua_event->enabled != uevent->enabled) { + ret = uevent->enabled ? + enable_ust_app_event(ua_sess, ua_event, app) : + disable_ust_app_event(ua_sess, ua_event, app); + } + } + +end: + return ret; +} + +/* Called with RCU read-side lock held. */ +static +void ust_app_synchronize_event_notifier_rules(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 = NULL; + struct ust_app_event_notifier_rule *event_notifier_rule; + unsigned int count, i; + + if (!ust_app_supports_notifiers(app)) { + goto end; + } + + /* + * Currrently, registering or unregistering a trigger with an + * event rule condition causes a full synchronization of the event + * notifiers. + * + * The first step attempts to add an event notifier for all registered + * triggers that apply to the user space tracers. Then, the + * application's event notifiers rules are all checked against the list + * of registered triggers. Any event notifier that doesn't have a + * matching trigger can be assumed to have been disabled. + * + * All of this is inefficient, but is put in place to get the feature + * rolling as it is simpler at this moment. It will be optimized Soon™ + * to allow the state of enabled + * event notifiers to be synchronized in a piece-wise way. + */ + + /* Get all triggers using uid 0 (root) */ + ret_code = notification_thread_command_list_triggers( + the_notification_thread_handle, 0, &triggers); + if (ret_code != LTTNG_OK) { + goto end; + } + + LTTNG_ASSERT(triggers); + + t_status = lttng_triggers_get_count(triggers, &count); + if (t_status != LTTNG_TRIGGER_STATUS_OK) { + goto end; + } + + for (i = 0; i < count; i++) { + struct lttng_condition *condition; + struct lttng_event_rule *event_rule; + struct lttng_trigger *trigger; + const struct ust_app_event_notifier_rule *looked_up_event_notifier_rule; + enum lttng_condition_status condition_status; + uint64_t token; + + trigger = lttng_triggers_borrow_mutable_at_index(triggers, i); + LTTNG_ASSERT(trigger); + + token = lttng_trigger_get_tracer_token(trigger); + condition = lttng_trigger_get_condition(trigger); + + if (lttng_condition_get_type(condition) != + LTTNG_CONDITION_TYPE_EVENT_RULE_MATCHES) { + /* Does not apply */ + continue; + } + + condition_status = + lttng_condition_event_rule_matches_borrow_rule_mutable( + condition, &event_rule); + LTTNG_ASSERT(condition_status == LTTNG_CONDITION_STATUS_OK); + + if (lttng_event_rule_get_domain_type(event_rule) == LTTNG_DOMAIN_KERNEL) { + /* Skip kernel related triggers. */ + continue; + } + + /* + * Find or create the associated token event rule. The caller + * holds the RCU read lock, so this is safe to call without + * explicitly acquiring it here. + */ + looked_up_event_notifier_rule = find_ust_app_event_notifier_rule( + app->token_to_event_notifier_rule_ht, token); + if (!looked_up_event_notifier_rule) { + ret = create_ust_app_event_notifier_rule(trigger, app); + if (ret < 0) { + goto end; + } + } + } + + rcu_read_lock(); + /* Remove all unknown event sources from the app. */ + cds_lfht_for_each_entry (app->token_to_event_notifier_rule_ht->ht, + &app_trigger_iter.iter, event_notifier_rule, + node.node) { + const uint64_t app_token = event_notifier_rule->token; + bool found = false; + + /* + * Check if the app event trigger still exists on the + * notification side. + */ + for (i = 0; i < count; i++) { + uint64_t notification_thread_token; + const struct lttng_trigger *trigger = + lttng_triggers_get_at_index( + triggers, i); + + LTTNG_ASSERT(trigger); + + notification_thread_token = + lttng_trigger_get_tracer_token(trigger); + + if (notification_thread_token == app_token) { + found = true; + break; + } + } + + if (found) { + /* Still valid. */ + continue; + } + + /* + * This trigger was unregistered, disable it on the tracer's + * side. + */ + ret = lttng_ht_del(app->token_to_event_notifier_rule_ht, + &app_trigger_iter); + LTTNG_ASSERT(ret == 0); + + /* Callee logs errors. */ + (void) disable_ust_object(app, event_notifier_rule->obj); + + delete_ust_app_event_notifier_rule( + app->sock, event_notifier_rule, app); + } + + rcu_read_unlock(); + +end: + lttng_triggers_destroy(triggers); + return; +} + +/* + * RCU read lock must be held by the caller. + */ +static +void ust_app_synchronize_all_channels(struct ltt_ust_session *usess, + struct ust_app_session *ua_sess, + struct ust_app *app) +{ + int ret = 0; + struct cds_lfht_iter uchan_iter; + struct ltt_ust_channel *uchan; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(app); + + cds_lfht_for_each_entry(usess->domain_global.channels->ht, &uchan_iter, + uchan, node.node) { + struct ust_app_channel *ua_chan; + struct cds_lfht_iter uevent_iter; + struct ltt_ust_event *uevent; + + /* + * Search for a matching ust_app_channel. If none is found, + * create it. Creating the channel will cause the ua_chan + * structure to be allocated, the channel buffers to be + * allocated (if necessary) and sent to the application, and + * all enabled contexts will be added to the channel. + */ + ret = find_or_create_ust_app_channel(usess, ua_sess, + app, uchan, &ua_chan); + if (ret) { + /* Tracer is probably gone or ENOMEM. */ + goto end; + } + + if (!ua_chan) { + /* ua_chan will be NULL for the metadata channel */ + continue; + } + + cds_lfht_for_each_entry(uchan->events->ht, &uevent_iter, uevent, + node.node) { + ret = ust_app_channel_synchronize_event(ua_chan, + uevent, ua_sess, app); + if (ret) { + goto end; + } + } + + if (ua_chan->enabled != uchan->enabled) { + ret = uchan->enabled ? + enable_ust_app_channel(ua_sess, uchan, app) : + disable_ust_app_channel(ua_sess, ua_chan, app); + if (ret) { + goto end; + } + } + } +end: + return; +} + +/* + * The caller must ensure that the application is compatible and is tracked + * by the process attribute trackers. + */ +static +void ust_app_synchronize(struct ltt_ust_session *usess, + struct ust_app *app) +{ + int ret = 0; + struct ust_app_session *ua_sess = NULL; + + /* + * The application's configuration should only be synchronized for + * active sessions. + */ + LTTNG_ASSERT(usess->active); + + ret = find_or_create_ust_app_session(usess, app, &ua_sess, NULL); + if (ret < 0) { + /* Tracer is probably gone or ENOMEM. */ + if (ua_sess) { + destroy_app_session(app, ua_sess); + } + goto end; + } + LTTNG_ASSERT(ua_sess); + + pthread_mutex_lock(&ua_sess->lock); + if (ua_sess->deleted) { + goto deleted_session; + } + + rcu_read_lock(); + + ust_app_synchronize_all_channels(usess, ua_sess, app); + + /* + * Create the metadata for the application. This returns gracefully if a + * metadata was already set for the session. + * + * The metadata channel must be created after the data channels as the + * consumer daemon assumes this ordering. When interacting with a relay + * daemon, the consumer will use this assumption to send the + * "STREAMS_SENT" message to the relay daemon. + */ + ret = create_ust_app_metadata(ua_sess, app, usess->consumer); + if (ret < 0) { + ERR("Metadata creation failed for app sock %d for session id %" PRIu64, + app->sock, usess->id); + } + + rcu_read_unlock(); + +deleted_session: + pthread_mutex_unlock(&ua_sess->lock); +end: + return; +} + +static +void ust_app_global_destroy(struct ltt_ust_session *usess, struct ust_app *app) +{ + struct ust_app_session *ua_sess; + + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + return; + } + destroy_app_session(app, ua_sess); +} + +/* + * Add channels/events from UST global domain to registered apps at sock. + * + * Called with session lock held. + * Called with RCU read-side lock held. + */ +void ust_app_global_update(struct ltt_ust_session *usess, struct ust_app *app) +{ + LTTNG_ASSERT(usess); + LTTNG_ASSERT(usess->active); + + DBG2("UST app global update for app sock %d for session id %" PRIu64, + app->sock, usess->id); + + if (!app->compatible) { + return; + } + if (trace_ust_id_tracker_lookup(LTTNG_PROCESS_ATTR_VIRTUAL_PROCESS_ID, + usess, app->pid) && + trace_ust_id_tracker_lookup( + LTTNG_PROCESS_ATTR_VIRTUAL_USER_ID, + usess, app->uid) && + trace_ust_id_tracker_lookup( + LTTNG_PROCESS_ATTR_VIRTUAL_GROUP_ID, + usess, app->gid)) { + /* + * Synchronize the application's internal tracing configuration + * and start tracing. + */ + ust_app_synchronize(usess, app); + ust_app_start_trace(usess, app); + } else { + ust_app_global_destroy(usess, app); + } +} + +/* + * Add all event notifiers to an application. + * + * Called with session lock held. + * Called with RCU read-side lock held. + */ +void ust_app_global_update_event_notifier_rules(struct ust_app *app) +{ + DBG2("UST application global event notifier rules update: app = '%s', pid = %d)", + app->name, app->pid); + + if (!app->compatible || !ust_app_supports_notifiers(app)) { + return; + } + + if (app->event_notifier_group.object == NULL) { + WARN("UST app global update of event notifiers for app skipped since communication handle is null: app = '%s' pid = %d)", + app->name, app->pid); + return; + } + + ust_app_synchronize_event_notifier_rules(app); +} + +/* + * Called with session lock held. + */ +void ust_app_global_update_all(struct ltt_ust_session *usess) +{ + 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(usess, app); + } + rcu_read_unlock(); +} + +void ust_app_global_update_all_event_notifier_rules(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_event_notifier_rules(app); + } + + rcu_read_unlock(); +} + +/* + * Add context to a specific channel for global UST domain. + */ +int ust_app_add_ctx_channel_glb(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan, struct ltt_ust_context *uctx) +{ + int ret = 0; + struct lttng_ht_node_str *ua_chan_node; + struct lttng_ht_iter iter, uiter; + struct ust_app_channel *ua_chan = NULL; + struct ust_app_session *ua_sess; + struct ust_app *app; + + LTTNG_ASSERT(usess->active); + + rcu_read_lock(); + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + if (!app->compatible) { + /* + * TODO: In time, we should notice the caller of this error by + * telling him that this is a version error. + */ + continue; + } + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + continue; + } + + pthread_mutex_lock(&ua_sess->lock); + + if (ua_sess->deleted) { + pthread_mutex_unlock(&ua_sess->lock); + continue; + } + + /* Lookup channel in the ust app session */ + lttng_ht_lookup(ua_sess->channels, (void *)uchan->name, &uiter); + ua_chan_node = lttng_ht_iter_get_node_str(&uiter); + if (ua_chan_node == NULL) { + goto next_app; + } + ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, + node); + ret = create_ust_app_channel_context(ua_chan, &uctx->ctx, app); + if (ret < 0) { + goto next_app; + } + next_app: + pthread_mutex_unlock(&ua_sess->lock); + } + + rcu_read_unlock(); + return ret; +} + +/* + * Receive registration and populate the given msg structure. + * + * On success return 0 else a negative value returned by the ustctl call. + */ +int ust_app_recv_registration(int sock, struct ust_register_msg *msg) +{ + int ret; + uint32_t pid, ppid, uid, gid; + + LTTNG_ASSERT(msg); + + ret = lttng_ust_ctl_recv_reg_msg(sock, &msg->type, &msg->major, &msg->minor, + &pid, &ppid, &uid, &gid, + &msg->bits_per_long, + &msg->uint8_t_alignment, + &msg->uint16_t_alignment, + &msg->uint32_t_alignment, + &msg->uint64_t_alignment, + &msg->long_alignment, + &msg->byte_order, + msg->name); + if (ret < 0) { + switch (-ret) { + case EPIPE: + case ECONNRESET: + case LTTNG_UST_ERR_EXITING: + DBG3("UST app recv reg message failed. Application died"); + break; + case LTTNG_UST_ERR_UNSUP_MAJOR: + ERR("UST app recv reg unsupported version %d.%d. Supporting %d.%d", + msg->major, msg->minor, LTTNG_UST_ABI_MAJOR_VERSION, + LTTNG_UST_ABI_MINOR_VERSION); + break; + default: + ERR("UST app recv reg message failed with ret %d", ret); + break; + } + goto error; + } + msg->pid = (pid_t) pid; + msg->ppid = (pid_t) ppid; + msg->uid = (uid_t) uid; + msg->gid = (gid_t) gid; + +error: + return ret; +} + +/* + * Return a ust app session object using the application object and the + * session object descriptor has a key. If not found, NULL is returned. + * A RCU read side lock MUST be acquired when calling this function. +*/ +static struct ust_app_session *find_session_by_objd(struct ust_app *app, + int objd) +{ + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter iter; + struct ust_app_session *ua_sess = NULL; + + LTTNG_ASSERT(app); + + lttng_ht_lookup(app->ust_sessions_objd, (void *)((unsigned long) objd), &iter); + node = lttng_ht_iter_get_node_ulong(&iter); + if (node == NULL) { + DBG2("UST app session find by objd %d not found", objd); + goto error; + } + + ua_sess = caa_container_of(node, struct ust_app_session, ust_objd_node); + +error: + return ua_sess; +} + +/* + * Return a ust app channel object using the application object and the channel + * object descriptor has a key. If not found, NULL is returned. A RCU read side + * lock MUST be acquired before calling this function. + */ +static struct ust_app_channel *find_channel_by_objd(struct ust_app *app, + int objd) +{ + struct lttng_ht_node_ulong *node; + struct lttng_ht_iter iter; + struct ust_app_channel *ua_chan = NULL; + + LTTNG_ASSERT(app); + + lttng_ht_lookup(app->ust_objd, (void *)((unsigned long) objd), &iter); + node = lttng_ht_iter_get_node_ulong(&iter); + if (node == NULL) { + DBG2("UST app channel find by objd %d not found", objd); + goto error; + } + + ua_chan = caa_container_of(node, struct ust_app_channel, ust_objd_node); + +error: + return ua_chan; +} + +/* + * Reply to a register channel notification from an application on the notify + * socket. The channel metadata is also created. + * + * The session UST registry lock is acquired in this function. + * + * On success 0 is returned else a negative value. + */ +static int reply_ust_register_channel(int sock, int cobjd, + size_t nr_fields, struct lttng_ust_ctl_field *fields) +{ + int ret, ret_code = 0; + uint32_t chan_id; + uint64_t chan_reg_key; + enum lttng_ust_ctl_channel_header type; + struct ust_app *app; + struct ust_app_channel *ua_chan; + struct ust_app_session *ua_sess; + struct ust_registry_session *registry; + struct ust_registry_channel *ust_reg_chan; + + rcu_read_lock(); + + /* Lookup application. If not found, there is a code flow error. */ + app = find_app_by_notify_sock(sock); + if (!app) { + DBG("Application socket %d is being torn down. Abort event notify", + sock); + ret = -1; + goto error_rcu_unlock; + } + + /* Lookup channel by UST object descriptor. */ + ua_chan = find_channel_by_objd(app, cobjd); + if (!ua_chan) { + DBG("Application channel is being torn down. Abort event notify"); + ret = 0; + goto error_rcu_unlock; + } + + LTTNG_ASSERT(ua_chan->session); + ua_sess = ua_chan->session; + + /* Get right session registry depending on the session buffer type. */ + registry = get_session_registry(ua_sess); + if (!registry) { + DBG("Application session is being torn down. Abort event notify"); + ret = 0; + goto error_rcu_unlock; + }; + + /* Depending on the buffer type, a different channel key is used. */ + if (ua_sess->buffer_type == LTTNG_BUFFER_PER_UID) { + chan_reg_key = ua_chan->tracing_channel_id; + } else { + chan_reg_key = ua_chan->key; + } + + pthread_mutex_lock(®istry->lock); + + ust_reg_chan = ust_registry_channel_find(registry, chan_reg_key); + LTTNG_ASSERT(ust_reg_chan); + + if (!ust_reg_chan->register_done) { + /* + * TODO: eventually use the registry event count for + * this channel to better guess header type for per-pid + * buffers. + */ + type = LTTNG_UST_CTL_CHANNEL_HEADER_LARGE; + ust_reg_chan->nr_ctx_fields = nr_fields; + ust_reg_chan->ctx_fields = fields; + fields = NULL; + ust_reg_chan->header_type = type; + } else { + /* Get current already assigned values. */ + type = ust_reg_chan->header_type; + } + /* Channel id is set during the object creation. */ + chan_id = ust_reg_chan->chan_id; + + /* Append to metadata */ + if (!ust_reg_chan->metadata_dumped) { + ret_code = ust_metadata_channel_statedump(registry, ust_reg_chan); + if (ret_code) { + ERR("Error appending channel metadata (errno = %d)", ret_code); + goto reply; + } + } + +reply: + DBG3("UST app replying to register channel key %" PRIu64 + " with id %u, type = %d, ret = %d", chan_reg_key, chan_id, type, + ret_code); + + ret = lttng_ust_ctl_reply_register_channel(sock, chan_id, type, ret_code); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app reply channel failed. Application died: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app reply channel failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app reply channel failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + goto error; + } + + /* This channel registry registration is completed. */ + ust_reg_chan->register_done = 1; + +error: + pthread_mutex_unlock(®istry->lock); +error_rcu_unlock: + rcu_read_unlock(); + free(fields); + return ret; +} + +/* + * Add event to the UST channel registry. When the event is added to the + * registry, the metadata is also created. Once done, this replies to the + * application with the appropriate error code. + * + * The session UST registry lock is acquired in the function. + * + * On success 0 is returned else a negative value. + */ +static int add_event_ust_registry(int sock, int sobjd, int cobjd, char *name, + char *sig, size_t nr_fields, struct lttng_ust_ctl_field *fields, + int loglevel_value, char *model_emf_uri) +{ + int ret, ret_code; + uint32_t event_id = 0; + uint64_t chan_reg_key; + struct ust_app *app; + struct ust_app_channel *ua_chan; + struct ust_app_session *ua_sess; + struct ust_registry_session *registry; + + rcu_read_lock(); + + /* Lookup application. If not found, there is a code flow error. */ + app = find_app_by_notify_sock(sock); + if (!app) { + DBG("Application socket %d is being torn down. Abort event notify", + sock); + ret = -1; + goto error_rcu_unlock; + } + + /* Lookup channel by UST object descriptor. */ + ua_chan = find_channel_by_objd(app, cobjd); + if (!ua_chan) { + DBG("Application channel is being torn down. Abort event notify"); + ret = 0; + goto error_rcu_unlock; + } + + LTTNG_ASSERT(ua_chan->session); + ua_sess = ua_chan->session; + + registry = get_session_registry(ua_sess); + if (!registry) { + DBG("Application session is being torn down. Abort event notify"); + ret = 0; + goto error_rcu_unlock; + } + + if (ua_sess->buffer_type == LTTNG_BUFFER_PER_UID) { + chan_reg_key = ua_chan->tracing_channel_id; + } else { + chan_reg_key = ua_chan->key; + } + + pthread_mutex_lock(®istry->lock); + + /* + * From this point on, this call acquires the ownership of the sig, fields + * and model_emf_uri meaning any free are done inside it if needed. These + * three variables MUST NOT be read/write after this. + */ + ret_code = ust_registry_create_event(registry, chan_reg_key, + sobjd, cobjd, name, sig, nr_fields, fields, + loglevel_value, model_emf_uri, ua_sess->buffer_type, + &event_id, app); + sig = NULL; + fields = NULL; + model_emf_uri = NULL; + + /* + * The return value is returned to ustctl so in case of an error, the + * application can be notified. In case of an error, it's important not to + * return a negative error or else the application will get closed. + */ + ret = lttng_ust_ctl_reply_register_event(sock, event_id, ret_code); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app reply event failed. Application died: pid = %d, sock = %d.", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app reply event failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app reply event failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + /* + * No need to wipe the create event since the application socket will + * get close on error hence cleaning up everything by itself. + */ + goto error; + } + + DBG3("UST registry event %s with id %" PRId32 " added successfully", + name, event_id); + +error: + pthread_mutex_unlock(®istry->lock); +error_rcu_unlock: + rcu_read_unlock(); + free(sig); + free(fields); + free(model_emf_uri); + return ret; +} + +/* + * Add enum to the UST session registry. Once done, this replies to the + * application with the appropriate error code. + * + * The session UST registry lock is acquired within this function. + * + * On success 0 is returned else a negative value. + */ +static int add_enum_ust_registry(int sock, int sobjd, char *name, + struct lttng_ust_ctl_enum_entry *entries, size_t nr_entries) +{ + int ret = 0, ret_code; + struct ust_app *app; + struct ust_app_session *ua_sess; + struct ust_registry_session *registry; + uint64_t enum_id = -1ULL; + + rcu_read_lock(); + + /* Lookup application. If not found, there is a code flow error. */ + app = find_app_by_notify_sock(sock); + if (!app) { + /* Return an error since this is not an error */ + DBG("Application socket %d is being torn down. Aborting enum registration", + sock); + free(entries); + ret = -1; + goto error_rcu_unlock; + } + + /* Lookup session by UST object descriptor. */ + ua_sess = find_session_by_objd(app, sobjd); + if (!ua_sess) { + /* Return an error since this is not an error */ + DBG("Application session is being torn down (session not found). Aborting enum registration."); + free(entries); + goto error_rcu_unlock; + } + + registry = get_session_registry(ua_sess); + if (!registry) { + DBG("Application session is being torn down (registry not found). Aborting enum registration."); + free(entries); + goto error_rcu_unlock; + } + + pthread_mutex_lock(®istry->lock); + + /* + * From this point on, the callee acquires the ownership of + * entries. The variable entries MUST NOT be read/written after + * call. + */ + ret_code = ust_registry_create_or_find_enum(registry, sobjd, name, + entries, nr_entries, &enum_id); + entries = NULL; + + /* + * The return value is returned to ustctl so in case of an error, the + * application can be notified. In case of an error, it's important not to + * return a negative error or else the application will get closed. + */ + ret = lttng_ust_ctl_reply_register_enum(sock, enum_id, ret_code); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app reply enum failed. Application died: pid = %d, sock = %d", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app reply enum failed. Communication time out: pid = %d, sock = %d", + app->pid, app->sock); + } else { + ERR("UST app reply enum failed with ret %d: pid = %d, sock = %d", + ret, app->pid, app->sock); + } + /* + * No need to wipe the create enum since the application socket will + * get close on error hence cleaning up everything by itself. + */ + goto error; + } + + DBG3("UST registry enum %s added successfully or already found", name); + +error: + pthread_mutex_unlock(®istry->lock); +error_rcu_unlock: + rcu_read_unlock(); + return ret; +} + +/* + * Handle application notification through the given notify socket. + * + * Return 0 on success or else a negative value. + */ +int ust_app_recv_notify(int sock) +{ + int ret; + enum lttng_ust_ctl_notify_cmd cmd; + + DBG3("UST app receiving notify from sock %d", sock); + + ret = lttng_ust_ctl_recv_notify(sock, &cmd); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app recv notify failed. Application died: sock = %d", + sock); + } else if (ret == -EAGAIN) { + WARN("UST app recv notify failed. Communication time out: sock = %d", + sock); + } else { + ERR("UST app recv notify failed with ret %d: sock = %d", + ret, sock); + } + goto error; + } + + switch (cmd) { + case LTTNG_UST_CTL_NOTIFY_CMD_EVENT: + { + int sobjd, cobjd, loglevel_value; + char name[LTTNG_UST_ABI_SYM_NAME_LEN], *sig, *model_emf_uri; + size_t nr_fields; + struct lttng_ust_ctl_field *fields; + + DBG2("UST app ustctl register event received"); + + ret = lttng_ust_ctl_recv_register_event(sock, &sobjd, &cobjd, name, + &loglevel_value, &sig, &nr_fields, &fields, + &model_emf_uri); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app recv event failed. Application died: sock = %d", + sock); + } else if (ret == -EAGAIN) { + WARN("UST app recv event failed. Communication time out: sock = %d", + sock); + } else { + ERR("UST app recv event failed with ret %d: sock = %d", + ret, sock); + } + goto error; + } + + /* + * Add event to the UST registry coming from the notify socket. This + * call will free if needed the sig, fields and model_emf_uri. This + * code path loses the ownsership of these variables and transfer them + * to the this function. + */ + ret = add_event_ust_registry(sock, sobjd, cobjd, name, sig, nr_fields, + fields, loglevel_value, model_emf_uri); + if (ret < 0) { + goto error; + } + + break; + } + case LTTNG_UST_CTL_NOTIFY_CMD_CHANNEL: + { + int sobjd, cobjd; + size_t nr_fields; + struct lttng_ust_ctl_field *fields; + + DBG2("UST app ustctl register channel received"); + + ret = lttng_ust_ctl_recv_register_channel(sock, &sobjd, &cobjd, &nr_fields, + &fields); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app recv channel failed. Application died: sock = %d", + sock); + } else if (ret == -EAGAIN) { + WARN("UST app recv channel failed. Communication time out: sock = %d", + sock); + } else { + ERR("UST app recv channel failed with ret %d: sock = %d)", + ret, sock); + } + goto error; + } + + /* + * The fields ownership are transfered to this function call meaning + * that if needed it will be freed. After this, it's invalid to access + * fields or clean it up. + */ + ret = reply_ust_register_channel(sock, cobjd, nr_fields, + fields); + if (ret < 0) { + goto error; + } + + break; + } + case LTTNG_UST_CTL_NOTIFY_CMD_ENUM: + { + int sobjd; + char name[LTTNG_UST_ABI_SYM_NAME_LEN]; + size_t nr_entries; + struct lttng_ust_ctl_enum_entry *entries; + + DBG2("UST app ustctl register enum received"); + + ret = lttng_ust_ctl_recv_register_enum(sock, &sobjd, name, + &entries, &nr_entries); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app recv enum failed. Application died: sock = %d", + sock); + } else if (ret == -EAGAIN) { + WARN("UST app recv enum failed. Communication time out: sock = %d", + sock); + } else { + ERR("UST app recv enum failed with ret %d: sock = %d", + ret, sock); + } + goto error; + } + + /* Callee assumes ownership of entries */ + ret = add_enum_ust_registry(sock, sobjd, name, + entries, nr_entries); + if (ret < 0) { + goto error; + } + + break; + } + default: + /* Should NEVER happen. */ + abort(); + } + +error: + return ret; +} + +/* + * Once the notify socket hangs up, this is called. First, it tries to find the + * corresponding application. On failure, the call_rcu to close the socket is + * executed. If an application is found, it tries to delete it from the notify + * socket hash table. Whathever the result, it proceeds to the call_rcu. + * + * Note that an object needs to be allocated here so on ENOMEM failure, the + * call RCU is not done but the rest of the cleanup is. + */ +void ust_app_notify_sock_unregister(int sock) +{ + int err_enomem = 0; + struct lttng_ht_iter iter; + struct ust_app *app; + struct ust_app_notify_sock_obj *obj; + + LTTNG_ASSERT(sock >= 0); + + rcu_read_lock(); + + obj = (ust_app_notify_sock_obj *) zmalloc(sizeof(*obj)); + if (!obj) { + /* + * An ENOMEM is kind of uncool. If this strikes we continue the + * procedure but the call_rcu will not be called. In this case, we + * accept the fd leak rather than possibly creating an unsynchronized + * state between threads. + * + * TODO: The notify object should be created once the notify socket is + * registered and stored independantely from the ust app object. The + * tricky part is to synchronize the teardown of the application and + * this notify object. Let's keep that in mind so we can avoid this + * kind of shenanigans with ENOMEM in the teardown path. + */ + err_enomem = 1; + } else { + obj->fd = sock; + } + + DBG("UST app notify socket unregister %d", sock); + + /* + * Lookup application by notify socket. If this fails, this means that the + * hash table delete has already been done by the application + * unregistration process so we can safely close the notify socket in a + * call RCU. + */ + app = find_app_by_notify_sock(sock); + if (!app) { + goto close_socket; + } + + iter.iter.node = &app->notify_sock_n.node; + + /* + * Whatever happens here either we fail or succeed, in both cases we have + * to close the socket after a grace period to continue to the call RCU + * here. If the deletion is successful, the application is not visible + * anymore by other threads and is it fails it means that it was already + * deleted from the hash table so either way we just have to close the + * socket. + */ + (void) lttng_ht_del(ust_app_ht_by_notify_sock, &iter); + +close_socket: + rcu_read_unlock(); + + /* + * Close socket after a grace period to avoid for the socket to be reused + * before the application object is freed creating potential race between + * threads trying to add unique in the global hash table. + */ + if (!err_enomem) { + call_rcu(&obj->head, close_notify_sock_rcu); + } +} + +/* + * Destroy a ust app data structure and free its memory. + */ +void ust_app_destroy(struct ust_app *app) +{ + if (!app) { + return; + } + + call_rcu(&app->pid_n.head, delete_ust_app_rcu); +} + +/* + * Take a snapshot for a given UST session. The snapshot is sent to the given + * output. + * + * Returns LTTNG_OK on success or a LTTNG_ERR error code. + */ +enum lttng_error_code ust_app_snapshot_record( + const struct ltt_ust_session *usess, + const struct consumer_output *output, int wait, + uint64_t nb_packets_per_stream) +{ + int ret = 0; + enum lttng_error_code status = LTTNG_OK; + struct lttng_ht_iter iter; + struct ust_app *app; + char *trace_path = NULL; + + LTTNG_ASSERT(usess); + LTTNG_ASSERT(output); + + rcu_read_lock(); + + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_UID: + { + struct buffer_reg_uid *reg; + + cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { + struct buffer_reg_channel *buf_reg_chan; + struct consumer_socket *socket; + char pathname[PATH_MAX]; + size_t consumer_path_offset = 0; + + if (!reg->registry->reg.ust->metadata_key) { + /* Skip since no metadata is present */ + continue; + } + + /* Get consumer socket to use to push the metadata.*/ + socket = consumer_find_socket_by_bitness(reg->bits_per_long, + usess->consumer); + if (!socket) { + status = LTTNG_ERR_INVALID; + goto error; + } + + memset(pathname, 0, sizeof(pathname)); + ret = snprintf(pathname, sizeof(pathname), + DEFAULT_UST_TRACE_UID_PATH, + reg->uid, reg->bits_per_long); + if (ret < 0) { + PERROR("snprintf snapshot path"); + status = LTTNG_ERR_INVALID; + goto error; + } + /* Free path allowed on previous iteration. */ + free(trace_path); + trace_path = setup_channel_trace_path(usess->consumer, pathname, + &consumer_path_offset); + if (!trace_path) { + status = LTTNG_ERR_INVALID; + goto error; + } + /* Add the UST default trace dir to path. */ + cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, + buf_reg_chan, node.node) { + status = consumer_snapshot_channel(socket, + buf_reg_chan->consumer_key, + output, 0, usess->uid, + usess->gid, &trace_path[consumer_path_offset], wait, + nb_packets_per_stream); + if (status != LTTNG_OK) { + goto error; + } + } + status = consumer_snapshot_channel(socket, + reg->registry->reg.ust->metadata_key, output, 1, + usess->uid, usess->gid, &trace_path[consumer_path_offset], + wait, 0); + if (status != LTTNG_OK) { + goto error; + } + } + break; + } + case LTTNG_BUFFER_PER_PID: + { + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + struct consumer_socket *socket; + struct lttng_ht_iter chan_iter; + struct ust_app_channel *ua_chan; + struct ust_app_session *ua_sess; + struct ust_registry_session *registry; + char pathname[PATH_MAX]; + size_t consumer_path_offset = 0; + + ua_sess = lookup_session_by_app(usess, app); + if (!ua_sess) { + /* Session not associated with this app. */ + continue; + } + + /* Get the right consumer socket for the application. */ + socket = consumer_find_socket_by_bitness(app->bits_per_long, + output); + if (!socket) { + status = LTTNG_ERR_INVALID; + goto error; + } + + /* Add the UST default trace dir to path. */ + memset(pathname, 0, sizeof(pathname)); + ret = snprintf(pathname, sizeof(pathname), "%s", + ua_sess->path); + if (ret < 0) { + status = LTTNG_ERR_INVALID; + PERROR("snprintf snapshot path"); + goto error; + } + /* Free path allowed on previous iteration. */ + free(trace_path); + trace_path = setup_channel_trace_path(usess->consumer, pathname, + &consumer_path_offset); + if (!trace_path) { + status = LTTNG_ERR_INVALID; + goto error; + } + cds_lfht_for_each_entry(ua_sess->channels->ht, &chan_iter.iter, + ua_chan, node.node) { + status = consumer_snapshot_channel(socket, + ua_chan->key, output, 0, + lttng_credentials_get_uid(&ua_sess->effective_credentials), + lttng_credentials_get_gid(&ua_sess->effective_credentials), + &trace_path[consumer_path_offset], wait, + nb_packets_per_stream); + switch (status) { + case LTTNG_OK: + break; + case LTTNG_ERR_CHAN_NOT_FOUND: + continue; + default: + goto error; + } + } + + registry = get_session_registry(ua_sess); + if (!registry) { + DBG("Application session is being torn down. Skip application."); + continue; + } + status = consumer_snapshot_channel(socket, + registry->metadata_key, output, 1, + lttng_credentials_get_uid(&ua_sess->effective_credentials), + lttng_credentials_get_gid(&ua_sess->effective_credentials), + &trace_path[consumer_path_offset], wait, 0); + switch (status) { + case LTTNG_OK: + break; + case LTTNG_ERR_CHAN_NOT_FOUND: + continue; + default: + goto error; + } + } + break; + } + default: + abort(); + break; + } + +error: + free(trace_path); + rcu_read_unlock(); + return status; +} + +/* + * Return the size taken by one more packet per stream. + */ +uint64_t ust_app_get_size_one_more_packet_per_stream( + const struct ltt_ust_session *usess, uint64_t cur_nr_packets) +{ + uint64_t tot_size = 0; + struct ust_app *app; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(usess); + + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_UID: + { + struct buffer_reg_uid *reg; + + cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { + struct buffer_reg_channel *buf_reg_chan; + + rcu_read_lock(); + cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, + buf_reg_chan, node.node) { + if (cur_nr_packets >= buf_reg_chan->num_subbuf) { + /* + * Don't take channel into account if we + * already grab all its packets. + */ + continue; + } + tot_size += buf_reg_chan->subbuf_size * buf_reg_chan->stream_count; + } + rcu_read_unlock(); + } + break; + } + case LTTNG_BUFFER_PER_PID: + { + rcu_read_lock(); + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + struct ust_app_channel *ua_chan; + struct ust_app_session *ua_sess; + struct lttng_ht_iter chan_iter; + + ua_sess = lookup_session_by_app(usess, app); + if (!ua_sess) { + /* Session not associated with this app. */ + continue; + } + + cds_lfht_for_each_entry(ua_sess->channels->ht, &chan_iter.iter, + ua_chan, node.node) { + if (cur_nr_packets >= ua_chan->attr.num_subbuf) { + /* + * Don't take channel into account if we + * already grab all its packets. + */ + continue; + } + tot_size += ua_chan->attr.subbuf_size * ua_chan->streams.count; + } + } + rcu_read_unlock(); + break; + } + default: + abort(); + break; + } + + return tot_size; +} + +int ust_app_uid_get_channel_runtime_stats(uint64_t ust_session_id, + struct cds_list_head *buffer_reg_uid_list, + struct consumer_output *consumer, uint64_t uchan_id, + int overwrite, uint64_t *discarded, uint64_t *lost) +{ + int ret; + uint64_t consumer_chan_key; + + *discarded = 0; + *lost = 0; + + ret = buffer_reg_uid_consumer_channel_key( + buffer_reg_uid_list, uchan_id, &consumer_chan_key); + if (ret < 0) { + /* Not found */ + ret = 0; + goto end; + } + + if (overwrite) { + ret = consumer_get_lost_packets(ust_session_id, + consumer_chan_key, consumer, lost); + } else { + ret = consumer_get_discarded_events(ust_session_id, + consumer_chan_key, consumer, discarded); + } + +end: + return ret; +} + +int ust_app_pid_get_channel_runtime_stats(struct ltt_ust_session *usess, + struct ltt_ust_channel *uchan, + struct consumer_output *consumer, int overwrite, + uint64_t *discarded, uint64_t *lost) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct lttng_ht_node_str *ua_chan_node; + struct ust_app *app; + struct ust_app_session *ua_sess; + struct ust_app_channel *ua_chan; + + *discarded = 0; + *lost = 0; + + rcu_read_lock(); + /* + * Iterate over every registered applications. Sum counters for + * all applications containing requested session and channel. + */ + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + struct lttng_ht_iter uiter; + + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + continue; + } + + /* Get channel */ + lttng_ht_lookup(ua_sess->channels, (void *) uchan->name, &uiter); + ua_chan_node = lttng_ht_iter_get_node_str(&uiter); + /* If the session is found for the app, the channel must be there */ + LTTNG_ASSERT(ua_chan_node); + + ua_chan = caa_container_of(ua_chan_node, struct ust_app_channel, node); + + if (overwrite) { + uint64_t _lost; + + ret = consumer_get_lost_packets(usess->id, ua_chan->key, + consumer, &_lost); + if (ret < 0) { + break; + } + (*lost) += _lost; + } else { + uint64_t _discarded; + + ret = consumer_get_discarded_events(usess->id, + ua_chan->key, consumer, &_discarded); + if (ret < 0) { + break; + } + (*discarded) += _discarded; + } + } + + rcu_read_unlock(); + return ret; +} + +static +int ust_app_regenerate_statedump(struct ltt_ust_session *usess, + struct ust_app *app) +{ + int ret = 0; + struct ust_app_session *ua_sess; + + DBG("Regenerating the metadata for ust app pid %d", app->pid); + + rcu_read_lock(); + + ua_sess = lookup_session_by_app(usess, app); + if (ua_sess == NULL) { + /* The session is in teardown process. Ignore and continue. */ + goto end; + } + + pthread_mutex_lock(&ua_sess->lock); + + if (ua_sess->deleted) { + goto end_unlock; + } + + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_regenerate_statedump(app->sock, ua_sess->handle); + pthread_mutex_unlock(&app->sock_lock); + +end_unlock: + pthread_mutex_unlock(&ua_sess->lock); + +end: + rcu_read_unlock(); + health_code_update(); + return ret; +} + +/* + * Regenerate the statedump for each app in the session. + */ +int ust_app_regenerate_statedump_all(struct ltt_ust_session *usess) +{ + int ret = 0; + struct lttng_ht_iter iter; + struct ust_app *app; + + DBG("Regenerating the metadata for all UST apps"); + + rcu_read_lock(); + + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + if (!app->compatible) { + continue; + } + + ret = ust_app_regenerate_statedump(usess, app); + if (ret < 0) { + /* Continue to the next app even on error */ + continue; + } + } + + rcu_read_unlock(); + + return 0; +} + +/* + * Rotate all the channels of a session. + * + * Return LTTNG_OK on success or else an LTTng error code. + */ +enum lttng_error_code ust_app_rotate_session(struct ltt_session *session) +{ + int ret; + enum lttng_error_code cmd_ret = LTTNG_OK; + struct lttng_ht_iter iter; + struct ust_app *app; + struct ltt_ust_session *usess = session->ust_session; + + LTTNG_ASSERT(usess); + + rcu_read_lock(); + + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_UID: + { + struct buffer_reg_uid *reg; + + cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { + struct buffer_reg_channel *buf_reg_chan; + struct consumer_socket *socket; + + /* Get consumer socket to use to push the metadata.*/ + socket = consumer_find_socket_by_bitness(reg->bits_per_long, + usess->consumer); + if (!socket) { + cmd_ret = LTTNG_ERR_INVALID; + goto error; + } + + /* Rotate the data channels. */ + cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, + buf_reg_chan, node.node) { + ret = consumer_rotate_channel(socket, + buf_reg_chan->consumer_key, + usess->uid, usess->gid, + usess->consumer, + /* is_metadata_channel */ false); + if (ret < 0) { + cmd_ret = LTTNG_ERR_ROTATION_FAIL_CONSUMER; + goto error; + } + } + + /* + * The metadata channel might not be present. + * + * Consumer stream allocation can be done + * asynchronously and can fail on intermediary + * operations (i.e add context) and lead to data + * channels created with no metadata channel. + */ + if (!reg->registry->reg.ust->metadata_key) { + /* Skip since no metadata is present. */ + continue; + } + + (void) push_metadata(reg->registry->reg.ust, usess->consumer); + + ret = consumer_rotate_channel(socket, + reg->registry->reg.ust->metadata_key, + usess->uid, usess->gid, + usess->consumer, + /* is_metadata_channel */ true); + if (ret < 0) { + cmd_ret = LTTNG_ERR_ROTATION_FAIL_CONSUMER; + goto error; + } + } + break; + } + case LTTNG_BUFFER_PER_PID: + { + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + struct consumer_socket *socket; + struct lttng_ht_iter chan_iter; + struct ust_app_channel *ua_chan; + struct ust_app_session *ua_sess; + struct ust_registry_session *registry; + + ua_sess = lookup_session_by_app(usess, app); + if (!ua_sess) { + /* Session not associated with this app. */ + continue; + } + + /* Get the right consumer socket for the application. */ + socket = consumer_find_socket_by_bitness(app->bits_per_long, + usess->consumer); + if (!socket) { + cmd_ret = LTTNG_ERR_INVALID; + goto error; + } + + registry = get_session_registry(ua_sess); + if (!registry) { + DBG("Application session is being torn down. Skip application."); + continue; + } + + /* Rotate the data channels. */ + cds_lfht_for_each_entry(ua_sess->channels->ht, &chan_iter.iter, + ua_chan, node.node) { + ret = consumer_rotate_channel(socket, + ua_chan->key, + lttng_credentials_get_uid(&ua_sess->effective_credentials), + lttng_credentials_get_gid(&ua_sess->effective_credentials), + ua_sess->consumer, + /* is_metadata_channel */ false); + if (ret < 0) { + /* Per-PID buffer and application going away. */ + if (ret == -LTTNG_ERR_CHAN_NOT_FOUND) + continue; + cmd_ret = LTTNG_ERR_ROTATION_FAIL_CONSUMER; + goto error; + } + } + + /* Rotate the metadata channel. */ + (void) push_metadata(registry, usess->consumer); + ret = consumer_rotate_channel(socket, + registry->metadata_key, + lttng_credentials_get_uid(&ua_sess->effective_credentials), + lttng_credentials_get_gid(&ua_sess->effective_credentials), + ua_sess->consumer, + /* is_metadata_channel */ true); + if (ret < 0) { + /* Per-PID buffer and application going away. */ + if (ret == -LTTNG_ERR_CHAN_NOT_FOUND) + continue; + cmd_ret = LTTNG_ERR_ROTATION_FAIL_CONSUMER; + goto error; + } + } + break; + } + default: + abort(); + break; + } + + cmd_ret = LTTNG_OK; + +error: + rcu_read_unlock(); + return cmd_ret; +} + +enum lttng_error_code ust_app_create_channel_subdirectories( + const struct ltt_ust_session *usess) +{ + enum lttng_error_code ret = LTTNG_OK; + struct lttng_ht_iter iter; + enum lttng_trace_chunk_status chunk_status; + char *pathname_index; + int fmt_ret; + + LTTNG_ASSERT(usess->current_trace_chunk); + rcu_read_lock(); + + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_UID: + { + struct buffer_reg_uid *reg; + + cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { + fmt_ret = asprintf(&pathname_index, + DEFAULT_UST_TRACE_DIR "/" DEFAULT_UST_TRACE_UID_PATH "/" DEFAULT_INDEX_DIR, + reg->uid, reg->bits_per_long); + if (fmt_ret < 0) { + ERR("Failed to format channel index directory"); + ret = LTTNG_ERR_CREATE_DIR_FAIL; + goto error; + } + + /* + * Create the index subdirectory which will take care + * of implicitly creating the channel's path. + */ + chunk_status = lttng_trace_chunk_create_subdirectory( + usess->current_trace_chunk, + pathname_index); + free(pathname_index); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = LTTNG_ERR_CREATE_DIR_FAIL; + goto error; + } + } + break; + } + case LTTNG_BUFFER_PER_PID: + { + struct ust_app *app; + + /* + * Create the toplevel ust/ directory in case no apps are running. + */ + chunk_status = lttng_trace_chunk_create_subdirectory( + usess->current_trace_chunk, + DEFAULT_UST_TRACE_DIR); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = LTTNG_ERR_CREATE_DIR_FAIL; + goto error; + } + + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, + pid_n.node) { + struct ust_app_session *ua_sess; + struct ust_registry_session *registry; + + ua_sess = lookup_session_by_app(usess, app); + if (!ua_sess) { + /* Session not associated with this app. */ + continue; + } + + registry = get_session_registry(ua_sess); + if (!registry) { + DBG("Application session is being torn down. Skip application."); + continue; + } + + fmt_ret = asprintf(&pathname_index, + DEFAULT_UST_TRACE_DIR "/%s/" DEFAULT_INDEX_DIR, + ua_sess->path); + if (fmt_ret < 0) { + ERR("Failed to format channel index directory"); + ret = LTTNG_ERR_CREATE_DIR_FAIL; + goto error; + } + /* + * Create the index subdirectory which will take care + * of implicitly creating the channel's path. + */ + chunk_status = lttng_trace_chunk_create_subdirectory( + usess->current_trace_chunk, + pathname_index); + free(pathname_index); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = LTTNG_ERR_CREATE_DIR_FAIL; + goto error; + } + } + break; + } + default: + abort(); + } + + ret = LTTNG_OK; +error: + rcu_read_unlock(); + return ret; +} + +/* + * Clear all the channels of a session. + * + * Return LTTNG_OK on success or else an LTTng error code. + */ +enum lttng_error_code ust_app_clear_session(struct ltt_session *session) +{ + int ret; + enum lttng_error_code cmd_ret = LTTNG_OK; + struct lttng_ht_iter iter; + struct ust_app *app; + struct ltt_ust_session *usess = session->ust_session; + + LTTNG_ASSERT(usess); + + rcu_read_lock(); + + if (usess->active) { + ERR("Expecting inactive session %s (%" PRIu64 ")", session->name, session->id); + cmd_ret = LTTNG_ERR_FATAL; + goto end; + } + + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_UID: + { + struct buffer_reg_uid *reg; + + cds_list_for_each_entry(reg, &usess->buffer_reg_uid_list, lnode) { + struct buffer_reg_channel *buf_reg_chan; + struct consumer_socket *socket; + + /* Get consumer socket to use to push the metadata.*/ + socket = consumer_find_socket_by_bitness(reg->bits_per_long, + usess->consumer); + if (!socket) { + cmd_ret = LTTNG_ERR_INVALID; + goto error_socket; + } + + /* Clear the data channels. */ + cds_lfht_for_each_entry(reg->registry->channels->ht, &iter.iter, + buf_reg_chan, node.node) { + ret = consumer_clear_channel(socket, + buf_reg_chan->consumer_key); + if (ret < 0) { + goto error; + } + } + + (void) push_metadata(reg->registry->reg.ust, usess->consumer); + + /* + * Clear the metadata channel. + * Metadata channel is not cleared per se but we still need to + * perform a rotation operation on it behind the scene. + */ + ret = consumer_clear_channel(socket, + reg->registry->reg.ust->metadata_key); + if (ret < 0) { + goto error; + } + } + break; + } + case LTTNG_BUFFER_PER_PID: + { + cds_lfht_for_each_entry(ust_app_ht->ht, &iter.iter, app, pid_n.node) { + struct consumer_socket *socket; + struct lttng_ht_iter chan_iter; + struct ust_app_channel *ua_chan; + struct ust_app_session *ua_sess; + struct ust_registry_session *registry; + + ua_sess = lookup_session_by_app(usess, app); + if (!ua_sess) { + /* Session not associated with this app. */ + continue; + } + + /* Get the right consumer socket for the application. */ + socket = consumer_find_socket_by_bitness(app->bits_per_long, + usess->consumer); + if (!socket) { + cmd_ret = LTTNG_ERR_INVALID; + goto error_socket; + } + + registry = get_session_registry(ua_sess); + if (!registry) { + DBG("Application session is being torn down. Skip application."); + continue; + } + + /* Clear the data channels. */ + cds_lfht_for_each_entry(ua_sess->channels->ht, &chan_iter.iter, + ua_chan, node.node) { + ret = consumer_clear_channel(socket, ua_chan->key); + if (ret < 0) { + /* Per-PID buffer and application going away. */ + if (ret == -LTTNG_ERR_CHAN_NOT_FOUND) { + continue; + } + goto error; + } + } + + (void) push_metadata(registry, usess->consumer); + + /* + * Clear the metadata channel. + * Metadata channel is not cleared per se but we still need to + * perform rotation operation on it behind the scene. + */ + ret = consumer_clear_channel(socket, registry->metadata_key); + if (ret < 0) { + /* Per-PID buffer and application going away. */ + if (ret == -LTTNG_ERR_CHAN_NOT_FOUND) { + continue; + } + goto error; + } + } + break; + } + default: + abort(); + break; + } + + cmd_ret = LTTNG_OK; + goto end; + +error: + switch (-ret) { + case LTTCOMM_CONSUMERD_RELAYD_CLEAR_DISALLOWED: + cmd_ret = LTTNG_ERR_CLEAR_RELAY_DISALLOWED; + break; + default: + cmd_ret = LTTNG_ERR_CLEAR_FAIL_CONSUMER; + } + +error_socket: +end: + rcu_read_unlock(); + return cmd_ret; +} + +/* + * This function skips the metadata channel as the begin/end timestamps of a + * metadata packet are useless. + * + * Moreover, opening a packet after a "clear" will cause problems for live + * sessions as it will introduce padding that was not part of the first trace + * chunk. The relay daemon expects the content of the metadata stream of + * successive metadata trace chunks to be strict supersets of one another. + * + * For example, flushing a packet at the beginning of the metadata stream of + * a trace chunk resulting from a "clear" session command will cause the + * size of the metadata stream of the new trace chunk to not match the size of + * the metadata stream of the original chunk. This will confuse the relay + * daemon as the same "offset" in a metadata stream will no longer point + * to the same content. + */ +enum lttng_error_code ust_app_open_packets(struct ltt_session *session) +{ + enum lttng_error_code ret = LTTNG_OK; + struct lttng_ht_iter iter; + struct ltt_ust_session *usess = session->ust_session; + + LTTNG_ASSERT(usess); + + rcu_read_lock(); + + switch (usess->buffer_type) { + case LTTNG_BUFFER_PER_UID: + { + struct buffer_reg_uid *reg; + + cds_list_for_each_entry ( + reg, &usess->buffer_reg_uid_list, lnode) { + struct buffer_reg_channel *buf_reg_chan; + struct consumer_socket *socket; + + socket = consumer_find_socket_by_bitness( + reg->bits_per_long, usess->consumer); + if (!socket) { + ret = LTTNG_ERR_FATAL; + goto error; + } + + cds_lfht_for_each_entry(reg->registry->channels->ht, + &iter.iter, buf_reg_chan, node.node) { + const int open_ret = + consumer_open_channel_packets( + socket, + buf_reg_chan->consumer_key); + + if (open_ret < 0) { + ret = LTTNG_ERR_UNK; + goto error; + } + } + } + break; + } + case LTTNG_BUFFER_PER_PID: + { + struct ust_app *app; + + cds_lfht_for_each_entry ( + ust_app_ht->ht, &iter.iter, app, pid_n.node) { + struct consumer_socket *socket; + struct lttng_ht_iter chan_iter; + struct ust_app_channel *ua_chan; + struct ust_app_session *ua_sess; + struct ust_registry_session *registry; + + ua_sess = lookup_session_by_app(usess, app); + if (!ua_sess) { + /* Session not associated with this app. */ + continue; + } + + /* Get the right consumer socket for the application. */ + socket = consumer_find_socket_by_bitness( + app->bits_per_long, usess->consumer); + if (!socket) { + ret = LTTNG_ERR_FATAL; + goto error; + } + + registry = get_session_registry(ua_sess); + if (!registry) { + DBG("Application session is being torn down. Skip application."); + continue; + } + + cds_lfht_for_each_entry(ua_sess->channels->ht, + &chan_iter.iter, ua_chan, node.node) { + const int open_ret = + consumer_open_channel_packets( + socket, + ua_chan->key); + + if (open_ret < 0) { + /* + * Per-PID buffer and application going + * away. + */ + if (open_ret == -LTTNG_ERR_CHAN_NOT_FOUND) { + continue; + } + + ret = LTTNG_ERR_UNK; + goto error; + } + } + } + break; + } + default: + abort(); + break; + } + +error: + rcu_read_unlock(); + return ret; +} diff --git a/src/bin/lttng-sessiond/ust-app.h b/src/bin/lttng-sessiond/ust-app.h index 77a365409..8d2d28986 100644 --- a/src/bin/lttng-sessiond/ust-app.h +++ b/src/bin/lttng-sessiond/ust-app.h @@ -572,7 +572,7 @@ static inline enum lttng_error_code ust_app_snapshot_record(struct ltt_ust_session *usess, const struct consumer_output *output, int wait, uint64_t max_stream_size) { - return 0; + return LTTNG_ERR_UNK; } static inline unsigned int ust_app_get_nb_stream(struct ltt_ust_session *usess) @@ -642,14 +642,14 @@ int ust_app_regenerate_statedump_all(struct ltt_ust_session *usess) static inline enum lttng_error_code ust_app_rotate_session(struct ltt_session *session) { - return 0; + return LTTNG_ERR_UNK; } static inline enum lttng_error_code ust_app_create_channel_subdirectories( const struct ltt_ust_session *session) { - return 0; + return LTTNG_ERR_UNK; } static inline @@ -661,13 +661,13 @@ int ust_app_release_object(struct ust_app *app, struct lttng_ust_abi_object_data static inline enum lttng_error_code ust_app_clear_session(struct ltt_session *session) { - return 0; + return LTTNG_ERR_UNK; } static inline enum lttng_error_code ust_app_open_packets(struct ltt_session *session) { - return 0; + return LTTNG_ERR_UNK; } #endif /* HAVE_LIBLTTNG_UST_CTL */ diff --git a/src/bin/lttng-sessiond/ust-consumer.c b/src/bin/lttng-sessiond/ust-consumer.c deleted file mode 100644 index a68c5d96d..000000000 --- a/src/bin/lttng-sessiond/ust-consumer.c +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "consumer.h" -#include "health-sessiond.h" -#include "ust-consumer.h" -#include "lttng-ust-error.h" -#include "buffer-registry.h" -#include "session.h" -#include "lttng-sessiond.h" - -/* - * Send a single channel to the consumer using command ASK_CHANNEL_CREATION. - * - * Consumer socket lock MUST be acquired before calling this. - */ -static int ask_channel_creation(struct ust_app_session *ua_sess, - struct ust_app_channel *ua_chan, - struct consumer_output *consumer, - struct consumer_socket *socket, - struct ust_registry_session *registry, - struct lttng_trace_chunk *trace_chunk) -{ - int ret, output; - uint32_t chan_id; - uint64_t key, chan_reg_key; - char *pathname = NULL; - struct lttcomm_consumer_msg msg; - struct ust_registry_channel *ust_reg_chan; - char shm_path[PATH_MAX] = ""; - char root_shm_path[PATH_MAX] = ""; - bool is_local_trace; - size_t consumer_path_offset = 0; - - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(ua_chan); - LTTNG_ASSERT(socket); - LTTNG_ASSERT(consumer); - LTTNG_ASSERT(registry); - - DBG2("Asking UST consumer for channel"); - - is_local_trace = consumer->net_seq_index == -1ULL; - /* Format the channel's path (relative to the current trace chunk). */ - pathname = setup_channel_trace_path(consumer, ua_sess->path, - &consumer_path_offset); - if (!pathname) { - ret = -1; - goto error; - } - - if (is_local_trace && trace_chunk) { - enum lttng_trace_chunk_status chunk_status; - char *pathname_index; - - ret = asprintf(&pathname_index, "%s/" DEFAULT_INDEX_DIR, - pathname); - if (ret < 0) { - ERR("Failed to format channel index directory"); - ret = -1; - goto error; - } - - /* - * Create the index subdirectory which will take care - * of implicitly creating the channel's path. - */ - chunk_status = lttng_trace_chunk_create_subdirectory( - trace_chunk, pathname_index); - free(pathname_index); - if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { - ret = -1; - goto error; - } - } - - /* Depending on the buffer type, a different channel key is used. */ - if (ua_sess->buffer_type == LTTNG_BUFFER_PER_UID) { - chan_reg_key = ua_chan->tracing_channel_id; - } else { - chan_reg_key = ua_chan->key; - } - - if (ua_chan->attr.type == LTTNG_UST_ABI_CHAN_METADATA) { - chan_id = -1U; - /* - * Metadata channels shm_path (buffers) are handled within - * session daemon. Consumer daemon should not try to create - * those buffer files. - */ - } else { - ust_reg_chan = ust_registry_channel_find(registry, chan_reg_key); - LTTNG_ASSERT(ust_reg_chan); - chan_id = ust_reg_chan->chan_id; - if (ua_sess->shm_path[0]) { - strncpy(shm_path, ua_sess->shm_path, sizeof(shm_path)); - shm_path[sizeof(shm_path) - 1] = '\0'; - strncat(shm_path, "/", - sizeof(shm_path) - strlen(shm_path) - 1); - strncat(shm_path, ua_chan->name, - sizeof(shm_path) - strlen(shm_path) - 1); - strncat(shm_path, "_", - sizeof(shm_path) - strlen(shm_path) - 1); - } - strncpy(root_shm_path, ua_sess->root_shm_path, sizeof(root_shm_path)); - root_shm_path[sizeof(root_shm_path) - 1] = '\0'; - } - - switch (ua_chan->attr.output) { - case LTTNG_UST_ABI_MMAP: - default: - output = LTTNG_EVENT_MMAP; - break; - } - - consumer_init_ask_channel_comm_msg(&msg, - ua_chan->attr.subbuf_size, - ua_chan->attr.num_subbuf, - ua_chan->attr.overwrite, - ua_chan->attr.switch_timer_interval, - ua_chan->attr.read_timer_interval, - ua_sess->live_timer_interval, - ua_sess->live_timer_interval != 0, - ua_chan->monitor_timer_interval, - output, - (int) ua_chan->attr.type, - ua_sess->tracing_id, - &pathname[consumer_path_offset], - ua_chan->name, - consumer->net_seq_index, - ua_chan->key, - registry->uuid, - chan_id, - ua_chan->tracefile_size, - ua_chan->tracefile_count, - ua_sess->id, - ua_sess->output_traces, - lttng_credentials_get_uid(&ua_sess->real_credentials), - ua_chan->attr.blocking_timeout, - root_shm_path, shm_path, - trace_chunk, - &ua_sess->effective_credentials); - - health_code_update(); - - ret = consumer_socket_send(socket, &msg, sizeof(msg)); - if (ret < 0) { - goto error; - } - - ret = consumer_recv_status_channel(socket, &key, - &ua_chan->expected_stream_count); - if (ret < 0) { - goto error; - } - /* Communication protocol error. */ - LTTNG_ASSERT(key == ua_chan->key); - /* We need at least one where 1 stream for 1 cpu. */ - if (ua_sess->output_traces) { - LTTNG_ASSERT(ua_chan->expected_stream_count > 0); - } - - DBG2("UST ask channel %" PRIu64 " successfully done with %u stream(s)", key, - ua_chan->expected_stream_count); - -error: - free(pathname); - health_code_update(); - return ret; -} - -/* - * Ask consumer to create a channel for a given session. - * - * Session list and rcu read side locks must be held by the caller. - * - * Returns 0 on success else a negative value. - */ -int ust_consumer_ask_channel(struct ust_app_session *ua_sess, - struct ust_app_channel *ua_chan, - struct consumer_output *consumer, - struct consumer_socket *socket, - struct ust_registry_session *registry, - struct lttng_trace_chunk * trace_chunk) -{ - int ret; - - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(ua_chan); - LTTNG_ASSERT(consumer); - LTTNG_ASSERT(socket); - LTTNG_ASSERT(registry); - - if (!consumer->enabled) { - ret = -LTTNG_ERR_NO_CONSUMER; - DBG3("Consumer is disabled"); - goto error; - } - - pthread_mutex_lock(socket->lock); - ret = ask_channel_creation(ua_sess, ua_chan, consumer, socket, registry, - trace_chunk); - pthread_mutex_unlock(socket->lock); - if (ret < 0) { - ERR("ask_channel_creation consumer command failed"); - goto error; - } - -error: - return ret; -} - -/* - * Send a get channel command to consumer using the given channel key. The - * channel object is populated and the stream list. - * - * Return 0 on success else a negative value. - */ -int ust_consumer_get_channel(struct consumer_socket *socket, - struct ust_app_channel *ua_chan) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(ua_chan); - LTTNG_ASSERT(socket); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_GET_CHANNEL; - msg.u.get_channel.key = ua_chan->key; - - pthread_mutex_lock(socket->lock); - health_code_update(); - - /* Send command and wait for OK reply. */ - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto error; - } - - /* First, get the channel from consumer. */ - ret = lttng_ust_ctl_recv_channel_from_consumer(*socket->fd_ptr, &ua_chan->obj); - if (ret < 0) { - if (ret != -EPIPE) { - ERR("Error recv channel from consumer %d with ret %d", - *socket->fd_ptr, ret); - } else { - DBG3("UST app recv channel from consumer. Consumer is dead."); - } - goto error; - } - - /* Next, get all streams. */ - while (1) { - struct ust_app_stream *stream; - - /* Create UST stream */ - stream = ust_app_alloc_stream(); - if (stream == NULL) { - ret = -ENOMEM; - goto error; - } - - /* Stream object is populated by this call if successful. */ - ret = lttng_ust_ctl_recv_stream_from_consumer(*socket->fd_ptr, &stream->obj); - if (ret < 0) { - free(stream); - if (ret == -LTTNG_UST_ERR_NOENT) { - DBG3("UST app consumer has no more stream available"); - break; - } - if (ret != -EPIPE) { - ERR("Recv stream from consumer %d with ret %d", - *socket->fd_ptr, ret); - } else { - DBG3("UST app recv stream from consumer. Consumer is dead."); - } - goto error; - } - - /* Order is important this is why a list is used. */ - cds_list_add_tail(&stream->list, &ua_chan->streams.head); - ua_chan->streams.count++; - - DBG2("UST app stream %d received successfully", ua_chan->streams.count); - } - - /* This MUST match or else we have a synchronization problem. */ - LTTNG_ASSERT(ua_chan->expected_stream_count == ua_chan->streams.count); - - /* Wait for confirmation that we can proceed with the streams. */ - ret = consumer_recv_status_reply(socket); - if (ret < 0) { - goto error; - } - -error: - health_code_update(); - pthread_mutex_unlock(socket->lock); - return ret; -} - -/* - * Send a destroy channel command to consumer using the given channel key. - * - * Note that this command MUST be used prior to a successful - * LTTNG_CONSUMER_GET_CHANNEL because once this command is done successfully, - * the streams are dispatched to the consumer threads and MUST be teardown - * through the hang up process. - * - * Return 0 on success else a negative value. - */ -int ust_consumer_destroy_channel(struct consumer_socket *socket, - struct ust_app_channel *ua_chan) -{ - int ret; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(ua_chan); - LTTNG_ASSERT(socket); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_CONSUMER_DESTROY_CHANNEL; - msg.u.destroy_channel.key = ua_chan->key; - - pthread_mutex_lock(socket->lock); - health_code_update(); - - ret = consumer_send_msg(socket, &msg); - if (ret < 0) { - goto error; - } - -error: - health_code_update(); - pthread_mutex_unlock(socket->lock); - return ret; -} - -/* - * Send a given stream to UST tracer. - * - * On success return 0 else a negative value. - */ -int ust_consumer_send_stream_to_ust(struct ust_app *app, - struct ust_app_channel *channel, struct ust_app_stream *stream) -{ - int ret; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(stream); - LTTNG_ASSERT(channel); - - DBG2("UST consumer send stream to app %d", app->sock); - - /* Relay stream to application. */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_send_stream_to_ust(app->sock, channel->obj, stream->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app send stream to ust failed. Application is dead. (pid: %d, sock: %d).", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app send stream to ust failed. Communication time out (pid: %d, sock: %d).", - app->pid, app->sock); - } else { - ERR("UST app send stream, handle %d, to ust failed with ret %d (pid: %d, sock: %d).", - stream->obj->handle, ret, app->pid, - app->sock); - } - goto error; - } - channel->handle = channel->obj->handle; - -error: - return ret; -} - -/* - * Send channel previously received from the consumer to the UST tracer. - * - * On success return 0 else a negative value. - */ -int ust_consumer_send_channel_to_ust(struct ust_app *app, - struct ust_app_session *ua_sess, struct ust_app_channel *channel) -{ - int ret; - - LTTNG_ASSERT(app); - LTTNG_ASSERT(ua_sess); - LTTNG_ASSERT(channel); - LTTNG_ASSERT(channel->obj); - - DBG2("UST app send channel to sock %d pid %d (name: %s, key: %" PRIu64 ")", - app->sock, app->pid, channel->name, channel->tracing_channel_id); - - /* Send stream to application. */ - pthread_mutex_lock(&app->sock_lock); - ret = lttng_ust_ctl_send_channel_to_ust(app->sock, ua_sess->handle, channel->obj); - pthread_mutex_unlock(&app->sock_lock); - if (ret < 0) { - if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { - DBG3("UST app send channel to ust failed. Application is dead (pid: %d, sock: %d).", - app->pid, app->sock); - } else if (ret == -EAGAIN) { - WARN("UST app send channel to ust failed. Communication timeout (pid: %d, sock: %d).", - app->pid, app->sock); - } else { - ERR("UST app send channel %s, to ust failed with ret %d (pid: %d, sock: %d).", - channel->name, ret, app->pid, - app->sock); - } - goto error; - } - -error: - return ret; -} - -/* - * Handle the metadata requests from the UST consumer - * - * Return 0 on success else a negative value. - */ -int ust_consumer_metadata_request(struct consumer_socket *socket) -{ - int ret; - ssize_t ret_push; - struct lttcomm_metadata_request_msg request; - struct buffer_reg_uid *reg_uid; - struct ust_registry_session *ust_reg; - struct lttcomm_consumer_msg msg; - - LTTNG_ASSERT(socket); - - rcu_read_lock(); - health_code_update(); - - /* Wait for a metadata request */ - pthread_mutex_lock(socket->lock); - ret = consumer_socket_recv(socket, &request, sizeof(request)); - pthread_mutex_unlock(socket->lock); - if (ret < 0) { - goto end; - } - - DBG("Metadata request received for session %" PRIu64 ", key %" PRIu64, - request.session_id, request.key); - - reg_uid = buffer_reg_uid_find(request.session_id, - request.bits_per_long, request.uid); - if (reg_uid) { - ust_reg = reg_uid->registry->reg.ust; - } else { - struct buffer_reg_pid *reg_pid = - buffer_reg_pid_find(request.session_id_per_pid); - if (!reg_pid) { - DBG("PID registry not found for session id %" PRIu64, - request.session_id_per_pid); - - memset(&msg, 0, sizeof(msg)); - msg.cmd_type = LTTNG_ERR_UND; - pthread_mutex_lock(socket->lock); - (void) consumer_send_msg(socket, &msg); - pthread_mutex_unlock(socket->lock); - /* - * This is possible since the session might have been destroyed - * during a consumer metadata request. So here, return gracefully - * because the destroy session will push the remaining metadata to - * the consumer. - */ - ret = 0; - goto end; - } - ust_reg = reg_pid->registry->reg.ust; - } - LTTNG_ASSERT(ust_reg); - - pthread_mutex_lock(&ust_reg->lock); - ret_push = ust_app_push_metadata(ust_reg, socket, 1); - pthread_mutex_unlock(&ust_reg->lock); - if (ret_push == -EPIPE) { - DBG("Application or relay closed while pushing metadata"); - } else if (ret_push < 0) { - ERR("Pushing metadata"); - ret = -1; - goto end; - } else { - DBG("UST Consumer metadata pushed successfully"); - } - ret = 0; - -end: - rcu_read_unlock(); - return ret; -} diff --git a/src/bin/lttng-sessiond/ust-consumer.cpp b/src/bin/lttng-sessiond/ust-consumer.cpp new file mode 100644 index 000000000..a68c5d96d --- /dev/null +++ b/src/bin/lttng-sessiond/ust-consumer.cpp @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2011 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "consumer.h" +#include "health-sessiond.h" +#include "ust-consumer.h" +#include "lttng-ust-error.h" +#include "buffer-registry.h" +#include "session.h" +#include "lttng-sessiond.h" + +/* + * Send a single channel to the consumer using command ASK_CHANNEL_CREATION. + * + * Consumer socket lock MUST be acquired before calling this. + */ +static int ask_channel_creation(struct ust_app_session *ua_sess, + struct ust_app_channel *ua_chan, + struct consumer_output *consumer, + struct consumer_socket *socket, + struct ust_registry_session *registry, + struct lttng_trace_chunk *trace_chunk) +{ + int ret, output; + uint32_t chan_id; + uint64_t key, chan_reg_key; + char *pathname = NULL; + struct lttcomm_consumer_msg msg; + struct ust_registry_channel *ust_reg_chan; + char shm_path[PATH_MAX] = ""; + char root_shm_path[PATH_MAX] = ""; + bool is_local_trace; + size_t consumer_path_offset = 0; + + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(ua_chan); + LTTNG_ASSERT(socket); + LTTNG_ASSERT(consumer); + LTTNG_ASSERT(registry); + + DBG2("Asking UST consumer for channel"); + + is_local_trace = consumer->net_seq_index == -1ULL; + /* Format the channel's path (relative to the current trace chunk). */ + pathname = setup_channel_trace_path(consumer, ua_sess->path, + &consumer_path_offset); + if (!pathname) { + ret = -1; + goto error; + } + + if (is_local_trace && trace_chunk) { + enum lttng_trace_chunk_status chunk_status; + char *pathname_index; + + ret = asprintf(&pathname_index, "%s/" DEFAULT_INDEX_DIR, + pathname); + if (ret < 0) { + ERR("Failed to format channel index directory"); + ret = -1; + goto error; + } + + /* + * Create the index subdirectory which will take care + * of implicitly creating the channel's path. + */ + chunk_status = lttng_trace_chunk_create_subdirectory( + trace_chunk, pathname_index); + free(pathname_index); + if (chunk_status != LTTNG_TRACE_CHUNK_STATUS_OK) { + ret = -1; + goto error; + } + } + + /* Depending on the buffer type, a different channel key is used. */ + if (ua_sess->buffer_type == LTTNG_BUFFER_PER_UID) { + chan_reg_key = ua_chan->tracing_channel_id; + } else { + chan_reg_key = ua_chan->key; + } + + if (ua_chan->attr.type == LTTNG_UST_ABI_CHAN_METADATA) { + chan_id = -1U; + /* + * Metadata channels shm_path (buffers) are handled within + * session daemon. Consumer daemon should not try to create + * those buffer files. + */ + } else { + ust_reg_chan = ust_registry_channel_find(registry, chan_reg_key); + LTTNG_ASSERT(ust_reg_chan); + chan_id = ust_reg_chan->chan_id; + if (ua_sess->shm_path[0]) { + strncpy(shm_path, ua_sess->shm_path, sizeof(shm_path)); + shm_path[sizeof(shm_path) - 1] = '\0'; + strncat(shm_path, "/", + sizeof(shm_path) - strlen(shm_path) - 1); + strncat(shm_path, ua_chan->name, + sizeof(shm_path) - strlen(shm_path) - 1); + strncat(shm_path, "_", + sizeof(shm_path) - strlen(shm_path) - 1); + } + strncpy(root_shm_path, ua_sess->root_shm_path, sizeof(root_shm_path)); + root_shm_path[sizeof(root_shm_path) - 1] = '\0'; + } + + switch (ua_chan->attr.output) { + case LTTNG_UST_ABI_MMAP: + default: + output = LTTNG_EVENT_MMAP; + break; + } + + consumer_init_ask_channel_comm_msg(&msg, + ua_chan->attr.subbuf_size, + ua_chan->attr.num_subbuf, + ua_chan->attr.overwrite, + ua_chan->attr.switch_timer_interval, + ua_chan->attr.read_timer_interval, + ua_sess->live_timer_interval, + ua_sess->live_timer_interval != 0, + ua_chan->monitor_timer_interval, + output, + (int) ua_chan->attr.type, + ua_sess->tracing_id, + &pathname[consumer_path_offset], + ua_chan->name, + consumer->net_seq_index, + ua_chan->key, + registry->uuid, + chan_id, + ua_chan->tracefile_size, + ua_chan->tracefile_count, + ua_sess->id, + ua_sess->output_traces, + lttng_credentials_get_uid(&ua_sess->real_credentials), + ua_chan->attr.blocking_timeout, + root_shm_path, shm_path, + trace_chunk, + &ua_sess->effective_credentials); + + health_code_update(); + + ret = consumer_socket_send(socket, &msg, sizeof(msg)); + if (ret < 0) { + goto error; + } + + ret = consumer_recv_status_channel(socket, &key, + &ua_chan->expected_stream_count); + if (ret < 0) { + goto error; + } + /* Communication protocol error. */ + LTTNG_ASSERT(key == ua_chan->key); + /* We need at least one where 1 stream for 1 cpu. */ + if (ua_sess->output_traces) { + LTTNG_ASSERT(ua_chan->expected_stream_count > 0); + } + + DBG2("UST ask channel %" PRIu64 " successfully done with %u stream(s)", key, + ua_chan->expected_stream_count); + +error: + free(pathname); + health_code_update(); + return ret; +} + +/* + * Ask consumer to create a channel for a given session. + * + * Session list and rcu read side locks must be held by the caller. + * + * Returns 0 on success else a negative value. + */ +int ust_consumer_ask_channel(struct ust_app_session *ua_sess, + struct ust_app_channel *ua_chan, + struct consumer_output *consumer, + struct consumer_socket *socket, + struct ust_registry_session *registry, + struct lttng_trace_chunk * trace_chunk) +{ + int ret; + + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(ua_chan); + LTTNG_ASSERT(consumer); + LTTNG_ASSERT(socket); + LTTNG_ASSERT(registry); + + if (!consumer->enabled) { + ret = -LTTNG_ERR_NO_CONSUMER; + DBG3("Consumer is disabled"); + goto error; + } + + pthread_mutex_lock(socket->lock); + ret = ask_channel_creation(ua_sess, ua_chan, consumer, socket, registry, + trace_chunk); + pthread_mutex_unlock(socket->lock); + if (ret < 0) { + ERR("ask_channel_creation consumer command failed"); + goto error; + } + +error: + return ret; +} + +/* + * Send a get channel command to consumer using the given channel key. The + * channel object is populated and the stream list. + * + * Return 0 on success else a negative value. + */ +int ust_consumer_get_channel(struct consumer_socket *socket, + struct ust_app_channel *ua_chan) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(ua_chan); + LTTNG_ASSERT(socket); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_GET_CHANNEL; + msg.u.get_channel.key = ua_chan->key; + + pthread_mutex_lock(socket->lock); + health_code_update(); + + /* Send command and wait for OK reply. */ + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto error; + } + + /* First, get the channel from consumer. */ + ret = lttng_ust_ctl_recv_channel_from_consumer(*socket->fd_ptr, &ua_chan->obj); + if (ret < 0) { + if (ret != -EPIPE) { + ERR("Error recv channel from consumer %d with ret %d", + *socket->fd_ptr, ret); + } else { + DBG3("UST app recv channel from consumer. Consumer is dead."); + } + goto error; + } + + /* Next, get all streams. */ + while (1) { + struct ust_app_stream *stream; + + /* Create UST stream */ + stream = ust_app_alloc_stream(); + if (stream == NULL) { + ret = -ENOMEM; + goto error; + } + + /* Stream object is populated by this call if successful. */ + ret = lttng_ust_ctl_recv_stream_from_consumer(*socket->fd_ptr, &stream->obj); + if (ret < 0) { + free(stream); + if (ret == -LTTNG_UST_ERR_NOENT) { + DBG3("UST app consumer has no more stream available"); + break; + } + if (ret != -EPIPE) { + ERR("Recv stream from consumer %d with ret %d", + *socket->fd_ptr, ret); + } else { + DBG3("UST app recv stream from consumer. Consumer is dead."); + } + goto error; + } + + /* Order is important this is why a list is used. */ + cds_list_add_tail(&stream->list, &ua_chan->streams.head); + ua_chan->streams.count++; + + DBG2("UST app stream %d received successfully", ua_chan->streams.count); + } + + /* This MUST match or else we have a synchronization problem. */ + LTTNG_ASSERT(ua_chan->expected_stream_count == ua_chan->streams.count); + + /* Wait for confirmation that we can proceed with the streams. */ + ret = consumer_recv_status_reply(socket); + if (ret < 0) { + goto error; + } + +error: + health_code_update(); + pthread_mutex_unlock(socket->lock); + return ret; +} + +/* + * Send a destroy channel command to consumer using the given channel key. + * + * Note that this command MUST be used prior to a successful + * LTTNG_CONSUMER_GET_CHANNEL because once this command is done successfully, + * the streams are dispatched to the consumer threads and MUST be teardown + * through the hang up process. + * + * Return 0 on success else a negative value. + */ +int ust_consumer_destroy_channel(struct consumer_socket *socket, + struct ust_app_channel *ua_chan) +{ + int ret; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(ua_chan); + LTTNG_ASSERT(socket); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_CONSUMER_DESTROY_CHANNEL; + msg.u.destroy_channel.key = ua_chan->key; + + pthread_mutex_lock(socket->lock); + health_code_update(); + + ret = consumer_send_msg(socket, &msg); + if (ret < 0) { + goto error; + } + +error: + health_code_update(); + pthread_mutex_unlock(socket->lock); + return ret; +} + +/* + * Send a given stream to UST tracer. + * + * On success return 0 else a negative value. + */ +int ust_consumer_send_stream_to_ust(struct ust_app *app, + struct ust_app_channel *channel, struct ust_app_stream *stream) +{ + int ret; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(stream); + LTTNG_ASSERT(channel); + + DBG2("UST consumer send stream to app %d", app->sock); + + /* Relay stream to application. */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_send_stream_to_ust(app->sock, channel->obj, stream->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app send stream to ust failed. Application is dead. (pid: %d, sock: %d).", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app send stream to ust failed. Communication time out (pid: %d, sock: %d).", + app->pid, app->sock); + } else { + ERR("UST app send stream, handle %d, to ust failed with ret %d (pid: %d, sock: %d).", + stream->obj->handle, ret, app->pid, + app->sock); + } + goto error; + } + channel->handle = channel->obj->handle; + +error: + return ret; +} + +/* + * Send channel previously received from the consumer to the UST tracer. + * + * On success return 0 else a negative value. + */ +int ust_consumer_send_channel_to_ust(struct ust_app *app, + struct ust_app_session *ua_sess, struct ust_app_channel *channel) +{ + int ret; + + LTTNG_ASSERT(app); + LTTNG_ASSERT(ua_sess); + LTTNG_ASSERT(channel); + LTTNG_ASSERT(channel->obj); + + DBG2("UST app send channel to sock %d pid %d (name: %s, key: %" PRIu64 ")", + app->sock, app->pid, channel->name, channel->tracing_channel_id); + + /* Send stream to application. */ + pthread_mutex_lock(&app->sock_lock); + ret = lttng_ust_ctl_send_channel_to_ust(app->sock, ua_sess->handle, channel->obj); + pthread_mutex_unlock(&app->sock_lock); + if (ret < 0) { + if (ret == -EPIPE || ret == -LTTNG_UST_ERR_EXITING) { + DBG3("UST app send channel to ust failed. Application is dead (pid: %d, sock: %d).", + app->pid, app->sock); + } else if (ret == -EAGAIN) { + WARN("UST app send channel to ust failed. Communication timeout (pid: %d, sock: %d).", + app->pid, app->sock); + } else { + ERR("UST app send channel %s, to ust failed with ret %d (pid: %d, sock: %d).", + channel->name, ret, app->pid, + app->sock); + } + goto error; + } + +error: + return ret; +} + +/* + * Handle the metadata requests from the UST consumer + * + * Return 0 on success else a negative value. + */ +int ust_consumer_metadata_request(struct consumer_socket *socket) +{ + int ret; + ssize_t ret_push; + struct lttcomm_metadata_request_msg request; + struct buffer_reg_uid *reg_uid; + struct ust_registry_session *ust_reg; + struct lttcomm_consumer_msg msg; + + LTTNG_ASSERT(socket); + + rcu_read_lock(); + health_code_update(); + + /* Wait for a metadata request */ + pthread_mutex_lock(socket->lock); + ret = consumer_socket_recv(socket, &request, sizeof(request)); + pthread_mutex_unlock(socket->lock); + if (ret < 0) { + goto end; + } + + DBG("Metadata request received for session %" PRIu64 ", key %" PRIu64, + request.session_id, request.key); + + reg_uid = buffer_reg_uid_find(request.session_id, + request.bits_per_long, request.uid); + if (reg_uid) { + ust_reg = reg_uid->registry->reg.ust; + } else { + struct buffer_reg_pid *reg_pid = + buffer_reg_pid_find(request.session_id_per_pid); + if (!reg_pid) { + DBG("PID registry not found for session id %" PRIu64, + request.session_id_per_pid); + + memset(&msg, 0, sizeof(msg)); + msg.cmd_type = LTTNG_ERR_UND; + pthread_mutex_lock(socket->lock); + (void) consumer_send_msg(socket, &msg); + pthread_mutex_unlock(socket->lock); + /* + * This is possible since the session might have been destroyed + * during a consumer metadata request. So here, return gracefully + * because the destroy session will push the remaining metadata to + * the consumer. + */ + ret = 0; + goto end; + } + ust_reg = reg_pid->registry->reg.ust; + } + LTTNG_ASSERT(ust_reg); + + pthread_mutex_lock(&ust_reg->lock); + ret_push = ust_app_push_metadata(ust_reg, socket, 1); + pthread_mutex_unlock(&ust_reg->lock); + if (ret_push == -EPIPE) { + DBG("Application or relay closed while pushing metadata"); + } else if (ret_push < 0) { + ERR("Pushing metadata"); + ret = -1; + goto end; + } else { + DBG("UST Consumer metadata pushed successfully"); + } + ret = 0; + +end: + rcu_read_unlock(); + return ret; +} diff --git a/src/bin/lttng-sessiond/ust-ctl-internal.h b/src/bin/lttng-sessiond/ust-ctl-internal.h index 610aa7f36..d85af2868 100644 --- a/src/bin/lttng-sessiond/ust-ctl-internal.h +++ b/src/bin/lttng-sessiond/ust-ctl-internal.h @@ -14,6 +14,10 @@ #include "lttng-ust-abi.h" +#ifdef __cplusplus +extern "C" { +#endif + #ifndef LTTNG_UST_UUID_LEN #define LTTNG_UST_UUID_LEN 16 #endif @@ -655,4 +659,8 @@ int lttng_ust_ctl_counter_clear(struct lttng_ust_ctl_daemon_counter *counter, void lttng_ust_ctl_sigbus_handle(void *addr); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_UST_CTL_INTERNAL_H */ diff --git a/src/bin/lttng-sessiond/ust-field-utils.c b/src/bin/lttng-sessiond/ust-field-utils.c deleted file mode 100644 index a89fade5e..000000000 --- a/src/bin/lttng-sessiond/ust-field-utils.c +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (C) 2018 Francis Deslauriers - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include - -#include "ust-field-utils.h" - -/* - * The lttng_ust_ctl_field is made of a combination of C basic types - * lttng_ust_ctl_basic_type and _lttng_ust_ctl_basic_type. - * - * lttng_ust_ctl_basic_type contains an enumeration describing the abstract type. - * _lttng_ust_ctl_basic_type does _NOT_ contain an enumeration describing the - * abstract type. - * - * A layer is needed to use the same code for both structures. - * When dealing with _lttng_ust_ctl_basic_type, we need to use the abstract type of - * the lttng_ust_ctl_type struct. - */ - -/* - * Compare two lttng_ust_ctl_integer_type fields. - * Returns 1 if both are identical. - */ -static bool match_lttng_ust_ctl_field_integer(const struct lttng_ust_ctl_integer_type *first, - const struct lttng_ust_ctl_integer_type *second) -{ - if (first->size != second->size) { - goto no_match; - } - if (first->alignment != second->alignment) { - goto no_match; - } - if (first->signedness != second->signedness) { - goto no_match; - } - if (first->encoding != second->encoding) { - goto no_match; - } - if (first->base != second->base) { - goto no_match; - } - if (first->reverse_byte_order != second->reverse_byte_order) { - goto no_match; - } - - return true; - -no_match: - return false; -} - -/* - * Compare two _lttng_ust_ctl_basic_type fields known to be of type integer. - * Returns 1 if both are identical. - */ -static bool match_lttng_ust_ctl_field_integer_from_raw_basic_type( - const union _lttng_ust_ctl_basic_type *first, - const union _lttng_ust_ctl_basic_type *second) -{ - return match_lttng_ust_ctl_field_integer(&first->integer, &second->integer); -} - -/* - * Compare two _lttng_ust_ctl_basic_type fields known to be of type enum. - * Returns 1 if both are identical. - */ -static bool match_lttng_ust_ctl_field_enum_from_raw_basic_type( - const union _lttng_ust_ctl_basic_type *first, - const union _lttng_ust_ctl_basic_type *second) -{ - /* - * Compare enumeration ID. Enumeration ID is provided to the application by - * the session daemon before event registration. - */ - if (first->enumeration.id != second->enumeration.id) { - goto no_match; - } - - /* - * Sanity check of the name and container type. Those were already checked - * during enum registration. - */ - if (strncmp(first->enumeration.name, second->enumeration.name, - LTTNG_UST_ABI_SYM_NAME_LEN)) { - goto no_match; - } - if (!match_lttng_ust_ctl_field_integer(&first->enumeration.container_type, - &second->enumeration.container_type)) { - goto no_match; - } - - return true; - -no_match: - return false; -} - -/* - * Compare two _lttng_ust_ctl_basic_type fields known to be of type string. - * Returns 1 if both are identical. - */ -static bool match_lttng_ust_ctl_field_string_from_raw_basic_type( - const union _lttng_ust_ctl_basic_type *first, - const union _lttng_ust_ctl_basic_type *second) -{ - return first->string.encoding == second->string.encoding; -} - -/* - * Compare two _lttng_ust_ctl_basic_type fields known to be of type float. - * Returns 1 if both are identical. - */ -static bool match_lttng_ust_ctl_field_float_from_raw_basic_type( - const union _lttng_ust_ctl_basic_type *first, - const union _lttng_ust_ctl_basic_type *second) -{ - if (first->_float.exp_dig != second->_float.exp_dig) { - goto no_match; - } - - if (first->_float.mant_dig != second->_float.mant_dig) { - goto no_match; - } - - if (first->_float.reverse_byte_order != - second->_float.reverse_byte_order) { - goto no_match; - } - - if (first->_float.alignment != second->_float.alignment) { - goto no_match; - } - - return true; - -no_match: - return false; -} - -/* - * Compare two _lttng_ust_ctl_basic_type fields given their respective abstract types. - * Returns 1 if both are identical. - */ -static bool match_lttng_ust_ctl_field_raw_basic_type( - enum lttng_ust_ctl_abstract_types first_atype, - const union _lttng_ust_ctl_basic_type *first, - enum lttng_ust_ctl_abstract_types second_atype, - const union _lttng_ust_ctl_basic_type *second) -{ - if (first_atype != second_atype) { - goto no_match; - } - - switch (first_atype) { - case lttng_ust_ctl_atype_integer: - if (!match_lttng_ust_ctl_field_integer_from_raw_basic_type(first, second)) { - goto no_match; - } - break; - case lttng_ust_ctl_atype_enum: - if (!match_lttng_ust_ctl_field_enum_from_raw_basic_type(first, second)) { - goto no_match; - } - break; - case lttng_ust_ctl_atype_string: - if (!match_lttng_ust_ctl_field_string_from_raw_basic_type(first, second)) { - goto no_match; - } - break; - case lttng_ust_ctl_atype_float: - if (!match_lttng_ust_ctl_field_float_from_raw_basic_type(first, second)) { - goto no_match; - } - break; - default: - goto no_match; - } - - return true; - -no_match: - return false; -} - -/* - * Compatibility layer between the lttng_ust_ctl_basic_type struct and - * _lttng_ust_ctl_basic_type union. - */ -static bool match_lttng_ust_ctl_field_basic_type(const struct lttng_ust_ctl_basic_type *first, - const struct lttng_ust_ctl_basic_type *second) -{ - return match_lttng_ust_ctl_field_raw_basic_type(first->atype, &first->u.basic, - second->atype, &second->u.basic); -} - -int match_lttng_ust_ctl_field(const struct lttng_ust_ctl_field *first, - const struct lttng_ust_ctl_field *second) -{ - /* Check the name of the field is identical. */ - if (strncmp(first->name, second->name, LTTNG_UST_ABI_SYM_NAME_LEN)) { - goto no_match; - } - - /* Check the field type is identical. */ - if (first->type.atype != second->type.atype) { - goto no_match; - } - - /* Check the field layout. */ - switch (first->type.atype) { - case lttng_ust_ctl_atype_integer: - case lttng_ust_ctl_atype_enum: - case lttng_ust_ctl_atype_string: - case lttng_ust_ctl_atype_float: - if (!match_lttng_ust_ctl_field_raw_basic_type(first->type.atype, - &first->type.u.legacy.basic, second->type.atype, - &second->type.u.legacy.basic)) { - goto no_match; - } - break; - case lttng_ust_ctl_atype_sequence: - /* Match element type of the sequence. */ - if (!match_lttng_ust_ctl_field_basic_type(&first->type.u.legacy.sequence.elem_type, - &second->type.u.legacy.sequence.elem_type)) { - goto no_match; - } - - /* Match length type of the sequence. */ - if (!match_lttng_ust_ctl_field_basic_type(&first->type.u.legacy.sequence.length_type, - &second->type.u.legacy.sequence.length_type)) { - goto no_match; - } - break; - case lttng_ust_ctl_atype_array: - /* Match element type of the array. */ - if (!match_lttng_ust_ctl_field_basic_type(&first->type.u.legacy.array.elem_type, - &second->type.u.legacy.array.elem_type)) { - goto no_match; - } - - /* Match length of the array. */ - if (first->type.u.legacy.array.length != second->type.u.legacy.array.length) { - goto no_match; - } - break; - case lttng_ust_ctl_atype_variant: - /* Compare number of choice of the variants. */ - if (first->type.u.legacy.variant.nr_choices != - second->type.u.legacy.variant.nr_choices) { - goto no_match; - } - - /* Compare tag name of the variants. */ - if (strncmp(first->type.u.legacy.variant.tag_name, - second->type.u.legacy.variant.tag_name, - LTTNG_UST_ABI_SYM_NAME_LEN)) { - goto no_match; - } - break; - case lttng_ust_ctl_atype_struct: - /* Compare number of fields of the structs. */ - if (first->type.u.legacy._struct.nr_fields != second->type.u.legacy._struct.nr_fields) { - goto no_match; - } - break; - case lttng_ust_ctl_atype_sequence_nestable: - if (first->type.u.sequence_nestable.alignment != second->type.u.sequence_nestable.alignment) { - goto no_match; - } - /* Compare length_name of the sequences. */ - if (strncmp(first->type.u.sequence_nestable.length_name, - second->type.u.sequence_nestable.length_name, - LTTNG_UST_ABI_SYM_NAME_LEN)) { - goto no_match; - } - /* Comparison will be done when marshalling following items. */ - break; - case lttng_ust_ctl_atype_array_nestable: - if (first->type.u.array_nestable.alignment != second->type.u.array_nestable.alignment) { - goto no_match; - } - /* Match length of the array. */ - if (first->type.u.array_nestable.length != second->type.u.array_nestable.length) { - goto no_match; - } - /* Comparison of element type will be done when marshalling following item. */ - break; - case lttng_ust_ctl_atype_enum_nestable: - if (first->type.u.enum_nestable.id != second->type.u.enum_nestable.id) { - goto no_match; - } - /* Compare name of the enums. */ - if (strncmp(first->type.u.enum_nestable.name, - second->type.u.enum_nestable.name, - LTTNG_UST_ABI_SYM_NAME_LEN)) { - goto no_match; - } - /* Comparison of element type will be done when marshalling following item. */ - break; - case lttng_ust_ctl_atype_struct_nestable: - if (first->type.u.struct_nestable.alignment != second->type.u.struct_nestable.alignment) { - goto no_match; - } - /* Compare number of fields of the structs. */ - if (first->type.u.struct_nestable.nr_fields != second->type.u.struct_nestable.nr_fields) { - goto no_match; - } - break; - case lttng_ust_ctl_atype_variant_nestable: - if (first->type.u.variant_nestable.alignment != second->type.u.variant_nestable.alignment) { - goto no_match; - } - /* Compare number of choice of the variants. */ - if (first->type.u.variant_nestable.nr_choices != - second->type.u.variant_nestable.nr_choices) { - goto no_match; - } - - /* Compare tag name of the variants. */ - if (strncmp(first->type.u.variant_nestable.tag_name, - second->type.u.variant_nestable.tag_name, - LTTNG_UST_ABI_SYM_NAME_LEN)) { - goto no_match; - } - break; - default: - goto no_match; - } - - return true; - -no_match: - return false; -} diff --git a/src/bin/lttng-sessiond/ust-field-utils.cpp b/src/bin/lttng-sessiond/ust-field-utils.cpp new file mode 100644 index 000000000..a89fade5e --- /dev/null +++ b/src/bin/lttng-sessiond/ust-field-utils.cpp @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2018 Francis Deslauriers + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include + +#include "ust-field-utils.h" + +/* + * The lttng_ust_ctl_field is made of a combination of C basic types + * lttng_ust_ctl_basic_type and _lttng_ust_ctl_basic_type. + * + * lttng_ust_ctl_basic_type contains an enumeration describing the abstract type. + * _lttng_ust_ctl_basic_type does _NOT_ contain an enumeration describing the + * abstract type. + * + * A layer is needed to use the same code for both structures. + * When dealing with _lttng_ust_ctl_basic_type, we need to use the abstract type of + * the lttng_ust_ctl_type struct. + */ + +/* + * Compare two lttng_ust_ctl_integer_type fields. + * Returns 1 if both are identical. + */ +static bool match_lttng_ust_ctl_field_integer(const struct lttng_ust_ctl_integer_type *first, + const struct lttng_ust_ctl_integer_type *second) +{ + if (first->size != second->size) { + goto no_match; + } + if (first->alignment != second->alignment) { + goto no_match; + } + if (first->signedness != second->signedness) { + goto no_match; + } + if (first->encoding != second->encoding) { + goto no_match; + } + if (first->base != second->base) { + goto no_match; + } + if (first->reverse_byte_order != second->reverse_byte_order) { + goto no_match; + } + + return true; + +no_match: + return false; +} + +/* + * Compare two _lttng_ust_ctl_basic_type fields known to be of type integer. + * Returns 1 if both are identical. + */ +static bool match_lttng_ust_ctl_field_integer_from_raw_basic_type( + const union _lttng_ust_ctl_basic_type *first, + const union _lttng_ust_ctl_basic_type *second) +{ + return match_lttng_ust_ctl_field_integer(&first->integer, &second->integer); +} + +/* + * Compare two _lttng_ust_ctl_basic_type fields known to be of type enum. + * Returns 1 if both are identical. + */ +static bool match_lttng_ust_ctl_field_enum_from_raw_basic_type( + const union _lttng_ust_ctl_basic_type *first, + const union _lttng_ust_ctl_basic_type *second) +{ + /* + * Compare enumeration ID. Enumeration ID is provided to the application by + * the session daemon before event registration. + */ + if (first->enumeration.id != second->enumeration.id) { + goto no_match; + } + + /* + * Sanity check of the name and container type. Those were already checked + * during enum registration. + */ + if (strncmp(first->enumeration.name, second->enumeration.name, + LTTNG_UST_ABI_SYM_NAME_LEN)) { + goto no_match; + } + if (!match_lttng_ust_ctl_field_integer(&first->enumeration.container_type, + &second->enumeration.container_type)) { + goto no_match; + } + + return true; + +no_match: + return false; +} + +/* + * Compare two _lttng_ust_ctl_basic_type fields known to be of type string. + * Returns 1 if both are identical. + */ +static bool match_lttng_ust_ctl_field_string_from_raw_basic_type( + const union _lttng_ust_ctl_basic_type *first, + const union _lttng_ust_ctl_basic_type *second) +{ + return first->string.encoding == second->string.encoding; +} + +/* + * Compare two _lttng_ust_ctl_basic_type fields known to be of type float. + * Returns 1 if both are identical. + */ +static bool match_lttng_ust_ctl_field_float_from_raw_basic_type( + const union _lttng_ust_ctl_basic_type *first, + const union _lttng_ust_ctl_basic_type *second) +{ + if (first->_float.exp_dig != second->_float.exp_dig) { + goto no_match; + } + + if (first->_float.mant_dig != second->_float.mant_dig) { + goto no_match; + } + + if (first->_float.reverse_byte_order != + second->_float.reverse_byte_order) { + goto no_match; + } + + if (first->_float.alignment != second->_float.alignment) { + goto no_match; + } + + return true; + +no_match: + return false; +} + +/* + * Compare two _lttng_ust_ctl_basic_type fields given their respective abstract types. + * Returns 1 if both are identical. + */ +static bool match_lttng_ust_ctl_field_raw_basic_type( + enum lttng_ust_ctl_abstract_types first_atype, + const union _lttng_ust_ctl_basic_type *first, + enum lttng_ust_ctl_abstract_types second_atype, + const union _lttng_ust_ctl_basic_type *second) +{ + if (first_atype != second_atype) { + goto no_match; + } + + switch (first_atype) { + case lttng_ust_ctl_atype_integer: + if (!match_lttng_ust_ctl_field_integer_from_raw_basic_type(first, second)) { + goto no_match; + } + break; + case lttng_ust_ctl_atype_enum: + if (!match_lttng_ust_ctl_field_enum_from_raw_basic_type(first, second)) { + goto no_match; + } + break; + case lttng_ust_ctl_atype_string: + if (!match_lttng_ust_ctl_field_string_from_raw_basic_type(first, second)) { + goto no_match; + } + break; + case lttng_ust_ctl_atype_float: + if (!match_lttng_ust_ctl_field_float_from_raw_basic_type(first, second)) { + goto no_match; + } + break; + default: + goto no_match; + } + + return true; + +no_match: + return false; +} + +/* + * Compatibility layer between the lttng_ust_ctl_basic_type struct and + * _lttng_ust_ctl_basic_type union. + */ +static bool match_lttng_ust_ctl_field_basic_type(const struct lttng_ust_ctl_basic_type *first, + const struct lttng_ust_ctl_basic_type *second) +{ + return match_lttng_ust_ctl_field_raw_basic_type(first->atype, &first->u.basic, + second->atype, &second->u.basic); +} + +int match_lttng_ust_ctl_field(const struct lttng_ust_ctl_field *first, + const struct lttng_ust_ctl_field *second) +{ + /* Check the name of the field is identical. */ + if (strncmp(first->name, second->name, LTTNG_UST_ABI_SYM_NAME_LEN)) { + goto no_match; + } + + /* Check the field type is identical. */ + if (first->type.atype != second->type.atype) { + goto no_match; + } + + /* Check the field layout. */ + switch (first->type.atype) { + case lttng_ust_ctl_atype_integer: + case lttng_ust_ctl_atype_enum: + case lttng_ust_ctl_atype_string: + case lttng_ust_ctl_atype_float: + if (!match_lttng_ust_ctl_field_raw_basic_type(first->type.atype, + &first->type.u.legacy.basic, second->type.atype, + &second->type.u.legacy.basic)) { + goto no_match; + } + break; + case lttng_ust_ctl_atype_sequence: + /* Match element type of the sequence. */ + if (!match_lttng_ust_ctl_field_basic_type(&first->type.u.legacy.sequence.elem_type, + &second->type.u.legacy.sequence.elem_type)) { + goto no_match; + } + + /* Match length type of the sequence. */ + if (!match_lttng_ust_ctl_field_basic_type(&first->type.u.legacy.sequence.length_type, + &second->type.u.legacy.sequence.length_type)) { + goto no_match; + } + break; + case lttng_ust_ctl_atype_array: + /* Match element type of the array. */ + if (!match_lttng_ust_ctl_field_basic_type(&first->type.u.legacy.array.elem_type, + &second->type.u.legacy.array.elem_type)) { + goto no_match; + } + + /* Match length of the array. */ + if (first->type.u.legacy.array.length != second->type.u.legacy.array.length) { + goto no_match; + } + break; + case lttng_ust_ctl_atype_variant: + /* Compare number of choice of the variants. */ + if (first->type.u.legacy.variant.nr_choices != + second->type.u.legacy.variant.nr_choices) { + goto no_match; + } + + /* Compare tag name of the variants. */ + if (strncmp(first->type.u.legacy.variant.tag_name, + second->type.u.legacy.variant.tag_name, + LTTNG_UST_ABI_SYM_NAME_LEN)) { + goto no_match; + } + break; + case lttng_ust_ctl_atype_struct: + /* Compare number of fields of the structs. */ + if (first->type.u.legacy._struct.nr_fields != second->type.u.legacy._struct.nr_fields) { + goto no_match; + } + break; + case lttng_ust_ctl_atype_sequence_nestable: + if (first->type.u.sequence_nestable.alignment != second->type.u.sequence_nestable.alignment) { + goto no_match; + } + /* Compare length_name of the sequences. */ + if (strncmp(first->type.u.sequence_nestable.length_name, + second->type.u.sequence_nestable.length_name, + LTTNG_UST_ABI_SYM_NAME_LEN)) { + goto no_match; + } + /* Comparison will be done when marshalling following items. */ + break; + case lttng_ust_ctl_atype_array_nestable: + if (first->type.u.array_nestable.alignment != second->type.u.array_nestable.alignment) { + goto no_match; + } + /* Match length of the array. */ + if (first->type.u.array_nestable.length != second->type.u.array_nestable.length) { + goto no_match; + } + /* Comparison of element type will be done when marshalling following item. */ + break; + case lttng_ust_ctl_atype_enum_nestable: + if (first->type.u.enum_nestable.id != second->type.u.enum_nestable.id) { + goto no_match; + } + /* Compare name of the enums. */ + if (strncmp(first->type.u.enum_nestable.name, + second->type.u.enum_nestable.name, + LTTNG_UST_ABI_SYM_NAME_LEN)) { + goto no_match; + } + /* Comparison of element type will be done when marshalling following item. */ + break; + case lttng_ust_ctl_atype_struct_nestable: + if (first->type.u.struct_nestable.alignment != second->type.u.struct_nestable.alignment) { + goto no_match; + } + /* Compare number of fields of the structs. */ + if (first->type.u.struct_nestable.nr_fields != second->type.u.struct_nestable.nr_fields) { + goto no_match; + } + break; + case lttng_ust_ctl_atype_variant_nestable: + if (first->type.u.variant_nestable.alignment != second->type.u.variant_nestable.alignment) { + goto no_match; + } + /* Compare number of choice of the variants. */ + if (first->type.u.variant_nestable.nr_choices != + second->type.u.variant_nestable.nr_choices) { + goto no_match; + } + + /* Compare tag name of the variants. */ + if (strncmp(first->type.u.variant_nestable.tag_name, + second->type.u.variant_nestable.tag_name, + LTTNG_UST_ABI_SYM_NAME_LEN)) { + goto no_match; + } + break; + default: + goto no_match; + } + + return true; + +no_match: + return false; +} diff --git a/src/bin/lttng-sessiond/ust-metadata.c b/src/bin/lttng-sessiond/ust-metadata.c deleted file mode 100644 index 7110662c7..000000000 --- a/src/bin/lttng-sessiond/ust-metadata.c +++ /dev/null @@ -1,1344 +0,0 @@ -/* - * Copyright (C) 2010-2013 Mathieu Desnoyers - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ust-registry.h" -#include "ust-clock.h" -#include "ust-app.h" - -#ifndef max_t -#define max_t(type, a, b) ((type) ((a) > (b) ? (a) : (b))) -#endif - -#define NR_CLOCK_OFFSET_SAMPLES 10 - -struct offset_sample { - int64_t offset; /* correlation offset */ - uint64_t measure_delta; /* lower is better */ -}; - -static -int _lttng_field_statedump(struct ust_registry_session *session, - const struct lttng_ust_ctl_field *fields, size_t nr_fields, - size_t *iter_field, size_t nesting); - -static inline -int get_count_order(unsigned int count) -{ - int order; - - order = lttng_fls(count) - 1; - if (count & (count - 1)) { - order++; - } - LTTNG_ASSERT(order >= 0); - return order; -} - -/* - * Returns offset where to write in metadata array, or negative error value on error. - */ -static -ssize_t metadata_reserve(struct ust_registry_session *session, size_t len) -{ - size_t new_len = session->metadata_len + len; - size_t new_alloc_len = new_len; - size_t old_alloc_len = session->metadata_alloc_len; - ssize_t ret; - - if (new_alloc_len > (UINT32_MAX >> 1)) - return -EINVAL; - if ((old_alloc_len << 1) > (UINT32_MAX >> 1)) - return -EINVAL; - - if (new_alloc_len > old_alloc_len) { - char *newptr; - - new_alloc_len = - max_t(size_t, 1U << get_count_order(new_alloc_len), old_alloc_len << 1); - newptr = realloc(session->metadata, new_alloc_len); - if (!newptr) - return -ENOMEM; - session->metadata = newptr; - /* We zero directly the memory from start of allocation. */ - memset(&session->metadata[old_alloc_len], 0, new_alloc_len - old_alloc_len); - session->metadata_alloc_len = new_alloc_len; - } - ret = session->metadata_len; - session->metadata_len += len; - return ret; -} - -static -int metadata_file_append(struct ust_registry_session *session, - const char *str, size_t len) -{ - ssize_t written; - - if (session->metadata_fd < 0) { - return 0; - } - /* Write to metadata file */ - written = lttng_write(session->metadata_fd, str, len); - if (written != len) { - return -1; - } - return 0; -} - -/* - * We have exclusive access to our metadata buffer (protected by the - * ust_lock), so we can do racy operations such as looking for - * remaining space left in packet and write, since mutual exclusion - * protects us from concurrent writes. - */ -static -int lttng_metadata_printf(struct ust_registry_session *session, - const char *fmt, ...) -{ - char *str = NULL; - size_t len; - va_list ap; - ssize_t offset; - int ret; - - va_start(ap, fmt); - ret = vasprintf(&str, fmt, ap); - va_end(ap); - if (ret < 0) - return -ENOMEM; - - len = strlen(str); - offset = metadata_reserve(session, len); - if (offset < 0) { - ret = offset; - goto end; - } - memcpy(&session->metadata[offset], str, len); - ret = metadata_file_append(session, str, len); - if (ret) { - PERROR("Error appending to metadata file"); - goto end; - } - DBG3("Append to metadata: \"%s\"", str); - ret = 0; - -end: - free(str); - return ret; -} - -static -int print_tabs(struct ust_registry_session *session, size_t nesting) -{ - size_t i; - - for (i = 0; i < nesting; i++) { - int ret; - - ret = lttng_metadata_printf(session, " "); - if (ret) { - return ret; - } - } - return 0; -} - -static -void sanitize_ctf_identifier(char *out, const char *in) -{ - size_t i; - - for (i = 0; i < LTTNG_UST_ABI_SYM_NAME_LEN; i++) { - switch (in[i]) { - case '.': - case '$': - case ':': - out[i] = '_'; - break; - default: - out[i] = in[i]; - } - } -} - -static -int print_escaped_ctf_string(struct ust_registry_session *session, const char *string) -{ - int ret = 0; - size_t i; - char cur; - - i = 0; - cur = string[i]; - while (cur != '\0') { - switch (cur) { - case '\n': - ret = lttng_metadata_printf(session, "%s", "\\n"); - break; - case '\\': - case '"': - ret = lttng_metadata_printf(session, "%c", '\\'); - if (ret) { - goto error; - } - /* We still print the current char */ - /* Fallthrough */ - default: - ret = lttng_metadata_printf(session, "%c", cur); - break; - } - - if (ret) { - goto error; - } - - cur = string[++i]; - } -error: - return ret; -} - -/* Called with session registry mutex held. */ -static -int ust_metadata_enum_statedump(struct ust_registry_session *session, - const char *enum_name, - uint64_t enum_id, - const struct lttng_ust_ctl_integer_type *container_type, - const char *field_name, size_t *iter_field, size_t nesting) -{ - struct ust_registry_enum *reg_enum; - const struct lttng_ust_ctl_enum_entry *entries; - size_t nr_entries; - int ret = 0; - size_t i; - char identifier[LTTNG_UST_ABI_SYM_NAME_LEN]; - - rcu_read_lock(); - reg_enum = ust_registry_lookup_enum_by_id(session, enum_name, enum_id); - rcu_read_unlock(); - /* reg_enum can still be used because session registry mutex is held. */ - if (!reg_enum) { - ret = -ENOENT; - goto end; - } - entries = reg_enum->entries; - nr_entries = reg_enum->nr_entries; - - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "enum : integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u; } {\n", - container_type->size, - container_type->alignment, - container_type->signedness, - (container_type->encoding == lttng_ust_ctl_encode_none) - ? "none" - : (container_type->encoding == lttng_ust_ctl_encode_UTF8) - ? "UTF8" - : "ASCII", - container_type->base); - if (ret) { - goto end; - } - nesting++; - /* Dump all entries */ - for (i = 0; i < nr_entries; i++) { - const struct lttng_ust_ctl_enum_entry *entry = &entries[i]; - int j, len; - - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "\""); - if (ret) { - goto end; - } - len = strlen(entry->string); - /* Escape the character '"' */ - for (j = 0; j < len; j++) { - char c = entry->string[j]; - - switch (c) { - case '"': - ret = lttng_metadata_printf(session, - "\\\""); - break; - case '\\': - ret = lttng_metadata_printf(session, - "\\\\"); - break; - default: - ret = lttng_metadata_printf(session, - "%c", c); - break; - } - if (ret) { - goto end; - } - } - ret = lttng_metadata_printf(session, "\""); - if (ret) { - goto end; - } - - if (entry->u.extra.options & - LTTNG_UST_CTL_UST_ENUM_ENTRY_OPTION_IS_AUTO) { - ret = lttng_metadata_printf(session, ",\n"); - if (ret) { - goto end; - } - } else { - ret = lttng_metadata_printf(session, - " = "); - if (ret) { - goto end; - } - - if (entry->start.signedness) { - ret = lttng_metadata_printf(session, - "%lld", (long long) entry->start.value); - } else { - ret = lttng_metadata_printf(session, - "%llu", entry->start.value); - } - if (ret) { - goto end; - } - - if (entry->start.signedness == entry->end.signedness && - entry->start.value == - entry->end.value) { - ret = lttng_metadata_printf(session, ",\n"); - } else { - if (entry->end.signedness) { - ret = lttng_metadata_printf(session, - " ... %lld,\n", - (long long) entry->end.value); - } else { - ret = lttng_metadata_printf(session, - " ... %llu,\n", - entry->end.value); - } - } - if (ret) { - goto end; - } - } - } - nesting--; - sanitize_ctf_identifier(identifier, field_name); - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, "} _%s;\n", - identifier); -end: - (*iter_field)++; - return ret; -} - -static -int _lttng_variant_statedump(struct ust_registry_session *session, - uint32_t nr_choices, const char *tag_name, - uint32_t alignment, - const struct lttng_ust_ctl_field *fields, size_t nr_fields, - size_t *iter_field, size_t nesting) -{ - const struct lttng_ust_ctl_field *variant = &fields[*iter_field]; - uint32_t i; - int ret; - char identifier[LTTNG_UST_ABI_SYM_NAME_LEN]; - - if (variant->type.atype != lttng_ust_ctl_atype_variant) { - ret = -EINVAL; - goto end; - } - (*iter_field)++; - sanitize_ctf_identifier(identifier, tag_name); - if (alignment) { - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "struct { } align(%u) _%s_padding;\n", - alignment * CHAR_BIT, - variant->name); - if (ret) { - goto end; - } - } - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "variant <_%s> {\n", - identifier); - if (ret) { - goto end; - } - - for (i = 0; i < nr_choices; i++) { - if (*iter_field >= nr_fields) { - ret = -EOVERFLOW; - goto end; - } - ret = _lttng_field_statedump(session, - fields, nr_fields, - iter_field, nesting + 1); - if (ret) { - goto end; - } - } - sanitize_ctf_identifier(identifier, variant->name); - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "} _%s;\n", - identifier); - if (ret) { - goto end; - } -end: - return ret; -} - -static -int _lttng_field_statedump(struct ust_registry_session *session, - const struct lttng_ust_ctl_field *fields, size_t nr_fields, - size_t *iter_field, size_t nesting) -{ - int ret = 0; - const char *bo_be = " byte_order = be;"; - const char *bo_le = " byte_order = le;"; - const char *bo_native = ""; - const char *bo_reverse; - const struct lttng_ust_ctl_field *field; - - if (*iter_field >= nr_fields) { - ret = -EOVERFLOW; - goto end; - } - field = &fields[*iter_field]; - - if (session->byte_order == BIG_ENDIAN) { - bo_reverse = bo_le; - } else { - bo_reverse = bo_be; - } - - switch (field->type.atype) { - case lttng_ust_ctl_atype_integer: - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s;\n", - field->type.u.integer.size, - field->type.u.integer.alignment, - field->type.u.integer.signedness, - (field->type.u.integer.encoding == lttng_ust_ctl_encode_none) - ? "none" - : (field->type.u.integer.encoding == lttng_ust_ctl_encode_UTF8) - ? "UTF8" - : "ASCII", - field->type.u.integer.base, - field->type.u.integer.reverse_byte_order ? bo_reverse : bo_native, - field->name); - (*iter_field)++; - break; - case lttng_ust_ctl_atype_enum: - ret = ust_metadata_enum_statedump(session, - field->type.u.legacy.basic.enumeration.name, - field->type.u.legacy.basic.enumeration.id, - &field->type.u.legacy.basic.enumeration.container_type, - field->name, iter_field, nesting); - break; - case lttng_ust_ctl_atype_float: - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "floating_point { exp_dig = %u; mant_dig = %u; align = %u;%s } _%s;\n", - field->type.u._float.exp_dig, - field->type.u._float.mant_dig, - field->type.u._float.alignment, - field->type.u._float.reverse_byte_order ? bo_reverse : bo_native, - field->name); - (*iter_field)++; - break; - case lttng_ust_ctl_atype_array: - { - const struct lttng_ust_ctl_basic_type *elem_type; - - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - elem_type = &field->type.u.legacy.array.elem_type; - /* Only integers are currently supported in arrays. */ - if (elem_type->atype != lttng_ust_ctl_atype_integer) { - ret = -EINVAL; - goto end; - } - ret = lttng_metadata_printf(session, - "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s[%u];\n", - elem_type->u.basic.integer.size, - elem_type->u.basic.integer.alignment, - elem_type->u.basic.integer.signedness, - (elem_type->u.basic.integer.encoding == lttng_ust_ctl_encode_none) - ? "none" - : (elem_type->u.basic.integer.encoding == lttng_ust_ctl_encode_UTF8) - ? "UTF8" - : "ASCII", - elem_type->u.basic.integer.base, - elem_type->u.basic.integer.reverse_byte_order ? bo_reverse : bo_native, - field->name, field->type.u.legacy.array.length); - (*iter_field)++; - break; - } - case lttng_ust_ctl_atype_array_nestable: - { - uint32_t array_length; - const struct lttng_ust_ctl_field *array_nestable; - const struct lttng_ust_ctl_type *elem_type; - - array_length = field->type.u.array_nestable.length; - (*iter_field)++; - - if (*iter_field >= nr_fields) { - ret = -EOVERFLOW; - goto end; - } - array_nestable = &fields[*iter_field]; - elem_type = &array_nestable->type; - - /* Only integers are currently supported in arrays. */ - if (elem_type->atype != lttng_ust_ctl_atype_integer) { - ret = -EINVAL; - goto end; - } - - if (field->type.u.array_nestable.alignment) { - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "struct { } align(%u) _%s_padding;\n", - field->type.u.array_nestable.alignment * CHAR_BIT, - field->name); - if (ret) { - goto end; - } - } - - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s[%u];\n", - elem_type->u.integer.size, - elem_type->u.integer.alignment, - elem_type->u.integer.signedness, - (elem_type->u.integer.encoding == lttng_ust_ctl_encode_none) - ? "none" - : (elem_type->u.integer.encoding == lttng_ust_ctl_encode_UTF8) - ? "UTF8" - : "ASCII", - elem_type->u.integer.base, - elem_type->u.integer.reverse_byte_order ? bo_reverse : bo_native, - field->name, array_length); - (*iter_field)++; - break; - } - case lttng_ust_ctl_atype_sequence: - { - const struct lttng_ust_ctl_basic_type *elem_type; - const struct lttng_ust_ctl_basic_type *length_type; - - elem_type = &field->type.u.legacy.sequence.elem_type; - length_type = &field->type.u.legacy.sequence.length_type; - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - - /* Only integers are currently supported in sequences. */ - if (elem_type->atype != lttng_ust_ctl_atype_integer) { - ret = -EINVAL; - goto end; - } - - ret = lttng_metadata_printf(session, - "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } __%s_length;\n", - length_type->u.basic.integer.size, - (unsigned int) length_type->u.basic.integer.alignment, - length_type->u.basic.integer.signedness, - (length_type->u.basic.integer.encoding == lttng_ust_ctl_encode_none) - ? "none" - : ((length_type->u.basic.integer.encoding == lttng_ust_ctl_encode_UTF8) - ? "UTF8" - : "ASCII"), - length_type->u.basic.integer.base, - length_type->u.basic.integer.reverse_byte_order ? bo_reverse : bo_native, - field->name); - if (ret) { - goto end; - } - - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s[ __%s_length ];\n", - elem_type->u.basic.integer.size, - (unsigned int) elem_type->u.basic.integer.alignment, - elem_type->u.basic.integer.signedness, - (elem_type->u.basic.integer.encoding == lttng_ust_ctl_encode_none) - ? "none" - : ((elem_type->u.basic.integer.encoding == lttng_ust_ctl_encode_UTF8) - ? "UTF8" - : "ASCII"), - elem_type->u.basic.integer.base, - elem_type->u.basic.integer.reverse_byte_order ? bo_reverse : bo_native, - field->name, - field->name); - (*iter_field)++; - break; - } - case lttng_ust_ctl_atype_sequence_nestable: - { - const struct lttng_ust_ctl_field *sequence_nestable; - const struct lttng_ust_ctl_type *elem_type; - - (*iter_field)++; - if (*iter_field >= nr_fields) { - ret = -EOVERFLOW; - goto end; - } - sequence_nestable = &fields[*iter_field]; - elem_type = &sequence_nestable->type; - - /* Only integers are currently supported in sequences. */ - if (elem_type->atype != lttng_ust_ctl_atype_integer) { - ret = -EINVAL; - goto end; - } - - if (field->type.u.sequence_nestable.alignment) { - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "struct { } align(%u) _%s_padding;\n", - field->type.u.sequence_nestable.alignment * CHAR_BIT, - field->name); - if (ret) { - goto end; - } - } - - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s[ _%s ];\n", - elem_type->u.integer.size, - (unsigned int) elem_type->u.integer.alignment, - elem_type->u.integer.signedness, - (elem_type->u.integer.encoding == lttng_ust_ctl_encode_none) - ? "none" - : ((elem_type->u.integer.encoding == lttng_ust_ctl_encode_UTF8) - ? "UTF8" - : "ASCII"), - elem_type->u.integer.base, - elem_type->u.integer.reverse_byte_order ? bo_reverse : bo_native, - field->name, - field->type.u.sequence_nestable.length_name); - (*iter_field)++; - break; - } - case lttng_ust_ctl_atype_string: - /* Default encoding is UTF8 */ - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "string%s _%s;\n", - field->type.u.string.encoding == lttng_ust_ctl_encode_ASCII ? - " { encoding = ASCII; }" : "", - field->name); - (*iter_field)++; - break; - case lttng_ust_ctl_atype_variant: - ret = _lttng_variant_statedump(session, - field->type.u.legacy.variant.nr_choices, - field->type.u.legacy.variant.tag_name, - 0, - fields, nr_fields, iter_field, nesting); - if (ret) { - goto end; - } - break; - case lttng_ust_ctl_atype_variant_nestable: - ret = _lttng_variant_statedump(session, - field->type.u.variant_nestable.nr_choices, - field->type.u.variant_nestable.tag_name, - field->type.u.variant_nestable.alignment, - fields, nr_fields, iter_field, nesting); - if (ret) { - goto end; - } - break; - case lttng_ust_ctl_atype_struct: - if (field->type.u.legacy._struct.nr_fields != 0) { - /* Currently only 0-length structures are supported. */ - ret = -EINVAL; - goto end; - } - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - ret = lttng_metadata_printf(session, - "struct {} _%s;\n", - field->name); - (*iter_field)++; - break; - case lttng_ust_ctl_atype_struct_nestable: - if (field->type.u.struct_nestable.nr_fields != 0) { - /* Currently only 0-length structures are supported. */ - ret = -EINVAL; - goto end; - } - ret = print_tabs(session, nesting); - if (ret) { - goto end; - } - if (field->type.u.struct_nestable.alignment) { - ret = lttng_metadata_printf(session, - "struct {} align(%u) _%s;\n", - field->type.u.struct_nestable.alignment * CHAR_BIT, - field->name); - if (ret) { - goto end; - } - } else { - ret = lttng_metadata_printf(session, - "struct {} _%s;\n", - field->name); - } - (*iter_field)++; - break; - case lttng_ust_ctl_atype_enum_nestable: - { - const struct lttng_ust_ctl_field *container_field; - const struct lttng_ust_ctl_type *container_type; - - (*iter_field)++; - if (*iter_field >= nr_fields) { - ret = -EOVERFLOW; - goto end; - } - container_field = &fields[*iter_field]; - container_type = &container_field->type; - - /* Only integers are supported as container types. */ - if (container_type->atype != lttng_ust_ctl_atype_integer) { - ret = -EINVAL; - goto end; - } - ret = ust_metadata_enum_statedump(session, - field->type.u.enum_nestable.name, - field->type.u.enum_nestable.id, - &container_type->u.integer, - field->name, iter_field, nesting); - break; - } - default: - ret = -EINVAL; - } -end: - return ret; -} - -static -int _lttng_context_metadata_statedump(struct ust_registry_session *session, - size_t nr_ctx_fields, - struct lttng_ust_ctl_field *ctx) -{ - int ret = 0; - size_t i = 0; - - if (!ctx) - return 0; - for (;;) { - if (i >= nr_ctx_fields) { - break; - } - ret = _lttng_field_statedump(session, ctx, - nr_ctx_fields, &i, 2); - if (ret) { - break; - } - } - return ret; -} - -static -int _lttng_fields_metadata_statedump(struct ust_registry_session *session, - struct ust_registry_event *event) -{ - int ret = 0; - size_t i = 0; - - for (;;) { - if (i >= event->nr_fields) { - break; - } - ret = _lttng_field_statedump(session, event->fields, - event->nr_fields, &i, 2); - if (ret) { - break; - } - } - return ret; -} - -/* - * Should be called with session registry mutex held. - */ -int ust_metadata_event_statedump(struct ust_registry_session *session, - struct ust_registry_channel *chan, - struct ust_registry_event *event) -{ - int ret = 0; - - /* Don't dump metadata events */ - if (chan->chan_id == -1U) - return 0; - - ret = lttng_metadata_printf(session, - "event {\n" - " name = \"%s\";\n" - " id = %u;\n" - " stream_id = %u;\n", - event->name, - event->id, - chan->chan_id); - if (ret) { - goto end; - } - - ret = lttng_metadata_printf(session, - " loglevel = %d;\n", - event->loglevel_value); - if (ret) { - goto end; - } - - if (event->model_emf_uri) { - ret = lttng_metadata_printf(session, - " model.emf.uri = \"%s\";\n", - event->model_emf_uri); - if (ret) { - goto end; - } - } - - ret = lttng_metadata_printf(session, - " fields := struct {\n" - ); - if (ret) { - goto end; - } - - ret = _lttng_fields_metadata_statedump(session, event); - if (ret) { - goto end; - } - - ret = lttng_metadata_printf(session, - " };\n" - "};\n\n"); - if (ret) { - goto end; - } - event->metadata_dumped = 1; - -end: - return ret; -} - -/* - * Should be called with session registry mutex held. - */ -int ust_metadata_channel_statedump(struct ust_registry_session *session, - struct ust_registry_channel *chan) -{ - int ret = 0; - - /* Don't dump metadata events */ - if (chan->chan_id == -1U) - return 0; - - if (!chan->header_type) - return -EINVAL; - - ret = lttng_metadata_printf(session, - "stream {\n" - " id = %u;\n" - " event.header := %s;\n" - " packet.context := struct packet_context;\n", - chan->chan_id, - chan->header_type == LTTNG_UST_CTL_CHANNEL_HEADER_COMPACT ? - "struct event_header_compact" : - "struct event_header_large"); - if (ret) { - goto end; - } - - if (chan->ctx_fields) { - ret = lttng_metadata_printf(session, - " event.context := struct {\n"); - if (ret) { - goto end; - } - } - ret = _lttng_context_metadata_statedump(session, - chan->nr_ctx_fields, - chan->ctx_fields); - if (ret) { - goto end; - } - if (chan->ctx_fields) { - ret = lttng_metadata_printf(session, - " };\n"); - if (ret) { - goto end; - } - } - - ret = lttng_metadata_printf(session, - "};\n\n"); - /* Flag success of metadata dump. */ - chan->metadata_dumped = 1; - -end: - return ret; -} - -static -int _lttng_stream_packet_context_declare(struct ust_registry_session *session) -{ - return lttng_metadata_printf(session, - "struct packet_context {\n" - " uint64_clock_monotonic_t timestamp_begin;\n" - " uint64_clock_monotonic_t timestamp_end;\n" - " uint64_t content_size;\n" - " uint64_t packet_size;\n" - " uint64_t packet_seq_num;\n" - " unsigned long events_discarded;\n" - " uint32_t cpu_id;\n" - "};\n\n" - ); -} - -/* - * Compact header: - * id: range: 0 - 30. - * id 31 is reserved to indicate an extended header. - * - * Large header: - * id: range: 0 - 65534. - * id 65535 is reserved to indicate an extended header. - */ -static -int _lttng_event_header_declare(struct ust_registry_session *session) -{ - return lttng_metadata_printf(session, - "struct event_header_compact {\n" - " enum : uint5_t { compact = 0 ... 30, extended = 31 } id;\n" - " variant {\n" - " struct {\n" - " uint27_clock_monotonic_t timestamp;\n" - " } compact;\n" - " struct {\n" - " uint32_t id;\n" - " uint64_clock_monotonic_t timestamp;\n" - " } extended;\n" - " } v;\n" - "} align(%u);\n" - "\n" - "struct event_header_large {\n" - " enum : uint16_t { compact = 0 ... 65534, extended = 65535 } id;\n" - " variant {\n" - " struct {\n" - " uint32_clock_monotonic_t timestamp;\n" - " } compact;\n" - " struct {\n" - " uint32_t id;\n" - " uint64_clock_monotonic_t timestamp;\n" - " } extended;\n" - " } v;\n" - "} align(%u);\n\n", - session->uint32_t_alignment, - session->uint16_t_alignment - ); -} - -/* - * The offset between monotonic and realtime clock can be negative if - * the system sets the REALTIME clock to 0 after boot. - */ -static -int measure_single_clock_offset(struct offset_sample *sample) -{ - uint64_t monotonic_avg, monotonic[2], measure_delta, realtime; - uint64_t tcf = trace_clock_freq(); - struct timespec rts = { 0, 0 }; - int ret; - - monotonic[0] = trace_clock_read64(); - ret = lttng_clock_gettime(CLOCK_REALTIME, &rts); - if (ret < 0) { - return ret; - } - monotonic[1] = trace_clock_read64(); - measure_delta = monotonic[1] - monotonic[0]; - if (measure_delta > sample->measure_delta) { - /* - * Discard value if it took longer to read than the best - * sample so far. - */ - return 0; - } - monotonic_avg = (monotonic[0] + monotonic[1]) >> 1; - realtime = (uint64_t) rts.tv_sec * tcf; - if (tcf == NSEC_PER_SEC) { - realtime += rts.tv_nsec; - } else { - realtime += (uint64_t) rts.tv_nsec * tcf / NSEC_PER_SEC; - } - sample->offset = (int64_t) realtime - monotonic_avg; - sample->measure_delta = measure_delta; - return 0; -} - -/* - * Approximation of NTP time of day to clock monotonic correlation, - * taken at start of trace. Keep the measurement that took the less time - * to complete, thus removing imprecision caused by preemption. - * May return a negative offset. - */ -static -int64_t measure_clock_offset(void) -{ - int i; - struct offset_sample offset_best_sample = { - .offset = 0, - .measure_delta = UINT64_MAX, - }; - - for (i = 0; i < NR_CLOCK_OFFSET_SAMPLES; i++) { - if (measure_single_clock_offset(&offset_best_sample)) { - return 0; - } - } - return offset_best_sample.offset; -} - -static -int print_metadata_session_information(struct ust_registry_session *registry) -{ - int ret; - struct ltt_session *session = NULL; - char creation_datetime[ISO8601_STR_LEN]; - - rcu_read_lock(); - session = session_find_by_id(registry->tracing_id); - if (!session) { - ret = -1; - goto error; - } - - /* Print the trace name */ - ret = lttng_metadata_printf(registry, " trace_name = \""); - if (ret) { - goto error; - } - - /* - * This is necessary since the creation time is present in the session - * name when it is generated. - */ - if (session->has_auto_generated_name) { - ret = print_escaped_ctf_string(registry, DEFAULT_SESSION_NAME); - } else { - ret = print_escaped_ctf_string(registry, session->name); - } - if (ret) { - goto error; - } - - ret = lttng_metadata_printf(registry, "\";\n"); - if (ret) { - goto error; - } - - /* Prepare creation time */ - ret = time_to_iso8601_str(session->creation_time, creation_datetime, - sizeof(creation_datetime)); - if (ret) { - goto error; - } - - /* Output the reste of the information */ - ret = lttng_metadata_printf(registry, - " trace_creation_datetime = \"%s\";\n" - " hostname = \"%s\";\n", - creation_datetime, session->hostname); - if (ret) { - goto error; - } - -error: - if (session) { - session_put(session); - } - rcu_read_unlock(); - return ret; -} - -static -int print_metadata_app_information(struct ust_registry_session *registry, - struct ust_app *app) -{ - int ret; - char datetime[ISO8601_STR_LEN]; - - if (!app) { - ret = 0; - goto end; - } - - ret = time_to_iso8601_str( - app->registration_time, datetime, sizeof(datetime)); - if (ret) { - goto end; - } - - ret = lttng_metadata_printf(registry, - " tracer_patchlevel = %u;\n" - " vpid = %d;\n" - " procname = \"%s\";\n" - " vpid_datetime = \"%s\";\n", - app->version.patchlevel, (int) app->pid, app->name, - datetime); - -end: - return ret; -} - -/* - * Should be called with session registry mutex held. - */ -int ust_metadata_session_statedump(struct ust_registry_session *session, - struct ust_app *app, - uint32_t major, - uint32_t minor) -{ - char uuid_s[LTTNG_UUID_STR_LEN], - clock_uuid_s[LTTNG_UUID_STR_LEN]; - int ret = 0; - - LTTNG_ASSERT(session); - - lttng_uuid_to_str(session->uuid, uuid_s); - - /* For crash ABI */ - ret = lttng_metadata_printf(session, - "/* CTF %u.%u */\n\n", - CTF_SPEC_MAJOR, - CTF_SPEC_MINOR); - if (ret) { - goto end; - } - - ret = lttng_metadata_printf(session, - "typealias integer { size = 8; align = %u; signed = false; } := uint8_t;\n" - "typealias integer { size = 16; align = %u; signed = false; } := uint16_t;\n" - "typealias integer { size = 32; align = %u; signed = false; } := uint32_t;\n" - "typealias integer { size = 64; align = %u; signed = false; } := uint64_t;\n" - "typealias integer { size = %u; align = %u; signed = false; } := unsigned long;\n" - "typealias integer { size = 5; align = 1; signed = false; } := uint5_t;\n" - "typealias integer { size = 27; align = 1; signed = false; } := uint27_t;\n" - "\n" - "trace {\n" - " major = %u;\n" - " minor = %u;\n" - " uuid = \"%s\";\n" - " byte_order = %s;\n" - " packet.header := struct {\n" - " uint32_t magic;\n" - " uint8_t uuid[16];\n" - " uint32_t stream_id;\n" - " uint64_t stream_instance_id;\n" - " };\n" - "};\n\n", - session->uint8_t_alignment, - session->uint16_t_alignment, - session->uint32_t_alignment, - session->uint64_t_alignment, - session->bits_per_long, - session->long_alignment, - CTF_SPEC_MAJOR, - CTF_SPEC_MINOR, - uuid_s, - session->byte_order == BIG_ENDIAN ? "be" : "le" - ); - if (ret) { - goto end; - } - - ret = lttng_metadata_printf(session, - "env {\n" - " domain = \"ust\";\n" - " tracer_name = \"lttng-ust\";\n" - " tracer_major = %u;\n" - " tracer_minor = %u;\n" - " tracer_buffering_scheme = \"%s\";\n" - " tracer_buffering_id = %u;\n" - " architecture_bit_width = %u;\n", - major, - minor, - app ? "pid" : "uid", - app ? (int) app->pid : (int) session->tracing_uid, - session->bits_per_long); - if (ret) { - goto end; - } - - ret = print_metadata_session_information(session); - if (ret) { - goto end; - } - - /* - * If per-application registry, we can output extra information - * about the application. - */ - ret = print_metadata_app_information(session, app); - if (ret) { - goto end; - } - - ret = lttng_metadata_printf(session, - "};\n\n" - ); - if (ret) { - goto end; - } - - ret = lttng_metadata_printf(session, - "clock {\n" - " name = \"%s\";\n", - trace_clock_name() - ); - if (ret) { - goto end; - } - - if (!trace_clock_uuid(clock_uuid_s)) { - ret = lttng_metadata_printf(session, - " uuid = \"%s\";\n", - clock_uuid_s - ); - if (ret) { - goto end; - } - } - - ret = lttng_metadata_printf(session, - " description = \"%s\";\n" - " freq = %" PRIu64 "; /* Frequency, in Hz */\n" - " /* clock value offset from Epoch is: offset * (1/freq) */\n" - " offset = %" PRId64 ";\n" - "};\n\n", - trace_clock_description(), - trace_clock_freq(), - measure_clock_offset() - ); - if (ret) { - goto end; - } - - ret = lttng_metadata_printf(session, - "typealias integer {\n" - " size = 27; align = 1; signed = false;\n" - " map = clock.%s.value;\n" - "} := uint27_clock_monotonic_t;\n" - "\n" - "typealias integer {\n" - " size = 32; align = %u; signed = false;\n" - " map = clock.%s.value;\n" - "} := uint32_clock_monotonic_t;\n" - "\n" - "typealias integer {\n" - " size = 64; align = %u; signed = false;\n" - " map = clock.%s.value;\n" - "} := uint64_clock_monotonic_t;\n\n", - trace_clock_name(), - session->uint32_t_alignment, - trace_clock_name(), - session->uint64_t_alignment, - trace_clock_name() - ); - if (ret) { - goto end; - } - - ret = _lttng_stream_packet_context_declare(session); - if (ret) { - goto end; - } - - ret = _lttng_event_header_declare(session); - if (ret) { - goto end; - } - -end: - return ret; -} diff --git a/src/bin/lttng-sessiond/ust-metadata.cpp b/src/bin/lttng-sessiond/ust-metadata.cpp new file mode 100644 index 000000000..abe473f27 --- /dev/null +++ b/src/bin/lttng-sessiond/ust-metadata.cpp @@ -0,0 +1,1344 @@ +/* + * Copyright (C) 2010-2013 Mathieu Desnoyers + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ust-registry.h" +#include "ust-clock.h" +#include "ust-app.h" + +#ifndef max_t +#define max_t(type, a, b) ((type) ((a) > (b) ? (a) : (b))) +#endif + +#define NR_CLOCK_OFFSET_SAMPLES 10 + +struct offset_sample { + int64_t offset; /* correlation offset */ + uint64_t measure_delta; /* lower is better */ +}; + +static +int _lttng_field_statedump(struct ust_registry_session *session, + const struct lttng_ust_ctl_field *fields, size_t nr_fields, + size_t *iter_field, size_t nesting); + +static inline +int get_count_order(unsigned int count) +{ + int order; + + order = lttng_fls(count) - 1; + if (count & (count - 1)) { + order++; + } + LTTNG_ASSERT(order >= 0); + return order; +} + +/* + * Returns offset where to write in metadata array, or negative error value on error. + */ +static +ssize_t metadata_reserve(struct ust_registry_session *session, size_t len) +{ + size_t new_len = session->metadata_len + len; + size_t new_alloc_len = new_len; + size_t old_alloc_len = session->metadata_alloc_len; + ssize_t ret; + + if (new_alloc_len > (UINT32_MAX >> 1)) + return -EINVAL; + if ((old_alloc_len << 1) > (UINT32_MAX >> 1)) + return -EINVAL; + + if (new_alloc_len > old_alloc_len) { + char *newptr; + + new_alloc_len = + max_t(size_t, 1U << get_count_order(new_alloc_len), old_alloc_len << 1); + newptr = (char *) realloc(session->metadata, new_alloc_len); + if (!newptr) + return -ENOMEM; + session->metadata = newptr; + /* We zero directly the memory from start of allocation. */ + memset(&session->metadata[old_alloc_len], 0, new_alloc_len - old_alloc_len); + session->metadata_alloc_len = new_alloc_len; + } + ret = session->metadata_len; + session->metadata_len += len; + return ret; +} + +static +int metadata_file_append(struct ust_registry_session *session, + const char *str, size_t len) +{ + ssize_t written; + + if (session->metadata_fd < 0) { + return 0; + } + /* Write to metadata file */ + written = lttng_write(session->metadata_fd, str, len); + if (written != len) { + return -1; + } + return 0; +} + +/* + * We have exclusive access to our metadata buffer (protected by the + * ust_lock), so we can do racy operations such as looking for + * remaining space left in packet and write, since mutual exclusion + * protects us from concurrent writes. + */ +static +int lttng_metadata_printf(struct ust_registry_session *session, + const char *fmt, ...) +{ + char *str = NULL; + size_t len; + va_list ap; + ssize_t offset; + int ret; + + va_start(ap, fmt); + ret = vasprintf(&str, fmt, ap); + va_end(ap); + if (ret < 0) + return -ENOMEM; + + len = strlen(str); + offset = metadata_reserve(session, len); + if (offset < 0) { + ret = offset; + goto end; + } + memcpy(&session->metadata[offset], str, len); + ret = metadata_file_append(session, str, len); + if (ret) { + PERROR("Error appending to metadata file"); + goto end; + } + DBG3("Append to metadata: \"%s\"", str); + ret = 0; + +end: + free(str); + return ret; +} + +static +int print_tabs(struct ust_registry_session *session, size_t nesting) +{ + size_t i; + + for (i = 0; i < nesting; i++) { + int ret; + + ret = lttng_metadata_printf(session, " "); + if (ret) { + return ret; + } + } + return 0; +} + +static +void sanitize_ctf_identifier(char *out, const char *in) +{ + size_t i; + + for (i = 0; i < LTTNG_UST_ABI_SYM_NAME_LEN; i++) { + switch (in[i]) { + case '.': + case '$': + case ':': + out[i] = '_'; + break; + default: + out[i] = in[i]; + } + } +} + +static +int print_escaped_ctf_string(struct ust_registry_session *session, const char *string) +{ + int ret = 0; + size_t i; + char cur; + + i = 0; + cur = string[i]; + while (cur != '\0') { + switch (cur) { + case '\n': + ret = lttng_metadata_printf(session, "%s", "\\n"); + break; + case '\\': + case '"': + ret = lttng_metadata_printf(session, "%c", '\\'); + if (ret) { + goto error; + } + /* We still print the current char */ + /* Fallthrough */ + default: + ret = lttng_metadata_printf(session, "%c", cur); + break; + } + + if (ret) { + goto error; + } + + cur = string[++i]; + } +error: + return ret; +} + +/* Called with session registry mutex held. */ +static +int ust_metadata_enum_statedump(struct ust_registry_session *session, + const char *enum_name, + uint64_t enum_id, + const struct lttng_ust_ctl_integer_type *container_type, + const char *field_name, size_t *iter_field, size_t nesting) +{ + struct ust_registry_enum *reg_enum; + const struct lttng_ust_ctl_enum_entry *entries; + size_t nr_entries; + int ret = 0; + size_t i; + char identifier[LTTNG_UST_ABI_SYM_NAME_LEN]; + + rcu_read_lock(); + reg_enum = ust_registry_lookup_enum_by_id(session, enum_name, enum_id); + rcu_read_unlock(); + /* reg_enum can still be used because session registry mutex is held. */ + if (!reg_enum) { + ret = -ENOENT; + goto end; + } + entries = reg_enum->entries; + nr_entries = reg_enum->nr_entries; + + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "enum : integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u; } {\n", + container_type->size, + container_type->alignment, + container_type->signedness, + (container_type->encoding == lttng_ust_ctl_encode_none) + ? "none" + : (container_type->encoding == lttng_ust_ctl_encode_UTF8) + ? "UTF8" + : "ASCII", + container_type->base); + if (ret) { + goto end; + } + nesting++; + /* Dump all entries */ + for (i = 0; i < nr_entries; i++) { + const struct lttng_ust_ctl_enum_entry *entry = &entries[i]; + int j, len; + + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "\""); + if (ret) { + goto end; + } + len = strlen(entry->string); + /* Escape the character '"' */ + for (j = 0; j < len; j++) { + char c = entry->string[j]; + + switch (c) { + case '"': + ret = lttng_metadata_printf(session, + "\\\""); + break; + case '\\': + ret = lttng_metadata_printf(session, + "\\\\"); + break; + default: + ret = lttng_metadata_printf(session, + "%c", c); + break; + } + if (ret) { + goto end; + } + } + ret = lttng_metadata_printf(session, "\""); + if (ret) { + goto end; + } + + if (entry->u.extra.options & + LTTNG_UST_CTL_UST_ENUM_ENTRY_OPTION_IS_AUTO) { + ret = lttng_metadata_printf(session, ",\n"); + if (ret) { + goto end; + } + } else { + ret = lttng_metadata_printf(session, + " = "); + if (ret) { + goto end; + } + + if (entry->start.signedness) { + ret = lttng_metadata_printf(session, + "%lld", (long long) entry->start.value); + } else { + ret = lttng_metadata_printf(session, + "%llu", entry->start.value); + } + if (ret) { + goto end; + } + + if (entry->start.signedness == entry->end.signedness && + entry->start.value == + entry->end.value) { + ret = lttng_metadata_printf(session, ",\n"); + } else { + if (entry->end.signedness) { + ret = lttng_metadata_printf(session, + " ... %lld,\n", + (long long) entry->end.value); + } else { + ret = lttng_metadata_printf(session, + " ... %llu,\n", + entry->end.value); + } + } + if (ret) { + goto end; + } + } + } + nesting--; + sanitize_ctf_identifier(identifier, field_name); + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, "} _%s;\n", + identifier); +end: + (*iter_field)++; + return ret; +} + +static +int _lttng_variant_statedump(struct ust_registry_session *session, + uint32_t nr_choices, const char *tag_name, + uint32_t alignment, + const struct lttng_ust_ctl_field *fields, size_t nr_fields, + size_t *iter_field, size_t nesting) +{ + const struct lttng_ust_ctl_field *variant = &fields[*iter_field]; + uint32_t i; + int ret; + char identifier[LTTNG_UST_ABI_SYM_NAME_LEN]; + + if (variant->type.atype != lttng_ust_ctl_atype_variant) { + ret = -EINVAL; + goto end; + } + (*iter_field)++; + sanitize_ctf_identifier(identifier, tag_name); + if (alignment) { + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "struct { } align(%u) _%s_padding;\n", + alignment * CHAR_BIT, + variant->name); + if (ret) { + goto end; + } + } + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "variant <_%s> {\n", + identifier); + if (ret) { + goto end; + } + + for (i = 0; i < nr_choices; i++) { + if (*iter_field >= nr_fields) { + ret = -EOVERFLOW; + goto end; + } + ret = _lttng_field_statedump(session, + fields, nr_fields, + iter_field, nesting + 1); + if (ret) { + goto end; + } + } + sanitize_ctf_identifier(identifier, variant->name); + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "} _%s;\n", + identifier); + if (ret) { + goto end; + } +end: + return ret; +} + +static +int _lttng_field_statedump(struct ust_registry_session *session, + const struct lttng_ust_ctl_field *fields, size_t nr_fields, + size_t *iter_field, size_t nesting) +{ + int ret = 0; + const char *bo_be = " byte_order = be;"; + const char *bo_le = " byte_order = le;"; + const char *bo_native = ""; + const char *bo_reverse; + const struct lttng_ust_ctl_field *field; + + if (*iter_field >= nr_fields) { + ret = -EOVERFLOW; + goto end; + } + field = &fields[*iter_field]; + + if (session->byte_order == BIG_ENDIAN) { + bo_reverse = bo_le; + } else { + bo_reverse = bo_be; + } + + switch (field->type.atype) { + case lttng_ust_ctl_atype_integer: + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s;\n", + field->type.u.integer.size, + field->type.u.integer.alignment, + field->type.u.integer.signedness, + (field->type.u.integer.encoding == lttng_ust_ctl_encode_none) + ? "none" + : (field->type.u.integer.encoding == lttng_ust_ctl_encode_UTF8) + ? "UTF8" + : "ASCII", + field->type.u.integer.base, + field->type.u.integer.reverse_byte_order ? bo_reverse : bo_native, + field->name); + (*iter_field)++; + break; + case lttng_ust_ctl_atype_enum: + ret = ust_metadata_enum_statedump(session, + field->type.u.legacy.basic.enumeration.name, + field->type.u.legacy.basic.enumeration.id, + &field->type.u.legacy.basic.enumeration.container_type, + field->name, iter_field, nesting); + break; + case lttng_ust_ctl_atype_float: + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "floating_point { exp_dig = %u; mant_dig = %u; align = %u;%s } _%s;\n", + field->type.u._float.exp_dig, + field->type.u._float.mant_dig, + field->type.u._float.alignment, + field->type.u._float.reverse_byte_order ? bo_reverse : bo_native, + field->name); + (*iter_field)++; + break; + case lttng_ust_ctl_atype_array: + { + const struct lttng_ust_ctl_basic_type *elem_type; + + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + elem_type = &field->type.u.legacy.array.elem_type; + /* Only integers are currently supported in arrays. */ + if (elem_type->atype != lttng_ust_ctl_atype_integer) { + ret = -EINVAL; + goto end; + } + ret = lttng_metadata_printf(session, + "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s[%u];\n", + elem_type->u.basic.integer.size, + elem_type->u.basic.integer.alignment, + elem_type->u.basic.integer.signedness, + (elem_type->u.basic.integer.encoding == lttng_ust_ctl_encode_none) + ? "none" + : (elem_type->u.basic.integer.encoding == lttng_ust_ctl_encode_UTF8) + ? "UTF8" + : "ASCII", + elem_type->u.basic.integer.base, + elem_type->u.basic.integer.reverse_byte_order ? bo_reverse : bo_native, + field->name, field->type.u.legacy.array.length); + (*iter_field)++; + break; + } + case lttng_ust_ctl_atype_array_nestable: + { + uint32_t array_length; + const struct lttng_ust_ctl_field *array_nestable; + const struct lttng_ust_ctl_type *elem_type; + + array_length = field->type.u.array_nestable.length; + (*iter_field)++; + + if (*iter_field >= nr_fields) { + ret = -EOVERFLOW; + goto end; + } + array_nestable = &fields[*iter_field]; + elem_type = &array_nestable->type; + + /* Only integers are currently supported in arrays. */ + if (elem_type->atype != lttng_ust_ctl_atype_integer) { + ret = -EINVAL; + goto end; + } + + if (field->type.u.array_nestable.alignment) { + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "struct { } align(%u) _%s_padding;\n", + field->type.u.array_nestable.alignment * CHAR_BIT, + field->name); + if (ret) { + goto end; + } + } + + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s[%u];\n", + elem_type->u.integer.size, + elem_type->u.integer.alignment, + elem_type->u.integer.signedness, + (elem_type->u.integer.encoding == lttng_ust_ctl_encode_none) + ? "none" + : (elem_type->u.integer.encoding == lttng_ust_ctl_encode_UTF8) + ? "UTF8" + : "ASCII", + elem_type->u.integer.base, + elem_type->u.integer.reverse_byte_order ? bo_reverse : bo_native, + field->name, array_length); + (*iter_field)++; + break; + } + case lttng_ust_ctl_atype_sequence: + { + const struct lttng_ust_ctl_basic_type *elem_type; + const struct lttng_ust_ctl_basic_type *length_type; + + elem_type = &field->type.u.legacy.sequence.elem_type; + length_type = &field->type.u.legacy.sequence.length_type; + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + + /* Only integers are currently supported in sequences. */ + if (elem_type->atype != lttng_ust_ctl_atype_integer) { + ret = -EINVAL; + goto end; + } + + ret = lttng_metadata_printf(session, + "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } __%s_length;\n", + length_type->u.basic.integer.size, + (unsigned int) length_type->u.basic.integer.alignment, + length_type->u.basic.integer.signedness, + (length_type->u.basic.integer.encoding == lttng_ust_ctl_encode_none) + ? "none" + : ((length_type->u.basic.integer.encoding == lttng_ust_ctl_encode_UTF8) + ? "UTF8" + : "ASCII"), + length_type->u.basic.integer.base, + length_type->u.basic.integer.reverse_byte_order ? bo_reverse : bo_native, + field->name); + if (ret) { + goto end; + } + + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s[ __%s_length ];\n", + elem_type->u.basic.integer.size, + (unsigned int) elem_type->u.basic.integer.alignment, + elem_type->u.basic.integer.signedness, + (elem_type->u.basic.integer.encoding == lttng_ust_ctl_encode_none) + ? "none" + : ((elem_type->u.basic.integer.encoding == lttng_ust_ctl_encode_UTF8) + ? "UTF8" + : "ASCII"), + elem_type->u.basic.integer.base, + elem_type->u.basic.integer.reverse_byte_order ? bo_reverse : bo_native, + field->name, + field->name); + (*iter_field)++; + break; + } + case lttng_ust_ctl_atype_sequence_nestable: + { + const struct lttng_ust_ctl_field *sequence_nestable; + const struct lttng_ust_ctl_type *elem_type; + + (*iter_field)++; + if (*iter_field >= nr_fields) { + ret = -EOVERFLOW; + goto end; + } + sequence_nestable = &fields[*iter_field]; + elem_type = &sequence_nestable->type; + + /* Only integers are currently supported in sequences. */ + if (elem_type->atype != lttng_ust_ctl_atype_integer) { + ret = -EINVAL; + goto end; + } + + if (field->type.u.sequence_nestable.alignment) { + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "struct { } align(%u) _%s_padding;\n", + field->type.u.sequence_nestable.alignment * CHAR_BIT, + field->name); + if (ret) { + goto end; + } + } + + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "integer { size = %u; align = %u; signed = %u; encoding = %s; base = %u;%s } _%s[ _%s ];\n", + elem_type->u.integer.size, + (unsigned int) elem_type->u.integer.alignment, + elem_type->u.integer.signedness, + (elem_type->u.integer.encoding == lttng_ust_ctl_encode_none) + ? "none" + : ((elem_type->u.integer.encoding == lttng_ust_ctl_encode_UTF8) + ? "UTF8" + : "ASCII"), + elem_type->u.integer.base, + elem_type->u.integer.reverse_byte_order ? bo_reverse : bo_native, + field->name, + field->type.u.sequence_nestable.length_name); + (*iter_field)++; + break; + } + case lttng_ust_ctl_atype_string: + /* Default encoding is UTF8 */ + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "string%s _%s;\n", + field->type.u.string.encoding == lttng_ust_ctl_encode_ASCII ? + " { encoding = ASCII; }" : "", + field->name); + (*iter_field)++; + break; + case lttng_ust_ctl_atype_variant: + ret = _lttng_variant_statedump(session, + field->type.u.legacy.variant.nr_choices, + field->type.u.legacy.variant.tag_name, + 0, + fields, nr_fields, iter_field, nesting); + if (ret) { + goto end; + } + break; + case lttng_ust_ctl_atype_variant_nestable: + ret = _lttng_variant_statedump(session, + field->type.u.variant_nestable.nr_choices, + field->type.u.variant_nestable.tag_name, + field->type.u.variant_nestable.alignment, + fields, nr_fields, iter_field, nesting); + if (ret) { + goto end; + } + break; + case lttng_ust_ctl_atype_struct: + if (field->type.u.legacy._struct.nr_fields != 0) { + /* Currently only 0-length structures are supported. */ + ret = -EINVAL; + goto end; + } + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + ret = lttng_metadata_printf(session, + "struct {} _%s;\n", + field->name); + (*iter_field)++; + break; + case lttng_ust_ctl_atype_struct_nestable: + if (field->type.u.struct_nestable.nr_fields != 0) { + /* Currently only 0-length structures are supported. */ + ret = -EINVAL; + goto end; + } + ret = print_tabs(session, nesting); + if (ret) { + goto end; + } + if (field->type.u.struct_nestable.alignment) { + ret = lttng_metadata_printf(session, + "struct {} align(%u) _%s;\n", + field->type.u.struct_nestable.alignment * CHAR_BIT, + field->name); + if (ret) { + goto end; + } + } else { + ret = lttng_metadata_printf(session, + "struct {} _%s;\n", + field->name); + } + (*iter_field)++; + break; + case lttng_ust_ctl_atype_enum_nestable: + { + const struct lttng_ust_ctl_field *container_field; + const struct lttng_ust_ctl_type *container_type; + + (*iter_field)++; + if (*iter_field >= nr_fields) { + ret = -EOVERFLOW; + goto end; + } + container_field = &fields[*iter_field]; + container_type = &container_field->type; + + /* Only integers are supported as container types. */ + if (container_type->atype != lttng_ust_ctl_atype_integer) { + ret = -EINVAL; + goto end; + } + ret = ust_metadata_enum_statedump(session, + field->type.u.enum_nestable.name, + field->type.u.enum_nestable.id, + &container_type->u.integer, + field->name, iter_field, nesting); + break; + } + default: + ret = -EINVAL; + } +end: + return ret; +} + +static +int _lttng_context_metadata_statedump(struct ust_registry_session *session, + size_t nr_ctx_fields, + struct lttng_ust_ctl_field *ctx) +{ + int ret = 0; + size_t i = 0; + + if (!ctx) + return 0; + for (;;) { + if (i >= nr_ctx_fields) { + break; + } + ret = _lttng_field_statedump(session, ctx, + nr_ctx_fields, &i, 2); + if (ret) { + break; + } + } + return ret; +} + +static +int _lttng_fields_metadata_statedump(struct ust_registry_session *session, + struct ust_registry_event *event) +{ + int ret = 0; + size_t i = 0; + + for (;;) { + if (i >= event->nr_fields) { + break; + } + ret = _lttng_field_statedump(session, event->fields, + event->nr_fields, &i, 2); + if (ret) { + break; + } + } + return ret; +} + +/* + * Should be called with session registry mutex held. + */ +int ust_metadata_event_statedump(struct ust_registry_session *session, + struct ust_registry_channel *chan, + struct ust_registry_event *event) +{ + int ret = 0; + + /* Don't dump metadata events */ + if (chan->chan_id == -1U) + return 0; + + ret = lttng_metadata_printf(session, + "event {\n" + " name = \"%s\";\n" + " id = %u;\n" + " stream_id = %u;\n", + event->name, + event->id, + chan->chan_id); + if (ret) { + goto end; + } + + ret = lttng_metadata_printf(session, + " loglevel = %d;\n", + event->loglevel_value); + if (ret) { + goto end; + } + + if (event->model_emf_uri) { + ret = lttng_metadata_printf(session, + " model.emf.uri = \"%s\";\n", + event->model_emf_uri); + if (ret) { + goto end; + } + } + + ret = lttng_metadata_printf(session, + " fields := struct {\n" + ); + if (ret) { + goto end; + } + + ret = _lttng_fields_metadata_statedump(session, event); + if (ret) { + goto end; + } + + ret = lttng_metadata_printf(session, + " };\n" + "};\n\n"); + if (ret) { + goto end; + } + event->metadata_dumped = 1; + +end: + return ret; +} + +/* + * Should be called with session registry mutex held. + */ +int ust_metadata_channel_statedump(struct ust_registry_session *session, + struct ust_registry_channel *chan) +{ + int ret = 0; + + /* Don't dump metadata events */ + if (chan->chan_id == -1U) + return 0; + + if (!chan->header_type) + return -EINVAL; + + ret = lttng_metadata_printf(session, + "stream {\n" + " id = %u;\n" + " event.header := %s;\n" + " packet.context := struct packet_context;\n", + chan->chan_id, + chan->header_type == LTTNG_UST_CTL_CHANNEL_HEADER_COMPACT ? + "struct event_header_compact" : + "struct event_header_large"); + if (ret) { + goto end; + } + + if (chan->ctx_fields) { + ret = lttng_metadata_printf(session, + " event.context := struct {\n"); + if (ret) { + goto end; + } + } + ret = _lttng_context_metadata_statedump(session, + chan->nr_ctx_fields, + chan->ctx_fields); + if (ret) { + goto end; + } + if (chan->ctx_fields) { + ret = lttng_metadata_printf(session, + " };\n"); + if (ret) { + goto end; + } + } + + ret = lttng_metadata_printf(session, + "};\n\n"); + /* Flag success of metadata dump. */ + chan->metadata_dumped = 1; + +end: + return ret; +} + +static +int _lttng_stream_packet_context_declare(struct ust_registry_session *session) +{ + return lttng_metadata_printf(session, + "struct packet_context {\n" + " uint64_clock_monotonic_t timestamp_begin;\n" + " uint64_clock_monotonic_t timestamp_end;\n" + " uint64_t content_size;\n" + " uint64_t packet_size;\n" + " uint64_t packet_seq_num;\n" + " unsigned long events_discarded;\n" + " uint32_t cpu_id;\n" + "};\n\n" + ); +} + +/* + * Compact header: + * id: range: 0 - 30. + * id 31 is reserved to indicate an extended header. + * + * Large header: + * id: range: 0 - 65534. + * id 65535 is reserved to indicate an extended header. + */ +static +int _lttng_event_header_declare(struct ust_registry_session *session) +{ + return lttng_metadata_printf(session, + "struct event_header_compact {\n" + " enum : uint5_t { compact = 0 ... 30, extended = 31 } id;\n" + " variant {\n" + " struct {\n" + " uint27_clock_monotonic_t timestamp;\n" + " } compact;\n" + " struct {\n" + " uint32_t id;\n" + " uint64_clock_monotonic_t timestamp;\n" + " } extended;\n" + " } v;\n" + "} align(%u);\n" + "\n" + "struct event_header_large {\n" + " enum : uint16_t { compact = 0 ... 65534, extended = 65535 } id;\n" + " variant {\n" + " struct {\n" + " uint32_clock_monotonic_t timestamp;\n" + " } compact;\n" + " struct {\n" + " uint32_t id;\n" + " uint64_clock_monotonic_t timestamp;\n" + " } extended;\n" + " } v;\n" + "} align(%u);\n\n", + session->uint32_t_alignment, + session->uint16_t_alignment + ); +} + +/* + * The offset between monotonic and realtime clock can be negative if + * the system sets the REALTIME clock to 0 after boot. + */ +static +int measure_single_clock_offset(struct offset_sample *sample) +{ + uint64_t monotonic_avg, monotonic[2], measure_delta, realtime; + uint64_t tcf = trace_clock_freq(); + struct timespec rts = { 0, 0 }; + int ret; + + monotonic[0] = trace_clock_read64(); + ret = lttng_clock_gettime(CLOCK_REALTIME, &rts); + if (ret < 0) { + return ret; + } + monotonic[1] = trace_clock_read64(); + measure_delta = monotonic[1] - monotonic[0]; + if (measure_delta > sample->measure_delta) { + /* + * Discard value if it took longer to read than the best + * sample so far. + */ + return 0; + } + monotonic_avg = (monotonic[0] + monotonic[1]) >> 1; + realtime = (uint64_t) rts.tv_sec * tcf; + if (tcf == NSEC_PER_SEC) { + realtime += rts.tv_nsec; + } else { + realtime += (uint64_t) rts.tv_nsec * tcf / NSEC_PER_SEC; + } + sample->offset = (int64_t) realtime - monotonic_avg; + sample->measure_delta = measure_delta; + return 0; +} + +/* + * Approximation of NTP time of day to clock monotonic correlation, + * taken at start of trace. Keep the measurement that took the less time + * to complete, thus removing imprecision caused by preemption. + * May return a negative offset. + */ +static +int64_t measure_clock_offset(void) +{ + int i; + struct offset_sample offset_best_sample = { + .offset = 0, + .measure_delta = UINT64_MAX, + }; + + for (i = 0; i < NR_CLOCK_OFFSET_SAMPLES; i++) { + if (measure_single_clock_offset(&offset_best_sample)) { + return 0; + } + } + return offset_best_sample.offset; +} + +static +int print_metadata_session_information(struct ust_registry_session *registry) +{ + int ret; + struct ltt_session *session = NULL; + char creation_datetime[ISO8601_STR_LEN]; + + rcu_read_lock(); + session = session_find_by_id(registry->tracing_id); + if (!session) { + ret = -1; + goto error; + } + + /* Print the trace name */ + ret = lttng_metadata_printf(registry, " trace_name = \""); + if (ret) { + goto error; + } + + /* + * This is necessary since the creation time is present in the session + * name when it is generated. + */ + if (session->has_auto_generated_name) { + ret = print_escaped_ctf_string(registry, DEFAULT_SESSION_NAME); + } else { + ret = print_escaped_ctf_string(registry, session->name); + } + if (ret) { + goto error; + } + + ret = lttng_metadata_printf(registry, "\";\n"); + if (ret) { + goto error; + } + + /* Prepare creation time */ + ret = time_to_iso8601_str(session->creation_time, creation_datetime, + sizeof(creation_datetime)); + if (ret) { + goto error; + } + + /* Output the reste of the information */ + ret = lttng_metadata_printf(registry, + " trace_creation_datetime = \"%s\";\n" + " hostname = \"%s\";\n", + creation_datetime, session->hostname); + if (ret) { + goto error; + } + +error: + if (session) { + session_put(session); + } + rcu_read_unlock(); + return ret; +} + +static +int print_metadata_app_information(struct ust_registry_session *registry, + struct ust_app *app) +{ + int ret; + char datetime[ISO8601_STR_LEN]; + + if (!app) { + ret = 0; + goto end; + } + + ret = time_to_iso8601_str( + app->registration_time, datetime, sizeof(datetime)); + if (ret) { + goto end; + } + + ret = lttng_metadata_printf(registry, + " tracer_patchlevel = %u;\n" + " vpid = %d;\n" + " procname = \"%s\";\n" + " vpid_datetime = \"%s\";\n", + app->version.patchlevel, (int) app->pid, app->name, + datetime); + +end: + return ret; +} + +/* + * Should be called with session registry mutex held. + */ +int ust_metadata_session_statedump(struct ust_registry_session *session, + struct ust_app *app, + uint32_t major, + uint32_t minor) +{ + char uuid_s[LTTNG_UUID_STR_LEN], + clock_uuid_s[LTTNG_UUID_STR_LEN]; + int ret = 0; + + LTTNG_ASSERT(session); + + lttng_uuid_to_str(session->uuid, uuid_s); + + /* For crash ABI */ + ret = lttng_metadata_printf(session, + "/* CTF %u.%u */\n\n", + CTF_SPEC_MAJOR, + CTF_SPEC_MINOR); + if (ret) { + goto end; + } + + ret = lttng_metadata_printf(session, + "typealias integer { size = 8; align = %u; signed = false; } := uint8_t;\n" + "typealias integer { size = 16; align = %u; signed = false; } := uint16_t;\n" + "typealias integer { size = 32; align = %u; signed = false; } := uint32_t;\n" + "typealias integer { size = 64; align = %u; signed = false; } := uint64_t;\n" + "typealias integer { size = %u; align = %u; signed = false; } := unsigned long;\n" + "typealias integer { size = 5; align = 1; signed = false; } := uint5_t;\n" + "typealias integer { size = 27; align = 1; signed = false; } := uint27_t;\n" + "\n" + "trace {\n" + " major = %u;\n" + " minor = %u;\n" + " uuid = \"%s\";\n" + " byte_order = %s;\n" + " packet.header := struct {\n" + " uint32_t magic;\n" + " uint8_t uuid[16];\n" + " uint32_t stream_id;\n" + " uint64_t stream_instance_id;\n" + " };\n" + "};\n\n", + session->uint8_t_alignment, + session->uint16_t_alignment, + session->uint32_t_alignment, + session->uint64_t_alignment, + session->bits_per_long, + session->long_alignment, + CTF_SPEC_MAJOR, + CTF_SPEC_MINOR, + uuid_s, + session->byte_order == BIG_ENDIAN ? "be" : "le" + ); + if (ret) { + goto end; + } + + ret = lttng_metadata_printf(session, + "env {\n" + " domain = \"ust\";\n" + " tracer_name = \"lttng-ust\";\n" + " tracer_major = %u;\n" + " tracer_minor = %u;\n" + " tracer_buffering_scheme = \"%s\";\n" + " tracer_buffering_id = %u;\n" + " architecture_bit_width = %u;\n", + major, + minor, + app ? "pid" : "uid", + app ? (int) app->pid : (int) session->tracing_uid, + session->bits_per_long); + if (ret) { + goto end; + } + + ret = print_metadata_session_information(session); + if (ret) { + goto end; + } + + /* + * If per-application registry, we can output extra information + * about the application. + */ + ret = print_metadata_app_information(session, app); + if (ret) { + goto end; + } + + ret = lttng_metadata_printf(session, + "};\n\n" + ); + if (ret) { + goto end; + } + + ret = lttng_metadata_printf(session, + "clock {\n" + " name = \"%s\";\n", + trace_clock_name() + ); + if (ret) { + goto end; + } + + if (!trace_clock_uuid(clock_uuid_s)) { + ret = lttng_metadata_printf(session, + " uuid = \"%s\";\n", + clock_uuid_s + ); + if (ret) { + goto end; + } + } + + ret = lttng_metadata_printf(session, + " description = \"%s\";\n" + " freq = %" PRIu64 "; /* Frequency, in Hz */\n" + " /* clock value offset from Epoch is: offset * (1/freq) */\n" + " offset = %" PRId64 ";\n" + "};\n\n", + trace_clock_description(), + trace_clock_freq(), + measure_clock_offset() + ); + if (ret) { + goto end; + } + + ret = lttng_metadata_printf(session, + "typealias integer {\n" + " size = 27; align = 1; signed = false;\n" + " map = clock.%s.value;\n" + "} := uint27_clock_monotonic_t;\n" + "\n" + "typealias integer {\n" + " size = 32; align = %u; signed = false;\n" + " map = clock.%s.value;\n" + "} := uint32_clock_monotonic_t;\n" + "\n" + "typealias integer {\n" + " size = 64; align = %u; signed = false;\n" + " map = clock.%s.value;\n" + "} := uint64_clock_monotonic_t;\n\n", + trace_clock_name(), + session->uint32_t_alignment, + trace_clock_name(), + session->uint64_t_alignment, + trace_clock_name() + ); + if (ret) { + goto end; + } + + ret = _lttng_stream_packet_context_declare(session); + if (ret) { + goto end; + } + + ret = _lttng_event_header_declare(session); + if (ret) { + goto end; + } + +end: + return ret; +} diff --git a/src/bin/lttng-sessiond/ust-registry.c b/src/bin/lttng-sessiond/ust-registry.c deleted file mode 100644 index 93131abaa..000000000 --- a/src/bin/lttng-sessiond/ust-registry.c +++ /dev/null @@ -1,1056 +0,0 @@ -/* - * Copyright (C) 2013 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include - -#include -#include -#include - -#include "ust-registry.h" -#include "ust-app.h" -#include "ust-field-utils.h" -#include "utils.h" -#include "lttng-sessiond.h" -#include "notification-thread-commands.h" - - -/* - * Hash table match function for event in the registry. - */ -static int ht_match_event(struct cds_lfht_node *node, const void *_key) -{ - const struct ust_registry_event *key; - struct ust_registry_event *event; - int i; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - event = caa_container_of(node, struct ust_registry_event, node.node); - LTTNG_ASSERT(event); - key = _key; - - /* It has to be a perfect match. First, compare the event names. */ - if (strncmp(event->name, key->name, sizeof(event->name))) { - goto no_match; - } - - /* Compare log levels. */ - if (event->loglevel_value != key->loglevel_value) { - goto no_match; - } - - /* Compare the number of fields. */ - if (event->nr_fields != key->nr_fields) { - goto no_match; - } - - /* Compare each field individually. */ - for (i = 0; i < event->nr_fields; i++) { - if (!match_lttng_ust_ctl_field(&event->fields[i], &key->fields[i])) { - goto no_match; - } - } - - /* Compare model URI. */ - if (event->model_emf_uri != NULL && key->model_emf_uri == NULL) { - goto no_match; - } else if(event->model_emf_uri == NULL && key->model_emf_uri != NULL) { - goto no_match; - } else if (event->model_emf_uri != NULL && key->model_emf_uri != NULL) { - if (strcmp(event->model_emf_uri, key->model_emf_uri)) { - goto no_match; - } - } - - /* Match */ - return 1; - -no_match: - return 0; -} - -static unsigned long ht_hash_event(const void *_key, unsigned long seed) -{ - uint64_t hashed_key; - const struct ust_registry_event *key = _key; - - LTTNG_ASSERT(key); - - hashed_key = (uint64_t) hash_key_str(key->name, seed); - - return hash_key_u64(&hashed_key, seed); -} - -static int compare_enums(const struct ust_registry_enum *reg_enum_a, - const struct ust_registry_enum *reg_enum_b) -{ - int ret = 0; - size_t i; - - LTTNG_ASSERT(strcmp(reg_enum_a->name, reg_enum_b->name) == 0); - if (reg_enum_a->nr_entries != reg_enum_b->nr_entries) { - ret = -1; - goto end; - } - for (i = 0; i < reg_enum_a->nr_entries; i++) { - const struct lttng_ust_ctl_enum_entry *entries_a, *entries_b; - - entries_a = ®_enum_a->entries[i]; - entries_b = ®_enum_b->entries[i]; - if (entries_a->start.value != entries_b->start.value) { - ret = -1; - goto end; - } - if (entries_a->end.value != entries_b->end.value) { - ret = -1; - goto end; - } - if (entries_a->start.signedness != entries_b->start.signedness) { - ret = -1; - goto end; - } - if (entries_a->end.signedness != entries_b->end.signedness) { - ret = -1; - goto end; - } - - if (strcmp(entries_a->string, entries_b->string)) { - ret = -1; - goto end; - } - } -end: - return ret; -} - -/* - * Hash table match function for enumerations in the session. Match is - * performed on enumeration name, and confirmed by comparing the enum - * entries. - */ -static int ht_match_enum(struct cds_lfht_node *node, const void *_key) -{ - struct ust_registry_enum *_enum; - const struct ust_registry_enum *key; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - _enum = caa_container_of(node, struct ust_registry_enum, - node.node); - LTTNG_ASSERT(_enum); - key = _key; - - if (strncmp(_enum->name, key->name, LTTNG_UST_ABI_SYM_NAME_LEN)) { - goto no_match; - } - if (compare_enums(_enum, key)) { - goto no_match; - } - - /* Match. */ - return 1; - -no_match: - return 0; -} - -/* - * Hash table match function for enumerations in the session. Match is - * performed by enumeration ID. - */ -static int ht_match_enum_id(struct cds_lfht_node *node, const void *_key) -{ - struct ust_registry_enum *_enum; - const struct ust_registry_enum *key = _key; - - LTTNG_ASSERT(node); - LTTNG_ASSERT(_key); - - _enum = caa_container_of(node, struct ust_registry_enum, node.node); - LTTNG_ASSERT(_enum); - - if (_enum->id != key->id) { - goto no_match; - } - - /* Match. */ - return 1; - -no_match: - return 0; -} - -/* - * Hash table hash function for enumerations in the session. The - * enumeration name is used for hashing. - */ -static unsigned long ht_hash_enum(void *_key, unsigned long seed) -{ - struct ust_registry_enum *key = _key; - - LTTNG_ASSERT(key); - return hash_key_str(key->name, seed); -} - -/* - * Return negative value on error, 0 if OK. - * - * TODO: we could add stricter verification of more types to catch - * errors in liblttng-ust implementation earlier than consumption by the - * trace reader. - */ -static -int validate_event_field(struct lttng_ust_ctl_field *field, - const char *event_name, - struct ust_app *app) -{ - int ret = 0; - - switch(field->type.atype) { - case lttng_ust_ctl_atype_integer: - case lttng_ust_ctl_atype_enum: - case lttng_ust_ctl_atype_array: - case lttng_ust_ctl_atype_sequence: - case lttng_ust_ctl_atype_string: - case lttng_ust_ctl_atype_variant: - case lttng_ust_ctl_atype_array_nestable: - case lttng_ust_ctl_atype_sequence_nestable: - case lttng_ust_ctl_atype_enum_nestable: - case lttng_ust_ctl_atype_variant_nestable: - break; - case lttng_ust_ctl_atype_struct: - if (field->type.u.legacy._struct.nr_fields != 0) { - WARN("Unsupported non-empty struct field."); - ret = -EINVAL; - goto end; - } - break; - case lttng_ust_ctl_atype_struct_nestable: - if (field->type.u.struct_nestable.nr_fields != 0) { - WARN("Unsupported non-empty struct field."); - ret = -EINVAL; - goto end; - } - break; - - case lttng_ust_ctl_atype_float: - switch (field->type.u._float.mant_dig) { - case 0: - WARN("UST application '%s' (pid: %d) has unknown float mantissa '%u' " - "in field '%s', rejecting event '%s'", - app->name, app->pid, - field->type.u._float.mant_dig, - field->name, - event_name); - ret = -EINVAL; - goto end; - default: - break; - } - break; - - default: - ret = -ENOENT; - goto end; - } -end: - return ret; -} - -static -int validate_event_fields(size_t nr_fields, struct lttng_ust_ctl_field *fields, - const char *event_name, struct ust_app *app) -{ - unsigned int i; - - for (i = 0; i < nr_fields; i++) { - if (validate_event_field(&fields[i], event_name, app) < 0) - return -EINVAL; - } - return 0; -} - -/* - * Allocate event and initialize it. This does NOT set a valid event id from a - * registry. - */ -static struct ust_registry_event *alloc_event(int session_objd, - int channel_objd, char *name, char *sig, size_t nr_fields, - struct lttng_ust_ctl_field *fields, int loglevel_value, - char *model_emf_uri, struct ust_app *app) -{ - struct ust_registry_event *event = NULL; - - /* - * Ensure that the field content is valid. - */ - if (validate_event_fields(nr_fields, fields, name, app) < 0) { - return NULL; - } - - event = zmalloc(sizeof(*event)); - if (!event) { - PERROR("zmalloc ust registry event"); - goto error; - } - - event->session_objd = session_objd; - event->channel_objd = channel_objd; - /* Allocated by ustctl. */ - event->signature = sig; - event->nr_fields = nr_fields; - event->fields = fields; - event->loglevel_value = loglevel_value; - event->model_emf_uri = model_emf_uri; - if (name) { - /* Copy event name and force NULL byte. */ - strncpy(event->name, name, sizeof(event->name)); - event->name[sizeof(event->name) - 1] = '\0'; - } - cds_lfht_node_init(&event->node.node); - -error: - return event; -} - -/* - * Free event data structure. This does NOT delete it from any hash table. It's - * safe to pass a NULL pointer. This shoudl be called inside a call RCU if the - * event is previously deleted from a rcu hash table. - */ -static void destroy_event(struct ust_registry_event *event) -{ - if (!event) { - return; - } - - free(event->fields); - free(event->model_emf_uri); - free(event->signature); - free(event); -} - -/* - * Destroy event function call of the call RCU. - */ -static void destroy_event_rcu(struct rcu_head *head) -{ - struct lttng_ht_node_u64 *node = - caa_container_of(head, struct lttng_ht_node_u64, head); - struct ust_registry_event *event = - caa_container_of(node, struct ust_registry_event, node); - - destroy_event(event); -} - -/* - * Find an event using the name and signature in the given registry. RCU read - * side lock MUST be acquired before calling this function and as long as the - * event reference is kept by the caller. - * - * On success, the event pointer is returned else NULL. - */ -struct ust_registry_event *ust_registry_find_event( - struct ust_registry_channel *chan, char *name, char *sig) -{ - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - struct ust_registry_event *event = NULL; - struct ust_registry_event key; - - LTTNG_ASSERT(chan); - LTTNG_ASSERT(name); - LTTNG_ASSERT(sig); - - /* Setup key for the match function. */ - strncpy(key.name, name, sizeof(key.name)); - key.name[sizeof(key.name) - 1] = '\0'; - key.signature = sig; - - cds_lfht_lookup(chan->ht->ht, chan->ht->hash_fct(&key, lttng_ht_seed), - chan->ht->match_fct, &key, &iter.iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (!node) { - goto end; - } - event = caa_container_of(node, struct ust_registry_event, node); - -end: - return event; -} - -/* - * Create a ust_registry_event from the given parameters and add it to the - * registry hash table. If event_id is valid, it is set with the newly created - * event id. - * - * On success, return 0 else a negative value. The created event MUST be unique - * so on duplicate entry -EINVAL is returned. On error, event_id is untouched. - * - * Should be called with session registry mutex held. - */ -int ust_registry_create_event(struct ust_registry_session *session, - uint64_t chan_key, int session_objd, int channel_objd, char *name, - char *sig, size_t nr_fields, struct lttng_ust_ctl_field *fields, - int loglevel_value, char *model_emf_uri, int buffer_type, - uint32_t *event_id_p, struct ust_app *app) -{ - int ret; - uint32_t event_id; - struct cds_lfht_node *nptr; - struct ust_registry_event *event = NULL; - struct ust_registry_channel *chan; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(name); - LTTNG_ASSERT(sig); - LTTNG_ASSERT(event_id_p); - - rcu_read_lock(); - - /* - * This should not happen but since it comes from the UST tracer, an - * external party, don't assert and simply validate values. - */ - if (session_objd < 0 || channel_objd < 0) { - ret = -EINVAL; - goto error_free; - } - - chan = ust_registry_channel_find(session, chan_key); - if (!chan) { - ret = -EINVAL; - goto error_free; - } - - /* Check if we've reached the maximum possible id. */ - if (ust_registry_is_max_id(chan->used_event_id)) { - ret = -ENOENT; - goto error_free; - } - - event = alloc_event(session_objd, channel_objd, name, sig, nr_fields, - fields, loglevel_value, model_emf_uri, app); - if (!event) { - ret = -ENOMEM; - goto error_free; - } - - DBG3("UST registry creating event with event: %s, sig: %s, id: %u, " - "chan_objd: %u, sess_objd: %u, chan_id: %u", event->name, - event->signature, event->id, event->channel_objd, - event->session_objd, chan->chan_id); - - /* - * This is an add unique with a custom match function for event. The node - * are matched using the event name and signature. - */ - nptr = cds_lfht_add_unique(chan->ht->ht, chan->ht->hash_fct(event, - lttng_ht_seed), chan->ht->match_fct, event, &event->node.node); - if (nptr != &event->node.node) { - if (buffer_type == LTTNG_BUFFER_PER_UID) { - /* - * This is normal, we just have to send the event id of the - * returned node and make sure we destroy the previously allocated - * event object. - */ - destroy_event(event); - event = caa_container_of(nptr, struct ust_registry_event, - node.node); - LTTNG_ASSERT(event); - event_id = event->id; - } else { - ERR("UST registry create event add unique failed for event: %s, " - "sig: %s, id: %u, chan_objd: %u, sess_objd: %u", - event->name, event->signature, event->id, - event->channel_objd, event->session_objd); - ret = -EINVAL; - goto error_unlock; - } - } else { - /* Request next event id if the node was successfully added. */ - event_id = event->id = ust_registry_get_next_event_id(chan); - } - - *event_id_p = event_id; - - if (!event->metadata_dumped) { - /* Append to metadata */ - ret = ust_metadata_event_statedump(session, chan, event); - if (ret) { - ERR("Error appending event metadata (errno = %d)", ret); - rcu_read_unlock(); - return ret; - } - } - - rcu_read_unlock(); - return 0; - -error_free: - free(sig); - free(fields); - free(model_emf_uri); -error_unlock: - rcu_read_unlock(); - destroy_event(event); - return ret; -} - -/* - * For a given event in a registry, delete the entry and destroy the event. - * This MUST be called within a RCU read side lock section. - */ -void ust_registry_destroy_event(struct ust_registry_channel *chan, - struct ust_registry_event *event) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(chan); - LTTNG_ASSERT(event); - - /* Delete the node first. */ - iter.iter.node = &event->node.node; - ret = lttng_ht_del(chan->ht, &iter); - LTTNG_ASSERT(!ret); - - call_rcu(&event->node.head, destroy_event_rcu); - - return; -} - -static void destroy_enum(struct ust_registry_enum *reg_enum) -{ - if (!reg_enum) { - return; - } - free(reg_enum->entries); - free(reg_enum); -} - -static void destroy_enum_rcu(struct rcu_head *head) -{ - struct ust_registry_enum *reg_enum = - caa_container_of(head, struct ust_registry_enum, rcu_head); - - destroy_enum(reg_enum); -} - -/* - * Lookup enumeration by name and comparing enumeration entries. - * Needs to be called from RCU read-side critical section. - */ -static struct ust_registry_enum *ust_registry_lookup_enum( - struct ust_registry_session *session, - const struct ust_registry_enum *reg_enum_lookup) -{ - struct ust_registry_enum *reg_enum = NULL; - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - - cds_lfht_lookup(session->enums->ht, - ht_hash_enum((void *) reg_enum_lookup, lttng_ht_seed), - ht_match_enum, reg_enum_lookup, &iter.iter); - node = lttng_ht_iter_get_node_str(&iter); - if (!node) { - goto end; - } - reg_enum = caa_container_of(node, struct ust_registry_enum, node); -end: - return reg_enum; -} - -/* - * Lookup enumeration by enum ID. - * Needs to be called from RCU read-side critical section. - */ -struct ust_registry_enum * - ust_registry_lookup_enum_by_id(struct ust_registry_session *session, - const char *enum_name, uint64_t enum_id) -{ - struct ust_registry_enum *reg_enum = NULL; - struct lttng_ht_node_str *node; - struct lttng_ht_iter iter; - struct ust_registry_enum reg_enum_lookup; - - memset(®_enum_lookup, 0, sizeof(reg_enum_lookup)); - strncpy(reg_enum_lookup.name, enum_name, LTTNG_UST_ABI_SYM_NAME_LEN); - reg_enum_lookup.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; - reg_enum_lookup.id = enum_id; - cds_lfht_lookup(session->enums->ht, - ht_hash_enum((void *) ®_enum_lookup, lttng_ht_seed), - ht_match_enum_id, ®_enum_lookup, &iter.iter); - node = lttng_ht_iter_get_node_str(&iter); - if (!node) { - goto end; - } - reg_enum = caa_container_of(node, struct ust_registry_enum, node); -end: - return reg_enum; -} - -/* - * Create a ust_registry_enum from the given parameters and add it to the - * registry hash table, or find it if already there. - * - * On success, return 0 else a negative value. - * - * Should be called with session registry mutex held. - * - * We receive ownership of entries. - */ -int ust_registry_create_or_find_enum(struct ust_registry_session *session, - int session_objd, char *enum_name, - struct lttng_ust_ctl_enum_entry *entries, size_t nr_entries, - uint64_t *enum_id) -{ - int ret = 0; - struct cds_lfht_node *nodep; - struct ust_registry_enum *reg_enum = NULL, *old_reg_enum; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(enum_name); - - rcu_read_lock(); - - /* - * This should not happen but since it comes from the UST tracer, an - * external party, don't assert and simply validate values. - */ - if (session_objd < 0) { - ret = -EINVAL; - goto end; - } - - /* Check if the enumeration was already dumped */ - reg_enum = zmalloc(sizeof(*reg_enum)); - if (!reg_enum) { - PERROR("zmalloc ust registry enumeration"); - ret = -ENOMEM; - goto end; - } - strncpy(reg_enum->name, enum_name, LTTNG_UST_ABI_SYM_NAME_LEN); - reg_enum->name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; - /* entries will be owned by reg_enum. */ - reg_enum->entries = entries; - reg_enum->nr_entries = nr_entries; - entries = NULL; - - old_reg_enum = ust_registry_lookup_enum(session, reg_enum); - if (old_reg_enum) { - DBG("enum %s already in sess_objd: %u", enum_name, session_objd); - /* Fall through. Use prior enum. */ - destroy_enum(reg_enum); - reg_enum = old_reg_enum; - } else { - DBG("UST registry creating enum: %s, sess_objd: %u", - enum_name, session_objd); - if (session->next_enum_id == -1ULL) { - ret = -EOVERFLOW; - destroy_enum(reg_enum); - goto end; - } - reg_enum->id = session->next_enum_id++; - cds_lfht_node_init(®_enum->node.node); - nodep = cds_lfht_add_unique(session->enums->ht, - ht_hash_enum(reg_enum, lttng_ht_seed), - ht_match_enum_id, reg_enum, - ®_enum->node.node); - LTTNG_ASSERT(nodep == ®_enum->node.node); - } - DBG("UST registry reply with enum %s with id %" PRIu64 " in sess_objd: %u", - enum_name, reg_enum->id, session_objd); - *enum_id = reg_enum->id; -end: - free(entries); - rcu_read_unlock(); - return ret; -} - -/* - * For a given enumeration in a registry, delete the entry and destroy - * the enumeration. - * This MUST be called within a RCU read side lock section. - */ -static void ust_registry_destroy_enum(struct ust_registry_session *reg_session, - struct ust_registry_enum *reg_enum) -{ - int ret; - struct lttng_ht_iter iter; - - LTTNG_ASSERT(reg_session); - LTTNG_ASSERT(reg_enum); - - /* Delete the node first. */ - iter.iter.node = ®_enum->node.node; - ret = lttng_ht_del(reg_session->enums, &iter); - LTTNG_ASSERT(!ret); - call_rcu(®_enum->rcu_head, destroy_enum_rcu); -} - -/* - * We need to execute ht_destroy outside of RCU read-side critical - * section and outside of call_rcu thread, so we postpone its execution - * using ht_cleanup_push. It is simpler than to change the semantic of - * the many callers of delete_ust_app_session(). - */ -static -void destroy_channel_rcu(struct rcu_head *head) -{ - struct ust_registry_channel *chan = - caa_container_of(head, struct ust_registry_channel, rcu_head); - - if (chan->ht) { - ht_cleanup_push(chan->ht); - } - free(chan->ctx_fields); - free(chan); -} - -/* - * Destroy every element of the registry and free the memory. This does NOT - * free the registry pointer since it might not have been allocated before so - * it's the caller responsability. - */ -static void destroy_channel(struct ust_registry_channel *chan, bool notif) -{ - struct lttng_ht_iter iter; - struct ust_registry_event *event; - enum lttng_error_code cmd_ret; - - LTTNG_ASSERT(chan); - - if (notif) { - cmd_ret = notification_thread_command_remove_channel( - the_notification_thread_handle, - chan->consumer_key, LTTNG_DOMAIN_UST); - if (cmd_ret != LTTNG_OK) { - ERR("Failed to remove channel from notification thread"); - } - } - - if (chan->ht) { - rcu_read_lock(); - /* Destroy all event associated with this registry. */ - cds_lfht_for_each_entry( - chan->ht->ht, &iter.iter, event, node.node) { - /* Delete the node from the ht and free it. */ - ust_registry_destroy_event(chan, event); - } - rcu_read_unlock(); - } - call_rcu(&chan->rcu_head, destroy_channel_rcu); -} - -/* - * Initialize registry with default values. - */ -int ust_registry_channel_add(struct ust_registry_session *session, - uint64_t key) -{ - int ret = 0; - struct ust_registry_channel *chan; - - LTTNG_ASSERT(session); - - chan = zmalloc(sizeof(*chan)); - if (!chan) { - PERROR("zmalloc ust registry channel"); - ret = -ENOMEM; - goto error_alloc; - } - - chan->ht = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); - if (!chan->ht) { - ret = -ENOMEM; - goto error; - } - - /* Set custom match function. */ - chan->ht->match_fct = ht_match_event; - chan->ht->hash_fct = ht_hash_event; - - /* - * Assign a channel ID right now since the event notification comes - * *before* the channel notify so the ID needs to be set at this point so - * the metadata can be dumped for that event. - */ - if (ust_registry_is_max_id(session->used_channel_id)) { - ret = -1; - goto error; - } - chan->chan_id = ust_registry_get_next_chan_id(session); - - rcu_read_lock(); - lttng_ht_node_init_u64(&chan->node, key); - lttng_ht_add_unique_u64(session->channels, &chan->node); - rcu_read_unlock(); - - return 0; - -error: - destroy_channel(chan, false); -error_alloc: - return ret; -} - -/* - * Find a channel in the given registry. RCU read side lock MUST be acquired - * before calling this function and as long as the event reference is kept by - * the caller. - * - * On success, the pointer is returned else NULL. - */ -struct ust_registry_channel *ust_registry_channel_find( - struct ust_registry_session *session, uint64_t key) -{ - struct lttng_ht_node_u64 *node; - struct lttng_ht_iter iter; - struct ust_registry_channel *chan = NULL; - - LTTNG_ASSERT(session); - LTTNG_ASSERT(session->channels); - - DBG3("UST registry channel finding key %" PRIu64, key); - - lttng_ht_lookup(session->channels, &key, &iter); - node = lttng_ht_iter_get_node_u64(&iter); - if (!node) { - goto end; - } - chan = caa_container_of(node, struct ust_registry_channel, node); - -end: - return chan; -} - -/* - * Remove channel using key from registry and free memory. - */ -void ust_registry_channel_del_free(struct ust_registry_session *session, - uint64_t key, bool notif) -{ - struct lttng_ht_iter iter; - struct ust_registry_channel *chan; - int ret; - - LTTNG_ASSERT(session); - - rcu_read_lock(); - chan = ust_registry_channel_find(session, key); - if (!chan) { - rcu_read_unlock(); - goto end; - } - - iter.iter.node = &chan->node.node; - ret = lttng_ht_del(session->channels, &iter); - LTTNG_ASSERT(!ret); - rcu_read_unlock(); - destroy_channel(chan, notif); - -end: - return; -} - -/* - * Initialize registry with default values and set the newly allocated session - * pointer to sessionp. - * - * Return 0 on success and sessionp is set or else return -1 and sessionp is - * kept untouched. - */ -int ust_registry_session_init(struct ust_registry_session **sessionp, - struct ust_app *app, - uint32_t bits_per_long, - uint32_t uint8_t_alignment, - uint32_t uint16_t_alignment, - uint32_t uint32_t_alignment, - uint32_t uint64_t_alignment, - uint32_t long_alignment, - int byte_order, - uint32_t major, - uint32_t minor, - const char *root_shm_path, - const char *shm_path, - uid_t euid, - gid_t egid, - uint64_t tracing_id, - uid_t tracing_uid) -{ - int ret; - struct ust_registry_session *session; - - LTTNG_ASSERT(sessionp); - - session = zmalloc(sizeof(*session)); - if (!session) { - PERROR("zmalloc ust registry session"); - goto error_alloc; - } - - pthread_mutex_init(&session->lock, NULL); - session->bits_per_long = bits_per_long; - session->uint8_t_alignment = uint8_t_alignment; - session->uint16_t_alignment = uint16_t_alignment; - session->uint32_t_alignment = uint32_t_alignment; - session->uint64_t_alignment = uint64_t_alignment; - session->long_alignment = long_alignment; - session->byte_order = byte_order; - session->metadata_fd = -1; - session->uid = euid; - session->gid = egid; - session->next_enum_id = 0; - session->major = major; - session->minor = minor; - strncpy(session->root_shm_path, root_shm_path, - sizeof(session->root_shm_path)); - session->root_shm_path[sizeof(session->root_shm_path) - 1] = '\0'; - if (shm_path[0]) { - strncpy(session->shm_path, shm_path, - sizeof(session->shm_path)); - session->shm_path[sizeof(session->shm_path) - 1] = '\0'; - strncpy(session->metadata_path, shm_path, - sizeof(session->metadata_path)); - session->metadata_path[sizeof(session->metadata_path) - 1] = '\0'; - strncat(session->metadata_path, "/metadata", - sizeof(session->metadata_path) - - strlen(session->metadata_path) - 1); - } - if (session->shm_path[0]) { - ret = run_as_mkdir_recursive(session->shm_path, - S_IRWXU | S_IRWXG, - euid, egid); - if (ret) { - PERROR("run_as_mkdir_recursive"); - goto error; - } - } - if (session->metadata_path[0]) { - /* Create metadata file */ - ret = run_as_open(session->metadata_path, - O_WRONLY | O_CREAT | O_EXCL, - S_IRUSR | S_IWUSR, euid, egid); - if (ret < 0) { - PERROR("Opening metadata file"); - goto error; - } - session->metadata_fd = ret; - } - - session->enums = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); - if (!session->enums) { - ERR("Failed to create enums hash table"); - goto error; - } - /* hash/match functions are specified at call site. */ - session->enums->match_fct = NULL; - session->enums->hash_fct = NULL; - - session->channels = lttng_ht_new(0, LTTNG_HT_TYPE_U64); - if (!session->channels) { - goto error; - } - - ret = lttng_uuid_generate(session->uuid); - if (ret) { - ERR("Failed to generate UST uuid (errno = %d)", ret); - goto error; - } - - session->tracing_id = tracing_id; - session->tracing_uid = tracing_uid; - - pthread_mutex_lock(&session->lock); - ret = ust_metadata_session_statedump(session, app, major, minor); - pthread_mutex_unlock(&session->lock); - if (ret) { - ERR("Failed to generate session metadata (errno = %d)", ret); - goto error; - } - - *sessionp = session; - - return 0; - -error: - ust_registry_session_destroy(session); - free(session); -error_alloc: - return -1; -} - -/* - * Destroy session registry. This does NOT free the given pointer since it - * might get passed as a reference. The registry lock should NOT be acquired. - */ -void ust_registry_session_destroy(struct ust_registry_session *reg) -{ - int ret; - struct lttng_ht_iter iter; - struct ust_registry_channel *chan; - struct ust_registry_enum *reg_enum; - - if (!reg) { - return; - } - - /* On error, EBUSY can be returned if lock. Code flow error. */ - ret = pthread_mutex_destroy(®->lock); - LTTNG_ASSERT(!ret); - - if (reg->channels) { - rcu_read_lock(); - /* Destroy all event associated with this registry. */ - cds_lfht_for_each_entry(reg->channels->ht, &iter.iter, chan, - node.node) { - /* Delete the node from the ht and free it. */ - ret = lttng_ht_del(reg->channels, &iter); - LTTNG_ASSERT(!ret); - destroy_channel(chan, true); - } - rcu_read_unlock(); - ht_cleanup_push(reg->channels); - } - - free(reg->metadata); - if (reg->metadata_fd >= 0) { - ret = close(reg->metadata_fd); - if (ret) { - PERROR("close"); - } - ret = run_as_unlink(reg->metadata_path, - reg->uid, reg->gid); - if (ret) { - PERROR("unlink"); - } - } - if (reg->root_shm_path[0]) { - /* - * Try deleting the directory hierarchy. - */ - (void) run_as_rmdir_recursive(reg->root_shm_path, - reg->uid, reg->gid, - LTTNG_DIRECTORY_HANDLE_SKIP_NON_EMPTY_FLAG); - } - /* Destroy the enum hash table */ - if (reg->enums) { - rcu_read_lock(); - /* Destroy all enum entries associated with this registry. */ - cds_lfht_for_each_entry(reg->enums->ht, &iter.iter, reg_enum, - node.node) { - ust_registry_destroy_enum(reg, reg_enum); - } - rcu_read_unlock(); - ht_cleanup_push(reg->enums); - } -} diff --git a/src/bin/lttng-sessiond/ust-registry.cpp b/src/bin/lttng-sessiond/ust-registry.cpp new file mode 100644 index 000000000..173465203 --- /dev/null +++ b/src/bin/lttng-sessiond/ust-registry.cpp @@ -0,0 +1,1056 @@ +/* + * Copyright (C) 2013 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include + +#include +#include +#include + +#include "ust-registry.h" +#include "ust-app.h" +#include "ust-field-utils.h" +#include "utils.h" +#include "lttng-sessiond.h" +#include "notification-thread-commands.h" + + +/* + * Hash table match function for event in the registry. + */ +static int ht_match_event(struct cds_lfht_node *node, const void *_key) +{ + const struct ust_registry_event *key; + struct ust_registry_event *event; + int i; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + event = caa_container_of(node, struct ust_registry_event, node.node); + LTTNG_ASSERT(event); + key = (ust_registry_event *) _key; + + /* It has to be a perfect match. First, compare the event names. */ + if (strncmp(event->name, key->name, sizeof(event->name))) { + goto no_match; + } + + /* Compare log levels. */ + if (event->loglevel_value != key->loglevel_value) { + goto no_match; + } + + /* Compare the number of fields. */ + if (event->nr_fields != key->nr_fields) { + goto no_match; + } + + /* Compare each field individually. */ + for (i = 0; i < event->nr_fields; i++) { + if (!match_lttng_ust_ctl_field(&event->fields[i], &key->fields[i])) { + goto no_match; + } + } + + /* Compare model URI. */ + if (event->model_emf_uri != NULL && key->model_emf_uri == NULL) { + goto no_match; + } else if(event->model_emf_uri == NULL && key->model_emf_uri != NULL) { + goto no_match; + } else if (event->model_emf_uri != NULL && key->model_emf_uri != NULL) { + if (strcmp(event->model_emf_uri, key->model_emf_uri)) { + goto no_match; + } + } + + /* Match */ + return 1; + +no_match: + return 0; +} + +static unsigned long ht_hash_event(const void *_key, unsigned long seed) +{ + uint64_t hashed_key; + const struct ust_registry_event *key = (ust_registry_event *) _key; + + LTTNG_ASSERT(key); + + hashed_key = (uint64_t) hash_key_str(key->name, seed); + + return hash_key_u64(&hashed_key, seed); +} + +static int compare_enums(const struct ust_registry_enum *reg_enum_a, + const struct ust_registry_enum *reg_enum_b) +{ + int ret = 0; + size_t i; + + LTTNG_ASSERT(strcmp(reg_enum_a->name, reg_enum_b->name) == 0); + if (reg_enum_a->nr_entries != reg_enum_b->nr_entries) { + ret = -1; + goto end; + } + for (i = 0; i < reg_enum_a->nr_entries; i++) { + const struct lttng_ust_ctl_enum_entry *entries_a, *entries_b; + + entries_a = ®_enum_a->entries[i]; + entries_b = ®_enum_b->entries[i]; + if (entries_a->start.value != entries_b->start.value) { + ret = -1; + goto end; + } + if (entries_a->end.value != entries_b->end.value) { + ret = -1; + goto end; + } + if (entries_a->start.signedness != entries_b->start.signedness) { + ret = -1; + goto end; + } + if (entries_a->end.signedness != entries_b->end.signedness) { + ret = -1; + goto end; + } + + if (strcmp(entries_a->string, entries_b->string)) { + ret = -1; + goto end; + } + } +end: + return ret; +} + +/* + * Hash table match function for enumerations in the session. Match is + * performed on enumeration name, and confirmed by comparing the enum + * entries. + */ +static int ht_match_enum(struct cds_lfht_node *node, const void *_key) +{ + struct ust_registry_enum *_enum; + const struct ust_registry_enum *key; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + _enum = caa_container_of(node, struct ust_registry_enum, + node.node); + LTTNG_ASSERT(_enum); + key = (ust_registry_enum *) _key; + + if (strncmp(_enum->name, key->name, LTTNG_UST_ABI_SYM_NAME_LEN)) { + goto no_match; + } + if (compare_enums(_enum, key)) { + goto no_match; + } + + /* Match. */ + return 1; + +no_match: + return 0; +} + +/* + * Hash table match function for enumerations in the session. Match is + * performed by enumeration ID. + */ +static int ht_match_enum_id(struct cds_lfht_node *node, const void *_key) +{ + struct ust_registry_enum *_enum; + const struct ust_registry_enum *key = (ust_registry_enum *) _key; + + LTTNG_ASSERT(node); + LTTNG_ASSERT(_key); + + _enum = caa_container_of(node, struct ust_registry_enum, node.node); + LTTNG_ASSERT(_enum); + + if (_enum->id != key->id) { + goto no_match; + } + + /* Match. */ + return 1; + +no_match: + return 0; +} + +/* + * Hash table hash function for enumerations in the session. The + * enumeration name is used for hashing. + */ +static unsigned long ht_hash_enum(void *_key, unsigned long seed) +{ + struct ust_registry_enum *key = (ust_registry_enum *) _key; + + LTTNG_ASSERT(key); + return hash_key_str(key->name, seed); +} + +/* + * Return negative value on error, 0 if OK. + * + * TODO: we could add stricter verification of more types to catch + * errors in liblttng-ust implementation earlier than consumption by the + * trace reader. + */ +static +int validate_event_field(struct lttng_ust_ctl_field *field, + const char *event_name, + struct ust_app *app) +{ + int ret = 0; + + switch(field->type.atype) { + case lttng_ust_ctl_atype_integer: + case lttng_ust_ctl_atype_enum: + case lttng_ust_ctl_atype_array: + case lttng_ust_ctl_atype_sequence: + case lttng_ust_ctl_atype_string: + case lttng_ust_ctl_atype_variant: + case lttng_ust_ctl_atype_array_nestable: + case lttng_ust_ctl_atype_sequence_nestable: + case lttng_ust_ctl_atype_enum_nestable: + case lttng_ust_ctl_atype_variant_nestable: + break; + case lttng_ust_ctl_atype_struct: + if (field->type.u.legacy._struct.nr_fields != 0) { + WARN("Unsupported non-empty struct field."); + ret = -EINVAL; + goto end; + } + break; + case lttng_ust_ctl_atype_struct_nestable: + if (field->type.u.struct_nestable.nr_fields != 0) { + WARN("Unsupported non-empty struct field."); + ret = -EINVAL; + goto end; + } + break; + + case lttng_ust_ctl_atype_float: + switch (field->type.u._float.mant_dig) { + case 0: + WARN("UST application '%s' (pid: %d) has unknown float mantissa '%u' " + "in field '%s', rejecting event '%s'", + app->name, app->pid, + field->type.u._float.mant_dig, + field->name, + event_name); + ret = -EINVAL; + goto end; + default: + break; + } + break; + + default: + ret = -ENOENT; + goto end; + } +end: + return ret; +} + +static +int validate_event_fields(size_t nr_fields, struct lttng_ust_ctl_field *fields, + const char *event_name, struct ust_app *app) +{ + unsigned int i; + + for (i = 0; i < nr_fields; i++) { + if (validate_event_field(&fields[i], event_name, app) < 0) + return -EINVAL; + } + return 0; +} + +/* + * Allocate event and initialize it. This does NOT set a valid event id from a + * registry. + */ +static struct ust_registry_event *alloc_event(int session_objd, + int channel_objd, char *name, char *sig, size_t nr_fields, + struct lttng_ust_ctl_field *fields, int loglevel_value, + char *model_emf_uri, struct ust_app *app) +{ + struct ust_registry_event *event = NULL; + + /* + * Ensure that the field content is valid. + */ + if (validate_event_fields(nr_fields, fields, name, app) < 0) { + return NULL; + } + + event = (ust_registry_event *) zmalloc(sizeof(*event)); + if (!event) { + PERROR("zmalloc ust registry event"); + goto error; + } + + event->session_objd = session_objd; + event->channel_objd = channel_objd; + /* Allocated by ustctl. */ + event->signature = sig; + event->nr_fields = nr_fields; + event->fields = fields; + event->loglevel_value = loglevel_value; + event->model_emf_uri = model_emf_uri; + if (name) { + /* Copy event name and force NULL byte. */ + strncpy(event->name, name, sizeof(event->name)); + event->name[sizeof(event->name) - 1] = '\0'; + } + cds_lfht_node_init(&event->node.node); + +error: + return event; +} + +/* + * Free event data structure. This does NOT delete it from any hash table. It's + * safe to pass a NULL pointer. This shoudl be called inside a call RCU if the + * event is previously deleted from a rcu hash table. + */ +static void destroy_event(struct ust_registry_event *event) +{ + if (!event) { + return; + } + + free(event->fields); + free(event->model_emf_uri); + free(event->signature); + free(event); +} + +/* + * Destroy event function call of the call RCU. + */ +static void destroy_event_rcu(struct rcu_head *head) +{ + struct lttng_ht_node_u64 *node = + caa_container_of(head, struct lttng_ht_node_u64, head); + struct ust_registry_event *event = + caa_container_of(node, struct ust_registry_event, node); + + destroy_event(event); +} + +/* + * Find an event using the name and signature in the given registry. RCU read + * side lock MUST be acquired before calling this function and as long as the + * event reference is kept by the caller. + * + * On success, the event pointer is returned else NULL. + */ +struct ust_registry_event *ust_registry_find_event( + struct ust_registry_channel *chan, char *name, char *sig) +{ + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + struct ust_registry_event *event = NULL; + struct ust_registry_event key; + + LTTNG_ASSERT(chan); + LTTNG_ASSERT(name); + LTTNG_ASSERT(sig); + + /* Setup key for the match function. */ + strncpy(key.name, name, sizeof(key.name)); + key.name[sizeof(key.name) - 1] = '\0'; + key.signature = sig; + + cds_lfht_lookup(chan->ht->ht, chan->ht->hash_fct(&key, lttng_ht_seed), + chan->ht->match_fct, &key, &iter.iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (!node) { + goto end; + } + event = caa_container_of(node, struct ust_registry_event, node); + +end: + return event; +} + +/* + * Create a ust_registry_event from the given parameters and add it to the + * registry hash table. If event_id is valid, it is set with the newly created + * event id. + * + * On success, return 0 else a negative value. The created event MUST be unique + * so on duplicate entry -EINVAL is returned. On error, event_id is untouched. + * + * Should be called with session registry mutex held. + */ +int ust_registry_create_event(struct ust_registry_session *session, + uint64_t chan_key, int session_objd, int channel_objd, char *name, + char *sig, size_t nr_fields, struct lttng_ust_ctl_field *fields, + int loglevel_value, char *model_emf_uri, int buffer_type, + uint32_t *event_id_p, struct ust_app *app) +{ + int ret; + uint32_t event_id; + struct cds_lfht_node *nptr; + struct ust_registry_event *event = NULL; + struct ust_registry_channel *chan; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(name); + LTTNG_ASSERT(sig); + LTTNG_ASSERT(event_id_p); + + rcu_read_lock(); + + /* + * This should not happen but since it comes from the UST tracer, an + * external party, don't assert and simply validate values. + */ + if (session_objd < 0 || channel_objd < 0) { + ret = -EINVAL; + goto error_free; + } + + chan = ust_registry_channel_find(session, chan_key); + if (!chan) { + ret = -EINVAL; + goto error_free; + } + + /* Check if we've reached the maximum possible id. */ + if (ust_registry_is_max_id(chan->used_event_id)) { + ret = -ENOENT; + goto error_free; + } + + event = alloc_event(session_objd, channel_objd, name, sig, nr_fields, + fields, loglevel_value, model_emf_uri, app); + if (!event) { + ret = -ENOMEM; + goto error_free; + } + + DBG3("UST registry creating event with event: %s, sig: %s, id: %u, " + "chan_objd: %u, sess_objd: %u, chan_id: %u", event->name, + event->signature, event->id, event->channel_objd, + event->session_objd, chan->chan_id); + + /* + * This is an add unique with a custom match function for event. The node + * are matched using the event name and signature. + */ + nptr = cds_lfht_add_unique(chan->ht->ht, chan->ht->hash_fct(event, + lttng_ht_seed), chan->ht->match_fct, event, &event->node.node); + if (nptr != &event->node.node) { + if (buffer_type == LTTNG_BUFFER_PER_UID) { + /* + * This is normal, we just have to send the event id of the + * returned node and make sure we destroy the previously allocated + * event object. + */ + destroy_event(event); + event = caa_container_of(nptr, struct ust_registry_event, + node.node); + LTTNG_ASSERT(event); + event_id = event->id; + } else { + ERR("UST registry create event add unique failed for event: %s, " + "sig: %s, id: %u, chan_objd: %u, sess_objd: %u", + event->name, event->signature, event->id, + event->channel_objd, event->session_objd); + ret = -EINVAL; + goto error_unlock; + } + } else { + /* Request next event id if the node was successfully added. */ + event_id = event->id = ust_registry_get_next_event_id(chan); + } + + *event_id_p = event_id; + + if (!event->metadata_dumped) { + /* Append to metadata */ + ret = ust_metadata_event_statedump(session, chan, event); + if (ret) { + ERR("Error appending event metadata (errno = %d)", ret); + rcu_read_unlock(); + return ret; + } + } + + rcu_read_unlock(); + return 0; + +error_free: + free(sig); + free(fields); + free(model_emf_uri); +error_unlock: + rcu_read_unlock(); + destroy_event(event); + return ret; +} + +/* + * For a given event in a registry, delete the entry and destroy the event. + * This MUST be called within a RCU read side lock section. + */ +void ust_registry_destroy_event(struct ust_registry_channel *chan, + struct ust_registry_event *event) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(chan); + LTTNG_ASSERT(event); + + /* Delete the node first. */ + iter.iter.node = &event->node.node; + ret = lttng_ht_del(chan->ht, &iter); + LTTNG_ASSERT(!ret); + + call_rcu(&event->node.head, destroy_event_rcu); + + return; +} + +static void destroy_enum(struct ust_registry_enum *reg_enum) +{ + if (!reg_enum) { + return; + } + free(reg_enum->entries); + free(reg_enum); +} + +static void destroy_enum_rcu(struct rcu_head *head) +{ + struct ust_registry_enum *reg_enum = + caa_container_of(head, struct ust_registry_enum, rcu_head); + + destroy_enum(reg_enum); +} + +/* + * Lookup enumeration by name and comparing enumeration entries. + * Needs to be called from RCU read-side critical section. + */ +static struct ust_registry_enum *ust_registry_lookup_enum( + struct ust_registry_session *session, + const struct ust_registry_enum *reg_enum_lookup) +{ + struct ust_registry_enum *reg_enum = NULL; + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + + cds_lfht_lookup(session->enums->ht, + ht_hash_enum((void *) reg_enum_lookup, lttng_ht_seed), + ht_match_enum, reg_enum_lookup, &iter.iter); + node = lttng_ht_iter_get_node_str(&iter); + if (!node) { + goto end; + } + reg_enum = caa_container_of(node, struct ust_registry_enum, node); +end: + return reg_enum; +} + +/* + * Lookup enumeration by enum ID. + * Needs to be called from RCU read-side critical section. + */ +struct ust_registry_enum * + ust_registry_lookup_enum_by_id(struct ust_registry_session *session, + const char *enum_name, uint64_t enum_id) +{ + struct ust_registry_enum *reg_enum = NULL; + struct lttng_ht_node_str *node; + struct lttng_ht_iter iter; + struct ust_registry_enum reg_enum_lookup; + + memset(®_enum_lookup, 0, sizeof(reg_enum_lookup)); + strncpy(reg_enum_lookup.name, enum_name, LTTNG_UST_ABI_SYM_NAME_LEN); + reg_enum_lookup.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; + reg_enum_lookup.id = enum_id; + cds_lfht_lookup(session->enums->ht, + ht_hash_enum((void *) ®_enum_lookup, lttng_ht_seed), + ht_match_enum_id, ®_enum_lookup, &iter.iter); + node = lttng_ht_iter_get_node_str(&iter); + if (!node) { + goto end; + } + reg_enum = caa_container_of(node, struct ust_registry_enum, node); +end: + return reg_enum; +} + +/* + * Create a ust_registry_enum from the given parameters and add it to the + * registry hash table, or find it if already there. + * + * On success, return 0 else a negative value. + * + * Should be called with session registry mutex held. + * + * We receive ownership of entries. + */ +int ust_registry_create_or_find_enum(struct ust_registry_session *session, + int session_objd, char *enum_name, + struct lttng_ust_ctl_enum_entry *entries, size_t nr_entries, + uint64_t *enum_id) +{ + int ret = 0; + struct cds_lfht_node *nodep; + struct ust_registry_enum *reg_enum = NULL, *old_reg_enum; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(enum_name); + + rcu_read_lock(); + + /* + * This should not happen but since it comes from the UST tracer, an + * external party, don't assert and simply validate values. + */ + if (session_objd < 0) { + ret = -EINVAL; + goto end; + } + + /* Check if the enumeration was already dumped */ + reg_enum = (ust_registry_enum *) zmalloc(sizeof(*reg_enum)); + if (!reg_enum) { + PERROR("zmalloc ust registry enumeration"); + ret = -ENOMEM; + goto end; + } + strncpy(reg_enum->name, enum_name, LTTNG_UST_ABI_SYM_NAME_LEN); + reg_enum->name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] = '\0'; + /* entries will be owned by reg_enum. */ + reg_enum->entries = entries; + reg_enum->nr_entries = nr_entries; + entries = NULL; + + old_reg_enum = ust_registry_lookup_enum(session, reg_enum); + if (old_reg_enum) { + DBG("enum %s already in sess_objd: %u", enum_name, session_objd); + /* Fall through. Use prior enum. */ + destroy_enum(reg_enum); + reg_enum = old_reg_enum; + } else { + DBG("UST registry creating enum: %s, sess_objd: %u", + enum_name, session_objd); + if (session->next_enum_id == -1ULL) { + ret = -EOVERFLOW; + destroy_enum(reg_enum); + goto end; + } + reg_enum->id = session->next_enum_id++; + cds_lfht_node_init(®_enum->node.node); + nodep = cds_lfht_add_unique(session->enums->ht, + ht_hash_enum(reg_enum, lttng_ht_seed), + ht_match_enum_id, reg_enum, + ®_enum->node.node); + LTTNG_ASSERT(nodep == ®_enum->node.node); + } + DBG("UST registry reply with enum %s with id %" PRIu64 " in sess_objd: %u", + enum_name, reg_enum->id, session_objd); + *enum_id = reg_enum->id; +end: + free(entries); + rcu_read_unlock(); + return ret; +} + +/* + * For a given enumeration in a registry, delete the entry and destroy + * the enumeration. + * This MUST be called within a RCU read side lock section. + */ +static void ust_registry_destroy_enum(struct ust_registry_session *reg_session, + struct ust_registry_enum *reg_enum) +{ + int ret; + struct lttng_ht_iter iter; + + LTTNG_ASSERT(reg_session); + LTTNG_ASSERT(reg_enum); + + /* Delete the node first. */ + iter.iter.node = ®_enum->node.node; + ret = lttng_ht_del(reg_session->enums, &iter); + LTTNG_ASSERT(!ret); + call_rcu(®_enum->rcu_head, destroy_enum_rcu); +} + +/* + * We need to execute ht_destroy outside of RCU read-side critical + * section and outside of call_rcu thread, so we postpone its execution + * using ht_cleanup_push. It is simpler than to change the semantic of + * the many callers of delete_ust_app_session(). + */ +static +void destroy_channel_rcu(struct rcu_head *head) +{ + struct ust_registry_channel *chan = + caa_container_of(head, struct ust_registry_channel, rcu_head); + + if (chan->ht) { + ht_cleanup_push(chan->ht); + } + free(chan->ctx_fields); + free(chan); +} + +/* + * Destroy every element of the registry and free the memory. This does NOT + * free the registry pointer since it might not have been allocated before so + * it's the caller responsability. + */ +static void destroy_channel(struct ust_registry_channel *chan, bool notif) +{ + struct lttng_ht_iter iter; + struct ust_registry_event *event; + enum lttng_error_code cmd_ret; + + LTTNG_ASSERT(chan); + + if (notif) { + cmd_ret = notification_thread_command_remove_channel( + the_notification_thread_handle, + chan->consumer_key, LTTNG_DOMAIN_UST); + if (cmd_ret != LTTNG_OK) { + ERR("Failed to remove channel from notification thread"); + } + } + + if (chan->ht) { + rcu_read_lock(); + /* Destroy all event associated with this registry. */ + cds_lfht_for_each_entry( + chan->ht->ht, &iter.iter, event, node.node) { + /* Delete the node from the ht and free it. */ + ust_registry_destroy_event(chan, event); + } + rcu_read_unlock(); + } + call_rcu(&chan->rcu_head, destroy_channel_rcu); +} + +/* + * Initialize registry with default values. + */ +int ust_registry_channel_add(struct ust_registry_session *session, + uint64_t key) +{ + int ret = 0; + struct ust_registry_channel *chan; + + LTTNG_ASSERT(session); + + chan = (ust_registry_channel *) zmalloc(sizeof(*chan)); + if (!chan) { + PERROR("zmalloc ust registry channel"); + ret = -ENOMEM; + goto error_alloc; + } + + chan->ht = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); + if (!chan->ht) { + ret = -ENOMEM; + goto error; + } + + /* Set custom match function. */ + chan->ht->match_fct = ht_match_event; + chan->ht->hash_fct = ht_hash_event; + + /* + * Assign a channel ID right now since the event notification comes + * *before* the channel notify so the ID needs to be set at this point so + * the metadata can be dumped for that event. + */ + if (ust_registry_is_max_id(session->used_channel_id)) { + ret = -1; + goto error; + } + chan->chan_id = ust_registry_get_next_chan_id(session); + + rcu_read_lock(); + lttng_ht_node_init_u64(&chan->node, key); + lttng_ht_add_unique_u64(session->channels, &chan->node); + rcu_read_unlock(); + + return 0; + +error: + destroy_channel(chan, false); +error_alloc: + return ret; +} + +/* + * Find a channel in the given registry. RCU read side lock MUST be acquired + * before calling this function and as long as the event reference is kept by + * the caller. + * + * On success, the pointer is returned else NULL. + */ +struct ust_registry_channel *ust_registry_channel_find( + struct ust_registry_session *session, uint64_t key) +{ + struct lttng_ht_node_u64 *node; + struct lttng_ht_iter iter; + struct ust_registry_channel *chan = NULL; + + LTTNG_ASSERT(session); + LTTNG_ASSERT(session->channels); + + DBG3("UST registry channel finding key %" PRIu64, key); + + lttng_ht_lookup(session->channels, &key, &iter); + node = lttng_ht_iter_get_node_u64(&iter); + if (!node) { + goto end; + } + chan = caa_container_of(node, struct ust_registry_channel, node); + +end: + return chan; +} + +/* + * Remove channel using key from registry and free memory. + */ +void ust_registry_channel_del_free(struct ust_registry_session *session, + uint64_t key, bool notif) +{ + struct lttng_ht_iter iter; + struct ust_registry_channel *chan; + int ret; + + LTTNG_ASSERT(session); + + rcu_read_lock(); + chan = ust_registry_channel_find(session, key); + if (!chan) { + rcu_read_unlock(); + goto end; + } + + iter.iter.node = &chan->node.node; + ret = lttng_ht_del(session->channels, &iter); + LTTNG_ASSERT(!ret); + rcu_read_unlock(); + destroy_channel(chan, notif); + +end: + return; +} + +/* + * Initialize registry with default values and set the newly allocated session + * pointer to sessionp. + * + * Return 0 on success and sessionp is set or else return -1 and sessionp is + * kept untouched. + */ +int ust_registry_session_init(struct ust_registry_session **sessionp, + struct ust_app *app, + uint32_t bits_per_long, + uint32_t uint8_t_alignment, + uint32_t uint16_t_alignment, + uint32_t uint32_t_alignment, + uint32_t uint64_t_alignment, + uint32_t long_alignment, + int byte_order, + uint32_t major, + uint32_t minor, + const char *root_shm_path, + const char *shm_path, + uid_t euid, + gid_t egid, + uint64_t tracing_id, + uid_t tracing_uid) +{ + int ret; + struct ust_registry_session *session; + + LTTNG_ASSERT(sessionp); + + session = (ust_registry_session *) zmalloc(sizeof(*session)); + if (!session) { + PERROR("zmalloc ust registry session"); + goto error_alloc; + } + + pthread_mutex_init(&session->lock, NULL); + session->bits_per_long = bits_per_long; + session->uint8_t_alignment = uint8_t_alignment; + session->uint16_t_alignment = uint16_t_alignment; + session->uint32_t_alignment = uint32_t_alignment; + session->uint64_t_alignment = uint64_t_alignment; + session->long_alignment = long_alignment; + session->byte_order = byte_order; + session->metadata_fd = -1; + session->uid = euid; + session->gid = egid; + session->next_enum_id = 0; + session->major = major; + session->minor = minor; + strncpy(session->root_shm_path, root_shm_path, + sizeof(session->root_shm_path)); + session->root_shm_path[sizeof(session->root_shm_path) - 1] = '\0'; + if (shm_path[0]) { + strncpy(session->shm_path, shm_path, + sizeof(session->shm_path)); + session->shm_path[sizeof(session->shm_path) - 1] = '\0'; + strncpy(session->metadata_path, shm_path, + sizeof(session->metadata_path)); + session->metadata_path[sizeof(session->metadata_path) - 1] = '\0'; + strncat(session->metadata_path, "/metadata", + sizeof(session->metadata_path) + - strlen(session->metadata_path) - 1); + } + if (session->shm_path[0]) { + ret = run_as_mkdir_recursive(session->shm_path, + S_IRWXU | S_IRWXG, + euid, egid); + if (ret) { + PERROR("run_as_mkdir_recursive"); + goto error; + } + } + if (session->metadata_path[0]) { + /* Create metadata file */ + ret = run_as_open(session->metadata_path, + O_WRONLY | O_CREAT | O_EXCL, + S_IRUSR | S_IWUSR, euid, egid); + if (ret < 0) { + PERROR("Opening metadata file"); + goto error; + } + session->metadata_fd = ret; + } + + session->enums = lttng_ht_new(0, LTTNG_HT_TYPE_STRING); + if (!session->enums) { + ERR("Failed to create enums hash table"); + goto error; + } + /* hash/match functions are specified at call site. */ + session->enums->match_fct = NULL; + session->enums->hash_fct = NULL; + + session->channels = lttng_ht_new(0, LTTNG_HT_TYPE_U64); + if (!session->channels) { + goto error; + } + + ret = lttng_uuid_generate(session->uuid); + if (ret) { + ERR("Failed to generate UST uuid (errno = %d)", ret); + goto error; + } + + session->tracing_id = tracing_id; + session->tracing_uid = tracing_uid; + + pthread_mutex_lock(&session->lock); + ret = ust_metadata_session_statedump(session, app, major, minor); + pthread_mutex_unlock(&session->lock); + if (ret) { + ERR("Failed to generate session metadata (errno = %d)", ret); + goto error; + } + + *sessionp = session; + + return 0; + +error: + ust_registry_session_destroy(session); + free(session); +error_alloc: + return -1; +} + +/* + * Destroy session registry. This does NOT free the given pointer since it + * might get passed as a reference. The registry lock should NOT be acquired. + */ +void ust_registry_session_destroy(struct ust_registry_session *reg) +{ + int ret; + struct lttng_ht_iter iter; + struct ust_registry_channel *chan; + struct ust_registry_enum *reg_enum; + + if (!reg) { + return; + } + + /* On error, EBUSY can be returned if lock. Code flow error. */ + ret = pthread_mutex_destroy(®->lock); + LTTNG_ASSERT(!ret); + + if (reg->channels) { + rcu_read_lock(); + /* Destroy all event associated with this registry. */ + cds_lfht_for_each_entry(reg->channels->ht, &iter.iter, chan, + node.node) { + /* Delete the node from the ht and free it. */ + ret = lttng_ht_del(reg->channels, &iter); + LTTNG_ASSERT(!ret); + destroy_channel(chan, true); + } + rcu_read_unlock(); + ht_cleanup_push(reg->channels); + } + + free(reg->metadata); + if (reg->metadata_fd >= 0) { + ret = close(reg->metadata_fd); + if (ret) { + PERROR("close"); + } + ret = run_as_unlink(reg->metadata_path, + reg->uid, reg->gid); + if (ret) { + PERROR("unlink"); + } + } + if (reg->root_shm_path[0]) { + /* + * Try deleting the directory hierarchy. + */ + (void) run_as_rmdir_recursive(reg->root_shm_path, + reg->uid, reg->gid, + LTTNG_DIRECTORY_HANDLE_SKIP_NON_EMPTY_FLAG); + } + /* Destroy the enum hash table */ + if (reg->enums) { + rcu_read_lock(); + /* Destroy all enum entries associated with this registry. */ + cds_lfht_for_each_entry(reg->enums->ht, &iter.iter, reg_enum, + node.node) { + ust_registry_destroy_enum(reg, reg_enum); + } + rcu_read_unlock(); + ht_cleanup_push(reg->enums); + } +} diff --git a/src/bin/lttng-sessiond/ust-sigbus.c b/src/bin/lttng-sessiond/ust-sigbus.c deleted file mode 100644 index 52a7ac270..000000000 --- a/src/bin/lttng-sessiond/ust-sigbus.c +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) 2021 Mathieu Desnoyers - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include "ust-sigbus.h" - -DEFINE_LTTNG_UST_SIGBUS_STATE(); - -void lttng_ust_handle_sigbus(void *address) -{ - lttng_ust_ctl_sigbus_handle(address); -} diff --git a/src/bin/lttng-sessiond/ust-sigbus.cpp b/src/bin/lttng-sessiond/ust-sigbus.cpp new file mode 100644 index 000000000..52a7ac270 --- /dev/null +++ b/src/bin/lttng-sessiond/ust-sigbus.cpp @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2021 Mathieu Desnoyers + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include "ust-sigbus.h" + +DEFINE_LTTNG_UST_SIGBUS_STATE(); + +void lttng_ust_handle_sigbus(void *address) +{ + lttng_ust_ctl_sigbus_handle(address); +} diff --git a/src/bin/lttng-sessiond/utils.c b/src/bin/lttng-sessiond/utils.c deleted file mode 100644 index 6936a03f9..000000000 --- a/src/bin/lttng-sessiond/utils.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * Copyright (C) 2011 Mathieu Desnoyers - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#define _LGPL_SOURCE -#include -#include - -#include - -#include "utils.h" -#include "snapshot.h" -#include "lttng-sessiond.h" - -int the_ht_cleanup_pipe[2] = {-1, -1}; - -/* - * Write to writable pipe used to notify a thread. - */ -int notify_thread_pipe(int wpipe) -{ - ssize_t ret; - - /* Ignore if the pipe is invalid. */ - if (wpipe < 0) { - return 0; - } - - ret = lttng_write(wpipe, "!", 1); - if (ret < 1) { - PERROR("write poll pipe"); - } - - return (int) ret; -} - -void ht_cleanup_push(struct lttng_ht *ht) -{ - ssize_t ret; - int fd = the_ht_cleanup_pipe[1]; - - if (!ht) { - return; - } - if (fd < 0) - return; - ret = lttng_write(fd, &ht, sizeof(ht)); - if (ret < sizeof(ht)) { - PERROR("write ht cleanup pipe %d", fd); - if (ret < 0) { - ret = -errno; - } - goto error; - } - - /* All good. Don't send back the write positive ret value. */ - ret = 0; -error: - LTTNG_ASSERT(!ret); -} - -int loglevels_match(int a_loglevel_type, int a_loglevel_value, - int b_loglevel_type, int b_loglevel_value, int loglevel_all_type) -{ - int match = 1; - - if (a_loglevel_type == b_loglevel_type) { - /* Same loglevel type. */ - if (b_loglevel_type != loglevel_all_type) { - /* - * Loglevel value must also match since the loglevel - * type is not all. - */ - if (a_loglevel_value != b_loglevel_value) { - match = 0; - } - } - } else { - /* Loglevel type is different: no match. */ - match = 0; - } - - return match; -} - -const char *session_get_base_path(const struct ltt_session *session) -{ - return consumer_output_get_base_path(session->consumer); -} - -const char *consumer_output_get_base_path(const struct consumer_output *output) -{ - return output->type == CONSUMER_DST_LOCAL ? - output->dst.session_root_path : - output->dst.net.base_dir; -} diff --git a/src/bin/lttng-sessiond/utils.cpp b/src/bin/lttng-sessiond/utils.cpp new file mode 100644 index 000000000..6936a03f9 --- /dev/null +++ b/src/bin/lttng-sessiond/utils.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2011 David Goulet + * Copyright (C) 2011 Mathieu Desnoyers + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#define _LGPL_SOURCE +#include +#include + +#include + +#include "utils.h" +#include "snapshot.h" +#include "lttng-sessiond.h" + +int the_ht_cleanup_pipe[2] = {-1, -1}; + +/* + * Write to writable pipe used to notify a thread. + */ +int notify_thread_pipe(int wpipe) +{ + ssize_t ret; + + /* Ignore if the pipe is invalid. */ + if (wpipe < 0) { + return 0; + } + + ret = lttng_write(wpipe, "!", 1); + if (ret < 1) { + PERROR("write poll pipe"); + } + + return (int) ret; +} + +void ht_cleanup_push(struct lttng_ht *ht) +{ + ssize_t ret; + int fd = the_ht_cleanup_pipe[1]; + + if (!ht) { + return; + } + if (fd < 0) + return; + ret = lttng_write(fd, &ht, sizeof(ht)); + if (ret < sizeof(ht)) { + PERROR("write ht cleanup pipe %d", fd); + if (ret < 0) { + ret = -errno; + } + goto error; + } + + /* All good. Don't send back the write positive ret value. */ + ret = 0; +error: + LTTNG_ASSERT(!ret); +} + +int loglevels_match(int a_loglevel_type, int a_loglevel_value, + int b_loglevel_type, int b_loglevel_value, int loglevel_all_type) +{ + int match = 1; + + if (a_loglevel_type == b_loglevel_type) { + /* Same loglevel type. */ + if (b_loglevel_type != loglevel_all_type) { + /* + * Loglevel value must also match since the loglevel + * type is not all. + */ + if (a_loglevel_value != b_loglevel_value) { + match = 0; + } + } + } else { + /* Loglevel type is different: no match. */ + match = 0; + } + + return match; +} + +const char *session_get_base_path(const struct ltt_session *session) +{ + return consumer_output_get_base_path(session->consumer); +} + +const char *consumer_output_get_base_path(const struct consumer_output *output) +{ + return output->type == CONSUMER_DST_LOCAL ? + output->dst.session_root_path : + output->dst.net.base_dir; +} diff --git a/src/common/buffer-view.h b/src/common/buffer-view.h index 58f1ede2e..ecd764d7c 100644 --- a/src/common/buffer-view.h +++ b/src/common/buffer-view.h @@ -13,6 +13,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_dynamic_buffer; struct lttng_buffer_view { @@ -92,4 +96,8 @@ bool lttng_buffer_view_contains_string(const struct lttng_buffer_view *buf, const char *str, size_t len_with_null_terminator); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_BUFFER_VIEW_H */ diff --git a/src/common/bytecode/bytecode.h b/src/common/bytecode/bytecode.h index d7ef4aedd..54ed7363e 100644 --- a/src/common/bytecode/bytecode.h +++ b/src/common/bytecode/bytecode.h @@ -13,6 +13,10 @@ #include "common/macros.h" #include "common/sessiond-comm/sessiond-comm.h" +#ifdef __cplusplus +extern "C" { +#endif + /* * offsets are absolute from start of bytecode. */ @@ -43,10 +47,6 @@ struct literal_double { double v; } LTTNG_PACKED; -struct literal_string { - char string[0]; -} LTTNG_PACKED; - enum bytecode_op { BYTECODE_OP_UNKNOWN = 0, @@ -262,4 +262,8 @@ unsigned int bytecode_get_len(struct lttng_bytecode *bytecode) return bytecode->len; } +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_COMMON_BYTECODE_H */ diff --git a/src/common/compat/directory-handle.h b/src/common/compat/directory-handle.h index 50185a526..9a1072019 100644 --- a/src/common/compat/directory-handle.h +++ b/src/common/compat/directory-handle.h @@ -13,6 +13,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + enum lttng_directory_handle_rmdir_recursive_flags { LTTNG_DIRECTORY_HANDLE_FAIL_NON_EMPTY_FLAG = (1U << 0), LTTNG_DIRECTORY_HANDLE_SKIP_NON_EMPTY_FLAG = (1U << 1), @@ -259,4 +263,9 @@ bool lttng_directory_handle_uses_fd( bool lttng_directory_handle_equals(const struct lttng_directory_handle *lhs, const struct lttng_directory_handle *rhs); + +#ifdef __cplusplus +} +#endif + #endif /* _COMPAT_PATH_HANDLE_H */ diff --git a/src/common/compat/poll.h b/src/common/compat/poll.h index e2a424abe..75de9ba5f 100644 --- a/src/common/compat/poll.h +++ b/src/common/compat/poll.h @@ -13,6 +13,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /* * Used by lttng_poll_clean to free the events structure in a lttng_poll_event. */ @@ -21,6 +25,10 @@ static inline void __lttng_poll_free(void *events) free(events); } +#ifdef __cplusplus +} +#endif + /* * epoll(7) implementation. */ @@ -30,6 +38,10 @@ static inline void __lttng_poll_free(void *events) #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* See man epoll(7) for this define path */ #define COMPAT_EPOLL_PROC_PATH "/proc/sys/fs/epoll/max_user_watches" @@ -208,6 +220,10 @@ static inline void lttng_poll_clean(struct lttng_poll_event *events) __lttng_poll_free((void *) events->events); } +#ifdef __cplusplus +} +#endif + #else /* HAVE_EPOLL */ /* * Fallback on poll(2) API @@ -226,6 +242,10 @@ static inline void lttng_poll_clean(struct lttng_poll_event *events) #include #include +#ifdef __cplusplus +extern "C" { +#endif + enum { /* Polling variables compatibility for poll */ LPOLLIN = POLLIN, @@ -377,6 +397,10 @@ static inline void lttng_poll_clean(struct lttng_poll_event *events) } } +#ifdef __cplusplus +} +#endif + #endif /* HAVE_EPOLL */ #endif /* _LTT_POLL_H */ diff --git a/src/common/config/session-config.h b/src/common/config/session-config.h index a6955b0b9..ff74eb099 100644 --- a/src/common/config/session-config.h +++ b/src/common/config/session-config.h @@ -13,6 +13,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct config_entry { /* section is NULL if the entry is not in a section */ const char *section; @@ -229,4 +233,8 @@ int config_load_session(const char *path, const char *session_name, int overwrite, unsigned int autoload, const struct config_load_session_override_attr *overrides); +#ifdef __cplusplus +} +#endif + #endif /* _CONFIG_H */ diff --git a/src/common/context.h b/src/common/context.h index f4abd8417..b6755adda 100644 --- a/src/common/context.h +++ b/src/common/context.h @@ -10,6 +10,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /* * Parse string as an application context of the form * "$app.provider_name:context_name" and return the provider name and context @@ -24,4 +28,8 @@ int parse_application_context(const char *str, char **provider_name, char **ctx_name); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_COMMON_CONTEXT_H */ diff --git a/src/common/credentials.h b/src/common/credentials.h index b1576a757..21aa33416 100644 --- a/src/common/credentials.h +++ b/src/common/credentials.h @@ -14,6 +14,10 @@ #include "optional.h" +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_credentials { LTTNG_OPTIONAL(uid_t) uid; LTTNG_OPTIONAL(gid_t) gid; @@ -32,4 +36,8 @@ bool lttng_credentials_is_equal_gid(const struct lttng_credentials *a, bool lttng_credentials_is_equal(const struct lttng_credentials *a, const struct lttng_credentials *b); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_CREDENTIALS_H */ diff --git a/src/common/daemonize.h b/src/common/daemonize.h index 2c88f06b7..8590779c2 100644 --- a/src/common/daemonize.h +++ b/src/common/daemonize.h @@ -12,6 +12,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* * Daemonize this process by forking and making the parent wait for the child * to signal it indicating readiness. Once received, the parent successfully @@ -25,4 +29,8 @@ int lttng_daemonize(pid_t *child_ppid, int *completion_flag, int close_fds); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_DAEMONIZE_H */ diff --git a/src/common/defaults.h b/src/common/defaults.h index 39b77470d..95b1dc29b 100644 --- a/src/common/defaults.h +++ b/src/common/defaults.h @@ -13,6 +13,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* Default unix group name for tracing. */ #define DEFAULT_TRACING_GROUP "tracing" @@ -424,4 +428,8 @@ size_t default_get_ust_uid_channel_subbuf_size(void); */ pthread_attr_t *default_pthread_attr(void); +#ifdef __cplusplus +} +#endif + #endif /* _DEFAULTS_H */ diff --git a/src/common/dynamic-buffer.h b/src/common/dynamic-buffer.h index 0e0dedc9b..d4813fa39 100644 --- a/src/common/dynamic-buffer.h +++ b/src/common/dynamic-buffer.h @@ -12,6 +12,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_buffer_view; struct lttng_dynamic_buffer { @@ -92,4 +96,8 @@ void lttng_dynamic_buffer_reset(struct lttng_dynamic_buffer *buffer); size_t lttng_dynamic_buffer_get_capacity_left( struct lttng_dynamic_buffer *buffer); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_DYNAMIC_BUFFER_H */ diff --git a/src/common/fd-handle.h b/src/common/fd-handle.h index dab9e2bfa..36a92473a 100644 --- a/src/common/fd-handle.h +++ b/src/common/fd-handle.h @@ -10,6 +10,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /* * Wrapper around a file descriptor providing reference counting semantics. * @@ -44,4 +48,8 @@ int fd_handle_get_fd(struct fd_handle *handle); */ struct fd_handle *fd_handle_copy(const struct fd_handle *handle); +#ifdef __cplusplus +} +#endif + #endif /* FS_HANDLE_H */ diff --git a/src/common/filter.h b/src/common/filter.h index 305434bf6..f5dcdf319 100644 --- a/src/common/filter.h +++ b/src/common/filter.h @@ -10,6 +10,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + struct bytecode_symbol_iterator; /* @@ -33,4 +37,8 @@ const char *bytecode_symbol_iterator_get_name( void bytecode_symbol_iterator_destroy(struct bytecode_symbol_iterator *it); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_COMMON_FILTER_H */ diff --git a/src/common/futex.h b/src/common/futex.h index 602be3186..92a844674 100644 --- a/src/common/futex.h +++ b/src/common/futex.h @@ -9,9 +9,17 @@ #ifndef _LTT_FUTEX_H #define _LTT_FUTEX_H +#ifdef __cplusplus +extern "C" { +#endif + void futex_wait_update(int32_t *futex, int active); void futex_nto1_prepare(int32_t *futex); void futex_nto1_wait(int32_t *futex); void futex_nto1_wake(int32_t *futex); +#ifdef __cplusplus +} +#endif + #endif /* _LTT_FUTEX_H */ diff --git a/src/common/hashtable/hashtable.h b/src/common/hashtable/hashtable.h index 2c77f0afc..e2dae968d 100644 --- a/src/common/hashtable/hashtable.h +++ b/src/common/hashtable/hashtable.h @@ -14,9 +14,13 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + extern unsigned long lttng_ht_seed; -typedef unsigned long (*hash_fct)(const void *_key, unsigned long seed); +typedef unsigned long (*hash_fct_type)(const void *_key, unsigned long seed); typedef cds_lfht_match_fct hash_match_fct; enum lttng_ht_type { @@ -29,7 +33,7 @@ enum lttng_ht_type { struct lttng_ht { struct cds_lfht *ht; cds_lfht_match_fct match_fct; - hash_fct hash_fct; + hash_fct_type hash_fct; }; struct lttng_ht_iter { @@ -122,4 +126,8 @@ struct lttng_ht_node_u64 *lttng_ht_iter_get_node_u64( struct lttng_ht_node_two_u64 *lttng_ht_iter_get_node_two_u64( struct lttng_ht_iter *iter); +#ifdef __cplusplus +} +#endif + #endif /* _LTT_HT_H */ diff --git a/src/common/hashtable/utils.h b/src/common/hashtable/utils.h index 992643931..44f80ce4f 100644 --- a/src/common/hashtable/utils.h +++ b/src/common/hashtable/utils.h @@ -10,6 +10,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + unsigned long hash_key_ulong(const void *_key, unsigned long seed); unsigned long hash_key_u64(const void *_key, unsigned long seed); unsigned long hash_key_str(const void *key, unsigned long seed); @@ -19,4 +23,8 @@ int hash_match_key_u64(const void *key1, const void *key2); int hash_match_key_str(const void *key1, const void *key2); int hash_match_key_two_u64(const void *key1, const void *key2); +#ifdef __cplusplus +} +#endif + #endif /* _LTT_HT_UTILS_H */ diff --git a/src/common/index-allocator.h b/src/common/index-allocator.h index 3da733e9e..9dbd745e6 100644 --- a/src/common/index-allocator.h +++ b/src/common/index-allocator.h @@ -10,6 +10,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_index_allocator; enum lttng_index_allocator_status { @@ -49,4 +53,8 @@ enum lttng_index_allocator_status lttng_index_allocator_release( */ void lttng_index_allocator_destroy(struct lttng_index_allocator *allocator); +#ifdef __cplusplus +} +#endif + #endif /* _COMMON_INDEX_ALLOCATOR_H */ diff --git a/src/common/kernel-ctl/kernel-ctl.h b/src/common/kernel-ctl/kernel-ctl.h index e6805a983..c2fa6408b 100644 --- a/src/common/kernel-ctl/kernel-ctl.h +++ b/src/common/kernel-ctl/kernel-ctl.h @@ -16,6 +16,10 @@ #include #include /* for struct lttng_filter_bytecode */ +#ifdef __cplusplus +extern "C" { +#endif + int kernctl_create_session(int fd); int kernctl_open_metadata(int fd, struct lttng_channel_attr *chops); int kernctl_create_channel(int fd, struct lttng_channel_attr *chops); @@ -125,4 +129,8 @@ int kernctl_get_current_timestamp(int fd, uint64_t *ts); int kernctl_get_sequence_number(int fd, uint64_t *seq); int kernctl_get_instance_id(int fd, uint64_t *seq); +#ifdef __cplusplus +} +#endif + #endif /* _LTTNG_KERNEL_CTL_H */ diff --git a/src/common/macros.h b/src/common/macros.h index 874501ef1..7530c912a 100644 --- a/src/common/macros.h +++ b/src/common/macros.h @@ -101,13 +101,6 @@ void *zmalloc(size_t len) #define ASSERT_LOCKED(lock) LTTNG_ASSERT(pthread_mutex_trylock(&lock)) -/* - * Get an aligned pointer to a value. This is meant - * as a helper to pass an aligned pointer to a member in a packed structure - * to a function. - */ -#define ALIGNED_CONST_PTR(value) (((const typeof(value) []) { value })) - /* * lttng_strncpy returns 0 on success, or nonzero on failure. * It checks that the @src string fits into @dst_len before performing diff --git a/src/common/optional.h b/src/common/optional.h index dcb7ca863..0501904cb 100644 --- a/src/common/optional.h +++ b/src/common/optional.h @@ -88,13 +88,13 @@ /* * Initialize an optional field as 'set' with a given value. */ -#define LTTNG_OPTIONAL_INIT_VALUE(val) { .value = val, .is_set = 1 } +#define LTTNG_OPTIONAL_INIT_VALUE(val) { .is_set = 1, .value = val } /* Set the value of an optional field. */ #define LTTNG_OPTIONAL_SET(field_ptr, val) \ do { \ - (field_ptr)->value = (val); \ (field_ptr)->is_set = 1; \ + (field_ptr)->value = (val); \ } while (0) /* Put an optional field in the "unset" (NULL-ed) state. */ diff --git a/src/common/payload-view.h b/src/common/payload-view.h index 57a8342d7..af8519ab3 100644 --- a/src/common/payload-view.h +++ b/src/common/payload-view.h @@ -11,6 +11,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_payload; struct fd_handle; @@ -46,7 +50,26 @@ struct fd_handle; struct lttng_payload_view { struct lttng_buffer_view buffer; /* private */ - const struct lttng_dynamic_pointer_array _fd_handles; + + /* + * Avoid a -Wreturn-type-c-linkage warning with clang. + * gcc is more permissive with regards to this warning, but + * clang is right that a structure containing a _const_ structure is not + * a trivial type in the eyes of the C++ standard, theoritically affecting its + * compatibility with C from an ABI standpoint: + * A trivial class is a class that is trivially copyable and has one or + * more default constructors, all of which are either trivial or deleted and + * at least one of which is not deleted. + * + * A const member implicitly deletes lttng_payload_view's constructor, + * making it non-trivial. This is not a problem for the moment as we are + * transitioning all code to C++11. + */ +#if !defined(__cplusplus) + const +#endif + struct lttng_dynamic_pointer_array _fd_handles; + struct { size_t *p_fd_handles_position; size_t fd_handles_position; @@ -159,4 +182,8 @@ int lttng_payload_view_get_fd_handle_count( struct fd_handle *lttng_payload_view_pop_fd_handle( struct lttng_payload_view *payload_view); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_PAYLOAD_VIEW_H */ diff --git a/src/common/payload.h b/src/common/payload.h index d6c0cc19e..e9bd8be9d 100644 --- a/src/common/payload.h +++ b/src/common/payload.h @@ -12,6 +12,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* * An lttng_payload encompasses the 'data' (bytes) and any passed file * descriptors as part of a message between liblttng-ctl and the session @@ -57,4 +61,8 @@ void lttng_payload_clear(struct lttng_payload *payload); int lttng_payload_push_fd_handle(struct lttng_payload *payload, struct fd_handle *fd_handle); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_PAYLOAD_H */ diff --git a/src/common/pipe.h b/src/common/pipe.h index d9f43d66e..b94d871e4 100644 --- a/src/common/pipe.h +++ b/src/common/pipe.h @@ -12,6 +12,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + enum lttng_pipe_state { LTTNG_PIPE_STATE_OPENED = 1, LTTNG_PIPE_STATE_CLOSED = 2, @@ -81,4 +85,8 @@ int lttng_pipe_release_readfd(struct lttng_pipe *pipe); /* Returns and releases the write end of the pipe. */ int lttng_pipe_release_writefd(struct lttng_pipe *pipe); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_PIPE_H */ diff --git a/src/common/readwrite.h b/src/common/readwrite.h index ac3668a5a..9923e8128 100644 --- a/src/common/readwrite.h +++ b/src/common/readwrite.h @@ -11,6 +11,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* * lttng_read and lttng_write take care of EINTR and partial read/write. * Upon success, they return the "count" received as parameter. @@ -22,4 +26,8 @@ ssize_t lttng_read(int fd, void *buf, size_t count); ssize_t lttng_write(int fd, const void *buf, size_t count); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_COMMON_READWRITE_H */ diff --git a/src/common/relayd/relayd.h b/src/common/relayd/relayd.h index 1fbe22676..6053be796 100644 --- a/src/common/relayd/relayd.h +++ b/src/common/relayd/relayd.h @@ -16,6 +16,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct relayd_stream_rotation_position { uint64_t stream_id; /* @@ -77,4 +81,8 @@ int relayd_get_configuration(struct lttcomm_relayd_sock *sock, uint64_t query_flags, uint64_t *result_flags); +#ifdef __cplusplus +} +#endif + #endif /* _RELAYD_H */ diff --git a/src/common/runas.h b/src/common/runas.h index 406dfca22..ca084102e 100644 --- a/src/common/runas.h +++ b/src/common/runas.h @@ -16,6 +16,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* * The run-as process is launched by forking without an exec*() call. This means * that any resource allocated before the run-as worker is launched should be @@ -61,4 +65,8 @@ int run_as_create_worker(const char *procname, post_fork_cleanup_cb clean_up_func, void *clean_up_user_data); void run_as_destroy_worker(void); +#ifdef __cplusplus +} +#endif + #endif /* _RUNAS_H */ diff --git a/src/common/sessiond-comm/inet.c b/src/common/sessiond-comm/inet.c index 357e10b08..93fb19bb5 100644 --- a/src/common/sessiond-comm/inet.c +++ b/src/common/sessiond-comm/inet.c @@ -87,19 +87,17 @@ error: */ int lttcomm_bind_inet_sock(struct lttcomm_sock *sock) { - return bind(sock->fd, - (const struct sockaddr *) ALIGNED_CONST_PTR( - sock->sockaddr.addr.sin), - sizeof(sock->sockaddr.addr.sin)); + struct sockaddr_in sockaddr = sock->sockaddr.addr.sin; + + return bind(sock->fd, &sockaddr, sizeof(sockaddr)); } static int connect_no_timeout(struct lttcomm_sock *sock) { - return connect(sock->fd, - (const struct sockaddr *) ALIGNED_CONST_PTR( - sock->sockaddr.addr.sin), - sizeof(sock->sockaddr.addr.sin)); + struct sockaddr_in sockaddr = sock->sockaddr.addr.sin; + + return connect(sock->fd, &sockaddr, sizeof(sockaddr)); } static @@ -109,6 +107,7 @@ int connect_with_timeout(struct lttcomm_sock *sock) int ret, flags, connect_ret; struct timespec orig_time, cur_time; unsigned long diff_ms; + struct sockaddr_in sockaddr; ret = fcntl(sock->fd, F_GETFL, 0); if (ret == -1) { @@ -130,10 +129,8 @@ int connect_with_timeout(struct lttcomm_sock *sock) return -1; } - connect_ret = connect(sock->fd, - (const struct sockaddr *) ALIGNED_CONST_PTR( - sock->sockaddr.addr.sin), - sizeof(sock->sockaddr.addr.sin)); + sockaddr = sock->sockaddr.addr.sin; + connect_ret = connect(sock->fd, &sockaddr, sizeof(sockaddr)); if (connect_ret == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINPROGRESS) { goto error; diff --git a/src/common/sessiond-comm/inet.h b/src/common/sessiond-comm/inet.h index 2db8c0890..5c0828b2b 100644 --- a/src/common/sessiond-comm/inet.h +++ b/src/common/sessiond-comm/inet.h @@ -12,6 +12,10 @@ #include "sessiond-comm.h" +#ifdef __cplusplus +extern "C" { +#endif + /* See man tcp(7) for more detail about this value. */ #define LTTCOMM_INET_PROC_SYN_RETRIES_PATH "/proc/sys/net/ipv4/tcp_syn_retries" #define LTTCOMM_INET_PROC_FIN_TIMEOUT_PATH "/proc/sys/net/ipv4/tcp_fin_timeout" @@ -53,4 +57,8 @@ extern ssize_t lttcomm_sendmsg_inet_sock(struct lttcomm_sock *sock, /* Initialize inet communication layer. */ extern void lttcomm_inet_init(void); +#ifdef __cplusplus +} +#endif + #endif /* _LTTCOMM_INET_H */ diff --git a/src/common/sessiond-comm/inet6.c b/src/common/sessiond-comm/inet6.c index f705bc0c0..8c0d0b795 100644 --- a/src/common/sessiond-comm/inet6.c +++ b/src/common/sessiond-comm/inet6.c @@ -85,19 +85,15 @@ error: */ int lttcomm_bind_inet6_sock(struct lttcomm_sock *sock) { - return bind(sock->fd, - (const struct sockaddr *) ALIGNED_CONST_PTR( - sock->sockaddr.addr.sin6), - sizeof(sock->sockaddr.addr.sin6)); + struct sockaddr_in6 sockaddr = sock->sockaddr.addr.sin6; + return bind(sock->fd, &sockaddr, sizeof(sockaddr)); } static int connect_no_timeout(struct lttcomm_sock *sock) { - return connect(sock->fd, - (const struct sockaddr *) ALIGNED_CONST_PTR( - sock->sockaddr.addr.sin6), - sizeof(sock->sockaddr.addr.sin6)); + struct sockaddr_in6 sockaddr = sock->sockaddr.addr.sin6; + return connect(sock->fd, &sockaddr, sizeof(sockaddr)); } static @@ -107,6 +103,7 @@ int connect_with_timeout(struct lttcomm_sock *sock) int ret, flags, connect_ret; struct timespec orig_time, cur_time; unsigned long diff_ms; + struct sockaddr_in6 sockaddr; ret = fcntl(sock->fd, F_GETFL, 0); if (ret == -1) { @@ -128,10 +125,8 @@ int connect_with_timeout(struct lttcomm_sock *sock) return -1; } - connect_ret = connect(sock->fd, - (const struct sockaddr *) ALIGNED_CONST_PTR( - sock->sockaddr.addr.sin6), - sizeof(sock->sockaddr.addr.sin6)); + sockaddr = sock->sockaddr.addr.sin6; + connect_ret = connect(sock->fd, &sockaddr, sizeof(sockaddr)); if (connect_ret == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINPROGRESS) { goto error; diff --git a/src/common/sessiond-comm/sessiond-comm.h b/src/common/sessiond-comm/sessiond-comm.h index c8e1e1ae1..65020bbfc 100644 --- a/src/common/sessiond-comm/sessiond-comm.h +++ b/src/common/sessiond-comm/sessiond-comm.h @@ -37,6 +37,10 @@ #include "inet6.h" #include +#ifdef __cplusplus +extern "C" { +#endif + /* Queue size of listen(2) */ #define LTTNG_SESSIOND_COMM_MAX_LISTEN 64 @@ -931,4 +935,8 @@ void lttcomm_init(void); /* Get network timeout, in milliseconds */ unsigned long lttcomm_get_network_timeout(void); +#ifdef __cplusplus +} +#endif + #endif /* _LTTNG_SESSIOND_COMM_H */ diff --git a/src/common/shm.h b/src/common/shm.h index d714506b8..530507c35 100644 --- a/src/common/shm.h +++ b/src/common/shm.h @@ -9,8 +9,16 @@ #ifndef _LTT_SHM_H #define _LTT_SHM_H +#ifdef __cplusplus +extern "C" { +#endif + char *shm_ust_get_mmap(char *shm_path, int global); int shm_create_anonymous(const char *owner_name); +#ifdef __cplusplus +} +#endif + #endif /* _LTT_SHM_H */ diff --git a/src/common/testpoint/testpoint.h b/src/common/testpoint/testpoint.h index 1f856f397..b613b1749 100644 --- a/src/common/testpoint/testpoint.h +++ b/src/common/testpoint/testpoint.h @@ -14,6 +14,10 @@ #include /* for caa_likely/unlikely */ +#ifdef __cplusplus +extern "C" { +#endif + extern int lttng_testpoint_activated; void *lttng_testpoint_lookup(const char *name); @@ -43,7 +47,7 @@ void *lttng_testpoint_lookup(const char *name); ret = tp(); \ } else { \ if (!found) { \ - tp = lttng_testpoint_lookup(tp_name); \ + tp = (int (*)(void)) lttng_testpoint_lookup(tp_name); \ if (tp) { \ found = 1; \ ret = tp(); \ @@ -59,4 +63,8 @@ void *lttng_testpoint_lookup(const char *name); #define TESTPOINT_DECL(name) \ _TESTPOINT_DECL(name) +#ifdef __cplusplus +} +#endif + #endif /* NTESTPOINT */ diff --git a/src/common/trace-chunk.h b/src/common/trace-chunk.h index 4e4440b6a..9bb4f3708 100644 --- a/src/common/trace-chunk.h +++ b/src/common/trace-chunk.h @@ -16,6 +16,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* * A trace chunk is a group of directories and files forming a (or a set of) * complete and independant trace(s). For instance, a trace archive chunk, @@ -182,4 +186,8 @@ bool lttng_trace_chunk_get(struct lttng_trace_chunk *chunk); void lttng_trace_chunk_put(struct lttng_trace_chunk *chunk); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_TRACE_CHUNK_H */ diff --git a/src/common/unix.h b/src/common/unix.h index 820dff0b5..e5c7435e3 100644 --- a/src/common/unix.h +++ b/src/common/unix.h @@ -16,6 +16,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + int lttcomm_create_unix_sock(const char *pathname); int lttcomm_create_anon_unix_socketpair(int *fds); int lttcomm_connect_unix_sock(const char *pathname); @@ -51,4 +55,8 @@ ssize_t lttcomm_recv_creds_unix_sock(int sock, void *buf, size_t len, int lttcomm_setsockopt_creds_unix_sock(int sock); +#ifdef __cplusplus +} +#endif + #endif /* _LTTCOMM_UNIX_H */ diff --git a/src/common/uuid.h b/src/common/uuid.h index 0d3e79aee..d95ea062e 100644 --- a/src/common/uuid.h +++ b/src/common/uuid.h @@ -13,6 +13,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /* * Includes final \0. */ @@ -59,4 +63,8 @@ void lttng_uuid_copy(lttng_uuid dst, const lttng_uuid src); */ int lttng_uuid_generate(lttng_uuid uuid_out); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_UUID_H */ diff --git a/src/common/waiter.h b/src/common/waiter.h index ed870aeb0..30b51ee80 100644 --- a/src/common/waiter.h +++ b/src/common/waiter.h @@ -17,6 +17,10 @@ #include #include "macros.h" +#ifdef __cplusplus +extern "C" { +#endif + struct lttng_waiter { struct cds_wfs_node wait_queue_node; int32_t state; @@ -33,4 +37,8 @@ void lttng_waiter_wait(struct lttng_waiter *waiter); */ void lttng_waiter_wake_up(struct lttng_waiter *waiter); +#ifdef __cplusplus +} +#endif + #endif /* LTTNG_WAITER_H */ diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 7ff30d266..c8deb2ec3 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -83,7 +83,7 @@ test_uri_LDADD = $(LIBTAP) $(LIBCOMMON) $(LIBHASHTABLE) $(DL_LIBS) RELAYD_OBJS = $(top_builddir)/src/bin/lttng-relayd/backward-compatibility-group-by.$(OBJEXT) -test_session_SOURCES = test_session.c +test_session_SOURCES = test_session.cpp test_session_LDADD = $(LIBTAP) $(LIBLTTNG_SESSIOND_COMMON) $(DL_LIBS) if HAVE_LIBLTTNG_UST_CTL @@ -93,11 +93,11 @@ endif # UST data structures unit test if HAVE_LIBLTTNG_UST_CTL -test_ust_data_SOURCES = test_ust_data.c +test_ust_data_SOURCES = test_ust_data.cpp test_ust_data_LDADD = $(LIBTAP) $(LIBLTTNG_SESSIOND_COMMON) $(DL_LIBS) endif -test_kernel_data_SOURCES = test_kernel_data.c +test_kernel_data_SOURCES = test_kernel_data.cpp test_kernel_data_LDADD = $(LIBTAP) $(LIBLTTNG_SESSIOND_COMMON) $(DL_LIBS) # utils suffix for unit test diff --git a/tests/unit/test_kernel_data.c b/tests/unit/test_kernel_data.c deleted file mode 100644 index 79489e3d3..000000000 --- a/tests/unit/test_kernel_data.c +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#define RANDOM_STRING_LEN 11 - -/* Number of TAP tests in this file */ -#define NUM_TESTS 11 - -#ifdef HAVE_LIBLTTNG_UST_CTL -#include -DEFINE_LTTNG_UST_SIGBUS_STATE(); -#endif - -static const char alphanum[] = - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - -static struct ltt_kernel_session *kern; -static char random_string[RANDOM_STRING_LEN]; - -/* - * Return random string of 10 characters. - * Not thread-safe. - */ -static char *get_random_string(void) -{ - int i; - - for (i = 0; i < RANDOM_STRING_LEN - 1; i++) { - random_string[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; - } - - random_string[RANDOM_STRING_LEN - 1] = '\0'; - - return random_string; -} - -static void test_create_one_kernel_session(void) -{ - kern = trace_kernel_create_session(); - ok(kern != NULL, "Create kernel session"); - - if (!kern) { - skip(1, "Kernel session is null"); - return; - } - ok(kern->fd == -1 && - kern->metadata_stream_fd == -1 && - kern->consumer_fds_sent == 0 && - kern->channel_count == 0 && - kern->stream_count_global == 0 && - kern->metadata == NULL, - "Validate kernel session"); -} - -static void test_create_kernel_metadata(void) -{ - LTTNG_ASSERT(kern != NULL); - - kern->metadata = trace_kernel_create_metadata(); - ok(kern->metadata != NULL, "Create kernel metadata"); - - ok(kern->metadata->fd == -1 && - kern->metadata->conf != NULL && - kern->metadata->conf->attr.overwrite - == DEFAULT_METADATA_OVERWRITE && - kern->metadata->conf->attr.subbuf_size - == default_get_metadata_subbuf_size() && - kern->metadata->conf->attr.num_subbuf - == DEFAULT_METADATA_SUBBUF_NUM && - kern->metadata->conf->attr.switch_timer_interval - == DEFAULT_METADATA_SWITCH_TIMER && - kern->metadata->conf->attr.read_timer_interval - == DEFAULT_METADATA_READ_TIMER && - kern->metadata->conf->attr.output - == LTTNG_EVENT_MMAP, - "Validate kernel session metadata"); - - trace_kernel_destroy_metadata(kern->metadata); -} - -static void test_create_kernel_channel(void) -{ - struct ltt_kernel_channel *chan; - struct lttng_channel attr; - struct lttng_channel_extended extended; - - memset(&attr, 0, sizeof(attr)); - memset(&extended, 0, sizeof(extended)); - attr.attr.extended.ptr = &extended; - - chan = trace_kernel_create_channel(&attr); - ok(chan != NULL, "Create kernel channel"); - - if (!chan) { - skip(1, "Channel is null"); - return; - } - - ok(chan->fd == -1 && - chan->enabled == 1 && - chan->stream_count == 0 && - chan->channel->attr.overwrite == attr.attr.overwrite, - "Validate kernel channel"); - - /* Init list in order to avoid sefaults from cds_list_del */ - CDS_INIT_LIST_HEAD(&chan->list); - trace_kernel_destroy_channel(chan); -} - -static void test_create_kernel_event(void) -{ - enum lttng_error_code ret; - struct ltt_kernel_event *event; - struct lttng_event ev; - - memset(&ev, 0, sizeof(ev)); - ok(!lttng_strncpy(ev.name, get_random_string(), - RANDOM_STRING_LEN), - "Validate string length"); - ev.type = LTTNG_EVENT_TRACEPOINT; - ev.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; - - ret = trace_kernel_create_event(&ev, NULL, NULL, &event); - ok(ret == LTTNG_OK, "Create kernel event"); - - if (!event) { - skip(1, "Event is null"); - return; - } - - ok(event->fd == -1 && - event->enabled == 1 && - event->event->instrumentation == LTTNG_KERNEL_ABI_TRACEPOINT && - strlen(event->event->name), - "Validate kernel event"); - - /* Init list in order to avoid sefaults from cds_list_del */ - CDS_INIT_LIST_HEAD(&event->list); - trace_kernel_destroy_event(event); -} - -static void test_create_kernel_stream(void) -{ - struct ltt_kernel_stream *stream; - - stream = trace_kernel_create_stream("stream1", 0); - ok(stream != NULL, "Create kernel stream"); - - if (!stream) { - skip(1, "Stream is null"); - return; - } - - ok(stream->fd == -1 && - stream->state == 0, - "Validate kernel stream"); - - /* Init list in order to avoid sefaults from cds_list_del */ - CDS_INIT_LIST_HEAD(&stream->list); - trace_kernel_destroy_stream(stream); -} - -int main(int argc, char **argv) -{ - plan_tests(NUM_TESTS); - - diag("Kernel data structure unit test"); - - test_create_one_kernel_session(); - test_create_kernel_metadata(); - test_create_kernel_channel(); - test_create_kernel_event(); - test_create_kernel_stream(); - - return exit_status(); -} diff --git a/tests/unit/test_kernel_data.cpp b/tests/unit/test_kernel_data.cpp new file mode 100644 index 000000000..79489e3d3 --- /dev/null +++ b/tests/unit/test_kernel_data.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2011 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define RANDOM_STRING_LEN 11 + +/* Number of TAP tests in this file */ +#define NUM_TESTS 11 + +#ifdef HAVE_LIBLTTNG_UST_CTL +#include +DEFINE_LTTNG_UST_SIGBUS_STATE(); +#endif + +static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + +static struct ltt_kernel_session *kern; +static char random_string[RANDOM_STRING_LEN]; + +/* + * Return random string of 10 characters. + * Not thread-safe. + */ +static char *get_random_string(void) +{ + int i; + + for (i = 0; i < RANDOM_STRING_LEN - 1; i++) { + random_string[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + random_string[RANDOM_STRING_LEN - 1] = '\0'; + + return random_string; +} + +static void test_create_one_kernel_session(void) +{ + kern = trace_kernel_create_session(); + ok(kern != NULL, "Create kernel session"); + + if (!kern) { + skip(1, "Kernel session is null"); + return; + } + ok(kern->fd == -1 && + kern->metadata_stream_fd == -1 && + kern->consumer_fds_sent == 0 && + kern->channel_count == 0 && + kern->stream_count_global == 0 && + kern->metadata == NULL, + "Validate kernel session"); +} + +static void test_create_kernel_metadata(void) +{ + LTTNG_ASSERT(kern != NULL); + + kern->metadata = trace_kernel_create_metadata(); + ok(kern->metadata != NULL, "Create kernel metadata"); + + ok(kern->metadata->fd == -1 && + kern->metadata->conf != NULL && + kern->metadata->conf->attr.overwrite + == DEFAULT_METADATA_OVERWRITE && + kern->metadata->conf->attr.subbuf_size + == default_get_metadata_subbuf_size() && + kern->metadata->conf->attr.num_subbuf + == DEFAULT_METADATA_SUBBUF_NUM && + kern->metadata->conf->attr.switch_timer_interval + == DEFAULT_METADATA_SWITCH_TIMER && + kern->metadata->conf->attr.read_timer_interval + == DEFAULT_METADATA_READ_TIMER && + kern->metadata->conf->attr.output + == LTTNG_EVENT_MMAP, + "Validate kernel session metadata"); + + trace_kernel_destroy_metadata(kern->metadata); +} + +static void test_create_kernel_channel(void) +{ + struct ltt_kernel_channel *chan; + struct lttng_channel attr; + struct lttng_channel_extended extended; + + memset(&attr, 0, sizeof(attr)); + memset(&extended, 0, sizeof(extended)); + attr.attr.extended.ptr = &extended; + + chan = trace_kernel_create_channel(&attr); + ok(chan != NULL, "Create kernel channel"); + + if (!chan) { + skip(1, "Channel is null"); + return; + } + + ok(chan->fd == -1 && + chan->enabled == 1 && + chan->stream_count == 0 && + chan->channel->attr.overwrite == attr.attr.overwrite, + "Validate kernel channel"); + + /* Init list in order to avoid sefaults from cds_list_del */ + CDS_INIT_LIST_HEAD(&chan->list); + trace_kernel_destroy_channel(chan); +} + +static void test_create_kernel_event(void) +{ + enum lttng_error_code ret; + struct ltt_kernel_event *event; + struct lttng_event ev; + + memset(&ev, 0, sizeof(ev)); + ok(!lttng_strncpy(ev.name, get_random_string(), + RANDOM_STRING_LEN), + "Validate string length"); + ev.type = LTTNG_EVENT_TRACEPOINT; + ev.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; + + ret = trace_kernel_create_event(&ev, NULL, NULL, &event); + ok(ret == LTTNG_OK, "Create kernel event"); + + if (!event) { + skip(1, "Event is null"); + return; + } + + ok(event->fd == -1 && + event->enabled == 1 && + event->event->instrumentation == LTTNG_KERNEL_ABI_TRACEPOINT && + strlen(event->event->name), + "Validate kernel event"); + + /* Init list in order to avoid sefaults from cds_list_del */ + CDS_INIT_LIST_HEAD(&event->list); + trace_kernel_destroy_event(event); +} + +static void test_create_kernel_stream(void) +{ + struct ltt_kernel_stream *stream; + + stream = trace_kernel_create_stream("stream1", 0); + ok(stream != NULL, "Create kernel stream"); + + if (!stream) { + skip(1, "Stream is null"); + return; + } + + ok(stream->fd == -1 && + stream->state == 0, + "Validate kernel stream"); + + /* Init list in order to avoid sefaults from cds_list_del */ + CDS_INIT_LIST_HEAD(&stream->list); + trace_kernel_destroy_stream(stream); +} + +int main(int argc, char **argv) +{ + plan_tests(NUM_TESTS); + + diag("Kernel data structure unit test"); + + test_create_one_kernel_session(); + test_create_kernel_metadata(); + test_create_kernel_channel(); + test_create_kernel_event(); + test_create_kernel_stream(); + + return exit_status(); +} diff --git a/tests/unit/test_session.c b/tests/unit/test_session.c deleted file mode 100644 index 4fc23791e..000000000 --- a/tests/unit/test_session.c +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SESSION1 "test1" - -#define MAX_SESSIONS 10000 -#define RANDOM_STRING_LEN 11 - -/* Number of TAP tests in this file */ -#define NUM_TESTS 11 - -static struct ltt_session_list *session_list; - -static const char alphanum[] = - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; -static char random_string[RANDOM_STRING_LEN]; - -/* - * Return random string of 10 characters. - * Not thread-safe. - */ -static char *get_random_string(void) -{ - int i; - - for (i = 0; i < RANDOM_STRING_LEN - 1; i++) { - random_string[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; - } - - random_string[RANDOM_STRING_LEN - 1] = '\0'; - - return random_string; -} - -/* - * Return 0 if session name is found, else -1 - */ -static int find_session_name(const char *name) -{ - struct ltt_session *iter; - - cds_list_for_each_entry(iter, &session_list->head, list) { - if (strcmp(iter->name, name) == 0) { - return 0; - } - } - - return -1; -} - -static int session_list_count(void) -{ - int count = 0; - struct ltt_session *iter; - - cds_list_for_each_entry(iter, &session_list->head, list) { - count++; - } - return count; -} - -/* - * Empty session list manually. - */ -static void empty_session_list(void) -{ - struct ltt_session *iter, *tmp; - - session_lock_list(); - cds_list_for_each_entry_safe(iter, tmp, &session_list->head, list) { - session_destroy(iter); - } - session_unlock_list(); - - /* Session list must be 0 */ - LTTNG_ASSERT(!session_list_count()); -} - -/* - * Test creation of 1 session - */ -static int create_one_session(const char *name) -{ - int ret; - enum lttng_error_code ret_code; - struct ltt_session *session = NULL; - - session_lock_list(); - ret_code = session_create(name, geteuid(), getegid(), &session); - session_put(session); - if (ret_code == LTTNG_OK) { - /* Validate */ - ret = find_session_name(name); - if (ret < 0) { - /* Session not found by name */ - printf("session not found after creation\n"); - ret = -1; - } else { - /* Success */ - ret = 0; - } - } else { - if (ret_code == LTTNG_ERR_EXIST_SESS) { - printf("(session already exists) "); - } - ret = -1; - } - - session_unlock_list(); - return ret; -} - -/* - * Test deletion of 1 session - */ -static int destroy_one_session(struct ltt_session *session) -{ - int ret; - char session_name[NAME_MAX]; - - strncpy(session_name, session->name, sizeof(session_name)); - session_name[sizeof(session_name) - 1] = '\0'; - - session_destroy(session); - session_put(session); - - ret = find_session_name(session_name); - if (ret < 0) { - /* Success, -1 means that the sesion is NOT found */ - ret = 0; - } else { - /* Fail */ - ret = -1; - } - return ret; -} - -/* - * This test is supposed to fail at the second create call. If so, return 0 for - * test success, else -1. - */ -static int two_session_same_name(void) -{ - int ret; - struct ltt_session *sess; - - ret = create_one_session(SESSION1); - if (ret < 0) { - /* Fail */ - ret = -1; - goto end; - } - - session_lock_list(); - sess = session_find_by_name(SESSION1); - if (sess) { - /* Success */ - session_put(sess); - session_unlock_list(); - ret = 0; - goto end_unlock; - } else { - /* Fail */ - ret = -1; - goto end_unlock; - } -end_unlock: - session_unlock_list(); -end: - return ret; -} - -static void test_session_list(void) -{ - session_list = session_get_list(); - ok(session_list != NULL, "Session list: not NULL"); -} - -static void test_create_one_session(void) -{ - ok(create_one_session(SESSION1) == 0, - "Create session: %s", - SESSION1); -} - -static void test_validate_session(void) -{ - struct ltt_session *tmp; - - session_lock_list(); - tmp = session_find_by_name(SESSION1); - - ok(tmp != NULL, - "Validating session: session found"); - - if (tmp) { - ok(tmp->kernel_session == NULL && - strlen(tmp->name), - "Validating session: basic sanity check"); - } else { - skip(1, "Skipping session validation check as session was not found"); - goto end; - } - - session_lock(tmp); - session_unlock(tmp); - session_put(tmp); -end: - session_unlock_list(); -} - -static void test_destroy_session(void) -{ - struct ltt_session *tmp; - - session_lock_list(); - tmp = session_find_by_name(SESSION1); - - ok(tmp != NULL, - "Destroying session: session found"); - - if (tmp) { - ok(destroy_one_session(tmp) == 0, - "Destroying session: %s destroyed", - SESSION1); - } else { - skip(1, "Skipping session destruction as it was not found"); - } - session_unlock_list(); -} - -static void test_duplicate_session(void) -{ - ok(two_session_same_name() == 0, - "Duplicate session creation"); -} - -static void test_session_name_generation(void) -{ - struct ltt_session *session = NULL; - enum lttng_error_code ret_code; - const char *expected_session_name_prefix = DEFAULT_SESSION_NAME; - - session_lock_list(); - ret_code = session_create(NULL, geteuid(), getegid(), &session); - ok(ret_code == LTTNG_OK, - "Create session with a NULL name (auto-generate a name)"); - if (!session) { - skip(1, "Skipping session name generation tests as session_create() failed."); - goto end; - } - diag("Automatically-generated session name: %s", *session->name ? - session->name : "ERROR"); - ok(*session->name && !strncmp(expected_session_name_prefix, session->name, - sizeof(DEFAULT_SESSION_NAME) - 1), - "Auto-generated session name starts with %s", - DEFAULT_SESSION_NAME); -end: - session_put(session); - session_unlock_list(); -} - -static void test_large_session_number(void) -{ - int ret, i, failed = 0; - struct ltt_session *iter, *tmp; - - for (i = 0; i < MAX_SESSIONS; i++) { - char *tmp_name = get_random_string(); - ret = create_one_session(tmp_name); - if (ret < 0) { - diag("session %d (name: %s) creation failed", i, tmp_name); - ++failed; - } - } - - ok(failed == 0, - "Large sessions number: created %u sessions", - MAX_SESSIONS); - - failed = 0; - - session_lock_list(); - for (i = 0; i < MAX_SESSIONS; i++) { - cds_list_for_each_entry_safe(iter, tmp, &session_list->head, list) { - LTTNG_ASSERT(session_get(iter)); - ret = destroy_one_session(iter); - if (ret < 0) { - diag("session %d destroy failed", i); - ++failed; - } - } - } - session_unlock_list(); - - ok(failed == 0 && session_list_count() == 0, - "Large sessions number: destroyed %u sessions", - MAX_SESSIONS); -} - -int main(int argc, char **argv) -{ - struct lttng_thread *ht_cleanup_thread; - - plan_tests(NUM_TESTS); - - the_health_sessiond = health_app_create(NR_HEALTH_SESSIOND_TYPES); - ht_cleanup_thread = launch_ht_cleanup_thread(); - LTTNG_ASSERT(ht_cleanup_thread); - lttng_thread_put(ht_cleanup_thread); - - diag("Sessions unit tests"); - - rcu_register_thread(); - - test_session_list(); - - test_create_one_session(); - - test_validate_session(); - - test_destroy_session(); - - test_duplicate_session(); - - empty_session_list(); - - test_session_name_generation(); - - test_large_session_number(); - - rcu_unregister_thread(); - lttng_thread_list_shutdown_orphans(); - - return exit_status(); -} diff --git a/tests/unit/test_session.cpp b/tests/unit/test_session.cpp new file mode 100644 index 000000000..4fc23791e --- /dev/null +++ b/tests/unit/test_session.cpp @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2011 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SESSION1 "test1" + +#define MAX_SESSIONS 10000 +#define RANDOM_STRING_LEN 11 + +/* Number of TAP tests in this file */ +#define NUM_TESTS 11 + +static struct ltt_session_list *session_list; + +static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; +static char random_string[RANDOM_STRING_LEN]; + +/* + * Return random string of 10 characters. + * Not thread-safe. + */ +static char *get_random_string(void) +{ + int i; + + for (i = 0; i < RANDOM_STRING_LEN - 1; i++) { + random_string[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + random_string[RANDOM_STRING_LEN - 1] = '\0'; + + return random_string; +} + +/* + * Return 0 if session name is found, else -1 + */ +static int find_session_name(const char *name) +{ + struct ltt_session *iter; + + cds_list_for_each_entry(iter, &session_list->head, list) { + if (strcmp(iter->name, name) == 0) { + return 0; + } + } + + return -1; +} + +static int session_list_count(void) +{ + int count = 0; + struct ltt_session *iter; + + cds_list_for_each_entry(iter, &session_list->head, list) { + count++; + } + return count; +} + +/* + * Empty session list manually. + */ +static void empty_session_list(void) +{ + struct ltt_session *iter, *tmp; + + session_lock_list(); + cds_list_for_each_entry_safe(iter, tmp, &session_list->head, list) { + session_destroy(iter); + } + session_unlock_list(); + + /* Session list must be 0 */ + LTTNG_ASSERT(!session_list_count()); +} + +/* + * Test creation of 1 session + */ +static int create_one_session(const char *name) +{ + int ret; + enum lttng_error_code ret_code; + struct ltt_session *session = NULL; + + session_lock_list(); + ret_code = session_create(name, geteuid(), getegid(), &session); + session_put(session); + if (ret_code == LTTNG_OK) { + /* Validate */ + ret = find_session_name(name); + if (ret < 0) { + /* Session not found by name */ + printf("session not found after creation\n"); + ret = -1; + } else { + /* Success */ + ret = 0; + } + } else { + if (ret_code == LTTNG_ERR_EXIST_SESS) { + printf("(session already exists) "); + } + ret = -1; + } + + session_unlock_list(); + return ret; +} + +/* + * Test deletion of 1 session + */ +static int destroy_one_session(struct ltt_session *session) +{ + int ret; + char session_name[NAME_MAX]; + + strncpy(session_name, session->name, sizeof(session_name)); + session_name[sizeof(session_name) - 1] = '\0'; + + session_destroy(session); + session_put(session); + + ret = find_session_name(session_name); + if (ret < 0) { + /* Success, -1 means that the sesion is NOT found */ + ret = 0; + } else { + /* Fail */ + ret = -1; + } + return ret; +} + +/* + * This test is supposed to fail at the second create call. If so, return 0 for + * test success, else -1. + */ +static int two_session_same_name(void) +{ + int ret; + struct ltt_session *sess; + + ret = create_one_session(SESSION1); + if (ret < 0) { + /* Fail */ + ret = -1; + goto end; + } + + session_lock_list(); + sess = session_find_by_name(SESSION1); + if (sess) { + /* Success */ + session_put(sess); + session_unlock_list(); + ret = 0; + goto end_unlock; + } else { + /* Fail */ + ret = -1; + goto end_unlock; + } +end_unlock: + session_unlock_list(); +end: + return ret; +} + +static void test_session_list(void) +{ + session_list = session_get_list(); + ok(session_list != NULL, "Session list: not NULL"); +} + +static void test_create_one_session(void) +{ + ok(create_one_session(SESSION1) == 0, + "Create session: %s", + SESSION1); +} + +static void test_validate_session(void) +{ + struct ltt_session *tmp; + + session_lock_list(); + tmp = session_find_by_name(SESSION1); + + ok(tmp != NULL, + "Validating session: session found"); + + if (tmp) { + ok(tmp->kernel_session == NULL && + strlen(tmp->name), + "Validating session: basic sanity check"); + } else { + skip(1, "Skipping session validation check as session was not found"); + goto end; + } + + session_lock(tmp); + session_unlock(tmp); + session_put(tmp); +end: + session_unlock_list(); +} + +static void test_destroy_session(void) +{ + struct ltt_session *tmp; + + session_lock_list(); + tmp = session_find_by_name(SESSION1); + + ok(tmp != NULL, + "Destroying session: session found"); + + if (tmp) { + ok(destroy_one_session(tmp) == 0, + "Destroying session: %s destroyed", + SESSION1); + } else { + skip(1, "Skipping session destruction as it was not found"); + } + session_unlock_list(); +} + +static void test_duplicate_session(void) +{ + ok(two_session_same_name() == 0, + "Duplicate session creation"); +} + +static void test_session_name_generation(void) +{ + struct ltt_session *session = NULL; + enum lttng_error_code ret_code; + const char *expected_session_name_prefix = DEFAULT_SESSION_NAME; + + session_lock_list(); + ret_code = session_create(NULL, geteuid(), getegid(), &session); + ok(ret_code == LTTNG_OK, + "Create session with a NULL name (auto-generate a name)"); + if (!session) { + skip(1, "Skipping session name generation tests as session_create() failed."); + goto end; + } + diag("Automatically-generated session name: %s", *session->name ? + session->name : "ERROR"); + ok(*session->name && !strncmp(expected_session_name_prefix, session->name, + sizeof(DEFAULT_SESSION_NAME) - 1), + "Auto-generated session name starts with %s", + DEFAULT_SESSION_NAME); +end: + session_put(session); + session_unlock_list(); +} + +static void test_large_session_number(void) +{ + int ret, i, failed = 0; + struct ltt_session *iter, *tmp; + + for (i = 0; i < MAX_SESSIONS; i++) { + char *tmp_name = get_random_string(); + ret = create_one_session(tmp_name); + if (ret < 0) { + diag("session %d (name: %s) creation failed", i, tmp_name); + ++failed; + } + } + + ok(failed == 0, + "Large sessions number: created %u sessions", + MAX_SESSIONS); + + failed = 0; + + session_lock_list(); + for (i = 0; i < MAX_SESSIONS; i++) { + cds_list_for_each_entry_safe(iter, tmp, &session_list->head, list) { + LTTNG_ASSERT(session_get(iter)); + ret = destroy_one_session(iter); + if (ret < 0) { + diag("session %d destroy failed", i); + ++failed; + } + } + } + session_unlock_list(); + + ok(failed == 0 && session_list_count() == 0, + "Large sessions number: destroyed %u sessions", + MAX_SESSIONS); +} + +int main(int argc, char **argv) +{ + struct lttng_thread *ht_cleanup_thread; + + plan_tests(NUM_TESTS); + + the_health_sessiond = health_app_create(NR_HEALTH_SESSIOND_TYPES); + ht_cleanup_thread = launch_ht_cleanup_thread(); + LTTNG_ASSERT(ht_cleanup_thread); + lttng_thread_put(ht_cleanup_thread); + + diag("Sessions unit tests"); + + rcu_register_thread(); + + test_session_list(); + + test_create_one_session(); + + test_validate_session(); + + test_destroy_session(); + + test_duplicate_session(); + + empty_session_list(); + + test_session_name_generation(); + + test_large_session_number(); + + rcu_unregister_thread(); + lttng_thread_list_shutdown_orphans(); + + return exit_status(); +} diff --git a/tests/unit/test_ust_data.c b/tests/unit/test_ust_data.c deleted file mode 100644 index 95c6fe12d..000000000 --- a/tests/unit/test_ust_data.c +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2011 David Goulet - * - * SPDX-License-Identifier: GPL-2.0-only - * - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -/* This path will NEVER be created in this test */ -#define PATH1 "/tmp/.test-junk-lttng" - -#define RANDOM_STRING_LEN 11 - -/* Number of TAP tests in this file */ -#define NUM_TESTS 16 - -DEFINE_LTTNG_UST_SIGBUS_STATE(); - -static const char alphanum[] = - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; -static char random_string[RANDOM_STRING_LEN]; - -/* - * Return random string of 10 characters. - * Not thread-safe. - */ -static char *get_random_string(void) -{ - int i; - - for (i = 0; i < RANDOM_STRING_LEN - 1; i++) { - random_string[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; - } - - random_string[RANDOM_STRING_LEN - 1] = '\0'; - - return random_string; -} - -static void test_create_one_ust_session(void) -{ - struct ltt_ust_session *usess = - trace_ust_create_session(42); - - ok(usess != NULL, "Create UST session"); - - if (!usess) { - skip(1, "UST session is null"); - return; - } - - ok(usess->id == 42 && - usess->active == 0 && - usess->domain_global.channels != NULL && - usess->uid == 0 && - usess->gid == 0, - "Validate UST session"); - - trace_ust_destroy_session(usess); -} - -static void test_create_ust_channel(void) -{ - struct ltt_ust_channel *uchan; - struct lttng_channel attr; - struct lttng_channel_extended extended; - - memset(&attr, 0, sizeof(attr)); - memset(&extended, 0, sizeof(extended)); - attr.attr.extended.ptr = &extended; - - ok(lttng_strncpy(attr.name, "channel0", sizeof(attr.name)) == 0, - "Validate channel name length"); - uchan = trace_ust_create_channel(&attr, LTTNG_DOMAIN_UST); - ok(uchan != NULL, "Create UST channel"); - - if (!uchan) { - skip(1, "UST channel is null"); - return; - } - - ok(uchan->enabled == 0 && - strncmp(uchan->name, "channel0", 8) == 0 && - uchan->name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] == '\0' && - uchan->ctx != NULL && - uchan->events != NULL && - uchan->attr.overwrite == attr.attr.overwrite, - "Validate UST channel"); - - trace_ust_destroy_channel(uchan); -} - -static void test_create_ust_event(void) -{ - struct ltt_ust_event *event; - struct lttng_event ev; - enum lttng_error_code ret; - - memset(&ev, 0, sizeof(ev)); - ok(lttng_strncpy(ev.name, get_random_string(), - LTTNG_SYMBOL_NAME_LEN) == 0, - "Validate string length"); - ev.type = LTTNG_EVENT_TRACEPOINT; - ev.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; - - ret = trace_ust_create_event(&ev, NULL, NULL, NULL, false, &event); - - ok(ret == LTTNG_OK, "Create UST event"); - - if (!event) { - skip(1, "UST event is null"); - return; - } - - ok(event->enabled == 0 && - event->attr.instrumentation == LTTNG_UST_ABI_TRACEPOINT && - strcmp(event->attr.name, ev.name) == 0 && - event->attr.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] == '\0', - "Validate UST event"); - - trace_ust_destroy_event(event); -} - -static void test_create_ust_event_exclusion(void) -{ - enum lttng_error_code ret; - struct ltt_ust_event *event; - struct lttng_event ev; - char *name; - char *random_name; - struct lttng_event_exclusion *exclusion = NULL; - struct lttng_event_exclusion *exclusion_copy = NULL; - const int exclusion_count = 2; - - memset(&ev, 0, sizeof(ev)); - - /* make a wildcarded event name */ - name = get_random_string(); - name[strlen(name) - 1] = '*'; - ok(lttng_strncpy(ev.name, name, LTTNG_SYMBOL_NAME_LEN) == 0, - "Validate string length"); - - ev.type = LTTNG_EVENT_TRACEPOINT; - ev.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; - - /* set up an exclusion set */ - exclusion = zmalloc(sizeof(*exclusion) + - LTTNG_SYMBOL_NAME_LEN * exclusion_count); - ok(exclusion != NULL, "Create UST exclusion"); - if (!exclusion) { - skip(4, "zmalloc failed"); - goto end; - } - - exclusion->count = exclusion_count; - random_name = get_random_string(); - strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 0), random_name, - LTTNG_SYMBOL_NAME_LEN); - strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 1), random_name, - LTTNG_SYMBOL_NAME_LEN); - - ret = trace_ust_create_event(&ev, NULL, NULL, exclusion, false, &event); - exclusion = NULL; - - ok(ret != LTTNG_OK, "Create UST event with identical exclusion names fails"); - - exclusion = zmalloc(sizeof(*exclusion) + - LTTNG_SYMBOL_NAME_LEN * exclusion_count); - ok(exclusion != NULL, "Create UST exclusion"); - if (!exclusion) { - skip(2, "zmalloc failed"); - goto end; - } - - exclusion_copy = zmalloc(sizeof(*exclusion) + - LTTNG_SYMBOL_NAME_LEN * exclusion_count); - if (!exclusion_copy) { - skip(2, "zmalloc failed"); - goto end; - } - - /* - * We are giving ownership of the exclusion struct to the - * trace_ust_create_event() function. Make a copy of the exclusion struct - * so we can compare it later. - */ - - exclusion->count = exclusion_count; - strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 0), - get_random_string(), LTTNG_SYMBOL_NAME_LEN); - strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 1), - get_random_string(), LTTNG_SYMBOL_NAME_LEN); - - exclusion_copy->count = exclusion_count; - strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion_copy, 0), - LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 0), LTTNG_SYMBOL_NAME_LEN); - strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion_copy, 1), - LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 1), LTTNG_SYMBOL_NAME_LEN); - - ret = trace_ust_create_event(&ev, NULL, NULL, exclusion, false, &event); - exclusion = NULL; - ok(ret == LTTNG_OK, "Create UST event with different exclusion names"); - - if (!event) { - skip(1, "UST event with exclusion is null"); - goto end; - } - - ok(event->enabled == 0 && - event->attr.instrumentation == LTTNG_UST_ABI_TRACEPOINT && - strcmp(event->attr.name, ev.name) == 0 && - event->exclusion != NULL && - event->exclusion->count == exclusion_count && - !memcmp(event->exclusion->names, exclusion_copy->names, - LTTNG_SYMBOL_NAME_LEN * exclusion_count) && - event->attr.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] == '\0', - "Validate UST event and exclusion"); - - trace_ust_destroy_event(event); -end: - free(exclusion); - free(exclusion_copy); - return; -} - - -static void test_create_ust_context(void) -{ - struct lttng_event_context ectx; - struct ltt_ust_context *uctx; - - ectx.ctx = LTTNG_EVENT_CONTEXT_VTID; - - uctx = trace_ust_create_context(&ectx); - ok(uctx != NULL, "Create UST context"); - - if (uctx) { - ok((int) uctx->ctx.ctx == LTTNG_UST_ABI_CONTEXT_VTID, - "Validate UST context"); - } else { - skip(1, "Skipping UST context validation as creation failed"); - } - free(uctx); -} - -int main(int argc, char **argv) -{ - plan_tests(NUM_TESTS); - - diag("UST data structures unit test"); - - rcu_register_thread(); - - test_create_one_ust_session(); - test_create_ust_channel(); - test_create_ust_event(); - test_create_ust_context(); - test_create_ust_event_exclusion(); - - rcu_unregister_thread(); - - return exit_status(); -} diff --git a/tests/unit/test_ust_data.cpp b/tests/unit/test_ust_data.cpp new file mode 100644 index 000000000..a6dfba429 --- /dev/null +++ b/tests/unit/test_ust_data.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2011 David Goulet + * + * SPDX-License-Identifier: GPL-2.0-only + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* This path will NEVER be created in this test */ +#define PATH1 "/tmp/.test-junk-lttng" + +#define RANDOM_STRING_LEN 11 + +/* Number of TAP tests in this file */ +#define NUM_TESTS 16 + +DEFINE_LTTNG_UST_SIGBUS_STATE(); + +static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; +static char random_string[RANDOM_STRING_LEN]; + +/* + * Return random string of 10 characters. + * Not thread-safe. + */ +static char *get_random_string(void) +{ + int i; + + for (i = 0; i < RANDOM_STRING_LEN - 1; i++) { + random_string[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + random_string[RANDOM_STRING_LEN - 1] = '\0'; + + return random_string; +} + +static void test_create_one_ust_session(void) +{ + struct ltt_ust_session *usess = + trace_ust_create_session(42); + + ok(usess != NULL, "Create UST session"); + + if (!usess) { + skip(1, "UST session is null"); + return; + } + + ok(usess->id == 42 && + usess->active == 0 && + usess->domain_global.channels != NULL && + usess->uid == 0 && + usess->gid == 0, + "Validate UST session"); + + trace_ust_destroy_session(usess); +} + +static void test_create_ust_channel(void) +{ + struct ltt_ust_channel *uchan; + struct lttng_channel attr; + struct lttng_channel_extended extended; + + memset(&attr, 0, sizeof(attr)); + memset(&extended, 0, sizeof(extended)); + attr.attr.extended.ptr = &extended; + + ok(lttng_strncpy(attr.name, "channel0", sizeof(attr.name)) == 0, + "Validate channel name length"); + uchan = trace_ust_create_channel(&attr, LTTNG_DOMAIN_UST); + ok(uchan != NULL, "Create UST channel"); + + if (!uchan) { + skip(1, "UST channel is null"); + return; + } + + ok(uchan->enabled == 0 && + strncmp(uchan->name, "channel0", 8) == 0 && + uchan->name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] == '\0' && + uchan->ctx != NULL && + uchan->events != NULL && + uchan->attr.overwrite == attr.attr.overwrite, + "Validate UST channel"); + + trace_ust_destroy_channel(uchan); +} + +static void test_create_ust_event(void) +{ + struct ltt_ust_event *event; + struct lttng_event ev; + enum lttng_error_code ret; + + memset(&ev, 0, sizeof(ev)); + ok(lttng_strncpy(ev.name, get_random_string(), + LTTNG_SYMBOL_NAME_LEN) == 0, + "Validate string length"); + ev.type = LTTNG_EVENT_TRACEPOINT; + ev.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; + + ret = trace_ust_create_event(&ev, NULL, NULL, NULL, false, &event); + + ok(ret == LTTNG_OK, "Create UST event"); + + if (!event) { + skip(1, "UST event is null"); + return; + } + + ok(event->enabled == 0 && + event->attr.instrumentation == LTTNG_UST_ABI_TRACEPOINT && + strcmp(event->attr.name, ev.name) == 0 && + event->attr.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] == '\0', + "Validate UST event"); + + trace_ust_destroy_event(event); +} + +static void test_create_ust_event_exclusion(void) +{ + enum lttng_error_code ret; + struct ltt_ust_event *event; + struct lttng_event ev; + char *name; + char *random_name; + struct lttng_event_exclusion *exclusion = NULL; + struct lttng_event_exclusion *exclusion_copy = NULL; + const int exclusion_count = 2; + + memset(&ev, 0, sizeof(ev)); + + /* make a wildcarded event name */ + name = get_random_string(); + name[strlen(name) - 1] = '*'; + ok(lttng_strncpy(ev.name, name, LTTNG_SYMBOL_NAME_LEN) == 0, + "Validate string length"); + + ev.type = LTTNG_EVENT_TRACEPOINT; + ev.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; + + /* set up an exclusion set */ + exclusion = (lttng_event_exclusion *) zmalloc(sizeof(*exclusion) + + LTTNG_SYMBOL_NAME_LEN * exclusion_count); + ok(exclusion != NULL, "Create UST exclusion"); + if (!exclusion) { + skip(4, "zmalloc failed"); + goto end; + } + + exclusion->count = exclusion_count; + random_name = get_random_string(); + strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 0), random_name, + LTTNG_SYMBOL_NAME_LEN); + strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 1), random_name, + LTTNG_SYMBOL_NAME_LEN); + + ret = trace_ust_create_event(&ev, NULL, NULL, exclusion, false, &event); + exclusion = NULL; + + ok(ret != LTTNG_OK, "Create UST event with identical exclusion names fails"); + + exclusion = (lttng_event_exclusion *) zmalloc(sizeof(*exclusion) + + LTTNG_SYMBOL_NAME_LEN * exclusion_count); + ok(exclusion != NULL, "Create UST exclusion"); + if (!exclusion) { + skip(2, "zmalloc failed"); + goto end; + } + + exclusion_copy = (lttng_event_exclusion *) zmalloc(sizeof(*exclusion) + + LTTNG_SYMBOL_NAME_LEN * exclusion_count); + if (!exclusion_copy) { + skip(2, "zmalloc failed"); + goto end; + } + + /* + * We are giving ownership of the exclusion struct to the + * trace_ust_create_event() function. Make a copy of the exclusion struct + * so we can compare it later. + */ + + exclusion->count = exclusion_count; + strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 0), + get_random_string(), LTTNG_SYMBOL_NAME_LEN); + strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 1), + get_random_string(), LTTNG_SYMBOL_NAME_LEN); + + exclusion_copy->count = exclusion_count; + strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion_copy, 0), + LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 0), LTTNG_SYMBOL_NAME_LEN); + strncpy(LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion_copy, 1), + LTTNG_EVENT_EXCLUSION_NAME_AT(exclusion, 1), LTTNG_SYMBOL_NAME_LEN); + + ret = trace_ust_create_event(&ev, NULL, NULL, exclusion, false, &event); + exclusion = NULL; + ok(ret == LTTNG_OK, "Create UST event with different exclusion names"); + + if (!event) { + skip(1, "UST event with exclusion is null"); + goto end; + } + + ok(event->enabled == 0 && + event->attr.instrumentation == LTTNG_UST_ABI_TRACEPOINT && + strcmp(event->attr.name, ev.name) == 0 && + event->exclusion != NULL && + event->exclusion->count == exclusion_count && + !memcmp(event->exclusion->names, exclusion_copy->names, + LTTNG_SYMBOL_NAME_LEN * exclusion_count) && + event->attr.name[LTTNG_UST_ABI_SYM_NAME_LEN - 1] == '\0', + "Validate UST event and exclusion"); + + trace_ust_destroy_event(event); +end: + free(exclusion); + free(exclusion_copy); + return; +} + + +static void test_create_ust_context(void) +{ + struct lttng_event_context ectx; + struct ltt_ust_context *uctx; + + ectx.ctx = LTTNG_EVENT_CONTEXT_VTID; + + uctx = trace_ust_create_context(&ectx); + ok(uctx != NULL, "Create UST context"); + + if (uctx) { + ok((int) uctx->ctx.ctx == LTTNG_UST_ABI_CONTEXT_VTID, + "Validate UST context"); + } else { + skip(1, "Skipping UST context validation as creation failed"); + } + free(uctx); +} + +int main(int argc, char **argv) +{ + plan_tests(NUM_TESTS); + + diag("UST data structures unit test"); + + rcu_register_thread(); + + test_create_one_ust_session(); + test_create_ust_channel(); + test_create_ust_event(); + test_create_ust_context(); + test_create_ust_event_exclusion(); + + rcu_unregister_thread(); + + return exit_status(); +} diff --git a/tests/utils/tap/tap.h b/tests/utils/tap/tap.h index c15909d89..84288f038 100644 --- a/tests/utils/tap/tap.h +++ b/tests/utils/tap/tap.h @@ -26,6 +26,10 @@ * SUCH DAMAGE. */ +#ifdef __cplusplus +extern "C" { +#endif + /* '## __VA_ARGS__' is a gcc'ism. C99 doesn't allow the token pasting and requires the caller to add the final comma if they've ommitted the optional arguments */ @@ -89,3 +93,7 @@ void todo_start(char *, ...); void todo_end(void); int exit_status(void); + +#ifdef __cplusplus +} +#endif