/*
- * Copyright (C) 2011 - David Goulet <david.goulet@polymtl.ca>
+ * Copyright (C) 2011 EfficiOS Inc.
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2 only,
- * as published by the Free Software Foundation.
+ * SPDX-License-Identifier: GPL-2.0-only
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _LGPL_SOURCE
* NOTES:
*
* No ltt_session.lock is taken here because those data structure are widely
- * spread across the lttng-tools code base so before caling functions below
+ * 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().
*/
/* 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.
}
/*
- * Allocate the ltt_sessions_ht_by_id HT.
+ * Allocate the ltt_sessions_ht_by_id and ltt_sessions_ht_by_name HT.
*
* The session list lock must be held.
*/
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;
}
*/
static void ltt_sessions_ht_destroy(void)
{
- if (!ltt_sessions_ht_by_id) {
- return;
+ if (ltt_sessions_ht_by_id) {
+ ht_cleanup_push(ltt_sessions_ht_by_id);
+ ltt_sessions_ht_by_id = NULL;
}
- 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.
- * If unallocated, the ltt_sessions_ht_by_id HT is allocated.
- * The session list lock must be held.
+ * 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)
{
goto end;
}
}
+
+ /* Should always be present with ltt_sessions_ht_by_id. */
+ 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 is empty.
+ * 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)
{
- int ret;
+ unsigned long count;
if (!ltt_sessions_ht_by_id) {
- ret = 1;
+ count = 0;
goto end;
}
- ret = lttng_ht_get_count(ltt_sessions_ht_by_id) ? 0 : 1;
+ assert(ltt_sessions_ht_by_name);
+
+ count = lttng_ht_get_count(ltt_sessions_ht_by_id);
+ assert(count == lttng_ht_get_count(ltt_sessions_ht_by_name));
end:
- return ret;
+ return count ? 0 : 1;
}
/*
* Remove a ltt_session from the ltt_sessions_ht_by_id.
- * If empty, the ltt_sessions_ht_by_id HT is freed.
+ * 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)
assert(ls);
assert(ltt_sessions_ht_by_id);
+ assert(ltt_sessions_ht_by_name);
iter.iter.node = &ls->node.node;
ret = lttng_ht_del(ltt_sessions_ht_by_id, &iter);
assert(!ret);
if (ltt_sessions_ht_empty()) {
- DBG("Empty ltt_sessions_ht_by_id, destroying it");
+ DBG("Empty ltt_sessions_ht_by_id/name, destroying hast tables");
ltt_sessions_ht_destroy();
}
}
CONSUMER_DST_LOCAL;
session->ust_session->current_trace_chunk = new_trace_chunk;
- if (is_local_trace) {
+ if (is_local_trace) {
enum lttng_error_code ret_error_code;
ret_error_code = ust_app_create_channel_subdirectories(
if (ret_error_code != LTTNG_OK) {
goto error;
}
- }
+ }
cds_lfht_for_each_entry(
session->ust_session->consumer->socks->ht,
&iter, socket, node.node) {
session->id, new_trace_chunk,
DEFAULT_UST_TRACE_DIR);
pthread_mutex_unlock(socket->lock);
- if (ret) {
+ if (ret) {
goto error;
- }
- }
- }
+ }
+ }
+ }
if (session->kernel_session) {
const uint64_t relayd_id =
session->kernel_session->consumer->net_seq_index;
if (ret_error_code != LTTNG_OK) {
goto error;
}
- }
+ }
cds_lfht_for_each_entry(
session->kernel_session->consumer->socks->ht,
&iter, socket, node.node) {
session->id, new_trace_chunk,
DEFAULT_KERNEL_TRACE_DIR);
pthread_mutex_unlock(socket->lock);
- if (ret) {
+ if (ret) {
goto error;
- }
- }
- }
+ }
+ }
+ }
/*
* Update local current trace chunk state last, only if all remote
if (session->kernel_session) {
session->kernel_session->current_trace_chunk = NULL;
}
- /*
+ /*
* Release references taken in the case where all references could not
* be acquired.
*/
const char *base_path;
struct lttng_directory_handle *session_output_directory = NULL;
const struct lttng_credentials session_credentials = {
- .uid = session->uid,
- .gid = session->gid,
+ .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;
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);
+ chunk_creation_ts, new_path);
if (!trace_chunk) {
goto error;
}
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);
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) {
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.
*
usess = session->ust_session;
ksess = session->kernel_session;
- /* Clean kernel session teardown, keeping data for destroy notifier. */
+ /* Clean kernel session teardown, keeping data for destroy notifier. */
kernel_destroy_session(ksess);
/* UST session teardown, keeping data for destroy notifier. */
* 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(kernel_poll_pipe[1]);
+ ret = notify_thread_pipe(the_kernel_poll_pipe[1]);
if (ret < 0) {
PERROR("write kernel poll pipe");
}
* 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);
}
}
*/
void session_destroy(struct ltt_session *session)
{
+ int ret;
+ struct lttng_ht_iter iter;
+
assert(!session->destroyed);
session->destroyed = true;
+
+ /*
+ * Remove immediately from the "session by name" hash table. Only one
+ * session is expected to exist with a given name for at any given time.
+ *
+ * Even if a session still technically exists for a little while longer,
+ * there is no point in performing action on a "destroyed" session.
+ */
+ iter.iter.node = &session->node_by_name.node;
+ ret = lttng_ht_del(ltt_sessions_ht_by_name, &iter);
+ assert(!ret);
+
session_put(session);
}
}
/*
- * Check if the UID or GID match the session. Root user has access to all
+ * Check if the UID matches the session. Root user has access to all
* sessions.
*/
-int session_access_ok(struct ltt_session *session, uid_t uid, gid_t gid)
+bool session_access_ok(struct ltt_session *session, uid_t uid)
{
assert(session);
-
- if (uid != session->uid && gid != session->gid && uid != 0) {
- return 0;
- } else {
- return 1;
- }
+ return (uid == session->uid) || uid == 0;
}
/*
}
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;
+}