From 1545fd17478b6ebbfe03df99ef5bc91bb117227f Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Tue, 19 Feb 2019 16:44:36 -0500 Subject: [PATCH] Add the trace chunk and trace chunk registry interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit A trace chunk is a set of stream files. It maps to the user-visible concept of "trace archive chunks" produced following a tracing session rotation. The concept of a "chunk" is introduced to make it possible to associate a group of stream files together, store common properties (e.g. the epoch, base path, list of files, credentials, etc.), and perform an action once all files have been closed/released. The "trace chunk" interface is to be used by the session, consumer, and relay daemons in different ways, through the OWNER or USER roles. The lttng_dynamic_pointer_array, lttng_dynamic_array, and optional utils are added since they are used by the trace chunk implementation. Signed-off-by: Jérémie Galarneau --- src/common/Makefile.am | 4 +- src/common/dynamic-array.c | 88 ++ src/common/dynamic-array.h | 139 ++++ src/common/optional.h | 90 +++ src/common/trace-chunk-registry.h | 98 +++ src/common/trace-chunk.c | 1233 +++++++++++++++++++++++++++++ src/common/trace-chunk.h | 168 ++++ 7 files changed, 1819 insertions(+), 1 deletion(-) create mode 100644 src/common/dynamic-array.c create mode 100644 src/common/dynamic-array.h create mode 100644 src/common/optional.h create mode 100644 src/common/trace-chunk-registry.h create mode 100644 src/common/trace-chunk.c create mode 100644 src/common/trace-chunk.h diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 1590820d5..4b1484e8b 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -28,7 +28,9 @@ libcommon_la_SOURCES = error.h error.c utils.c utils.h runas.h runas.c \ location.c \ waiter.h waiter.c \ userspace-probe.c event.c time.c \ - session-descriptor.c credentials.h + session-descriptor.c credentials.h \ + trace-chunk.c trace-chunk.h trace-chunk-registry.h \ + dynamic-array.h dynamic-array.c optional.h if HAVE_ELF_H libcommon_la_SOURCES += lttng-elf.h lttng-elf.c diff --git a/src/common/dynamic-array.c b/src/common/dynamic-array.c new file mode 100644 index 000000000..69c9c614d --- /dev/null +++ b/src/common/dynamic-array.c @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +LTTNG_HIDDEN +void lttng_dynamic_array_init(struct lttng_dynamic_array *array, + size_t element_size) +{ + lttng_dynamic_buffer_init(&array->buffer); + array->element_size = element_size; +} + +LTTNG_HIDDEN +int lttng_dynamic_array_add_element(struct lttng_dynamic_array *array, + const void *element) +{ + int ret; + + if (!array || !element) { + ret = -1; + goto end; + } + + ret = lttng_dynamic_buffer_append(&array->buffer, element, + array->element_size); + if (ret) { + goto end; + } + array->size++; +end: + return ret; +} + +LTTNG_HIDDEN +void lttng_dynamic_array_reset(struct lttng_dynamic_array *array, + lttng_dynamic_array_element_destructor destructor) +{ + if (destructor) { + size_t i; + + for (i = 0; i < lttng_dynamic_array_get_count(array); i++) { + destructor(lttng_dynamic_array_get_element(array, i)); + } + } + + lttng_dynamic_buffer_reset(&array->buffer); + array->size = 0; +} + +LTTNG_HIDDEN +void lttng_dynamic_pointer_array_init( + struct lttng_dynamic_pointer_array *array) +{ + lttng_dynamic_array_init(&array->array, sizeof(void *)); +} + +/* Release any memory used by the dynamic array. */ +LTTNG_HIDDEN +void lttng_dynamic_pointer_array_reset( + struct lttng_dynamic_pointer_array *array, + lttng_dynamic_pointer_array_destructor destructor) +{ + if (destructor) { + size_t i, count = lttng_dynamic_pointer_array_get_count(array); + + for (i = 0; i < count; i++) { + void *ptr = lttng_dynamic_pointer_array_get_pointer( + array, i); + destructor(ptr); + } + } + lttng_dynamic_array_reset(&array->array, NULL); +} diff --git a/src/common/dynamic-array.h b/src/common/dynamic-array.h new file mode 100644 index 000000000..a5e4f7fb5 --- /dev/null +++ b/src/common/dynamic-array.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2019 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LTTNG_DYNAMIC_ARRAY_H +#define LTTNG_DYNAMIC_ARRAY_H + +#include +#include + +struct lttng_dynamic_array { + struct lttng_dynamic_buffer buffer; + size_t element_size; + size_t size; +}; + +struct lttng_dynamic_pointer_array { + struct lttng_dynamic_array array; +}; + +typedef void (*lttng_dynamic_array_element_destructor)(void *element); +typedef void (*lttng_dynamic_pointer_array_destructor)(void *ptr); + +/* + * Initialize a resizable array of fixed-size elements. This performs no + * allocation and can't fail. + */ +LTTNG_HIDDEN +void lttng_dynamic_array_init(struct lttng_dynamic_array *array, + size_t element_size); + +/* + * Returns the number of elements in the dynamic array. + */ +static inline +size_t lttng_dynamic_array_get_count( + const struct lttng_dynamic_array *array) +{ + return array->size; +} + +/* + * Returns a pointer to the element. Mutating operations on the array invalidate + * the returned pointer. + */ +static inline +void *lttng_dynamic_array_get_element(const struct lttng_dynamic_array *array, + size_t element_index) +{ + assert(element_index < array->size); + return array->buffer.data + (element_index * array->element_size); +} + +/* + * Add an element to the end of a dynamic array. The array's element count is + * increased by one and its underlying capacity is adjusted automatically. + * + * element is a pointer to the element to add (copy) to the array. + */ +LTTNG_HIDDEN +int lttng_dynamic_array_add_element(struct lttng_dynamic_array *array, + const void *element); + +/* Release any memory used by the dynamic array. */ +LTTNG_HIDDEN +void lttng_dynamic_array_reset(struct lttng_dynamic_array *array, + lttng_dynamic_array_element_destructor destructor); + + +/* + * Specialization of lttng_dynamic_array for pointers. This utility + * is built under the assumption that pointer sizes are equal + * for all data types on supported architectures. Revisit this in the event + * of a port to an Harvard architecture. + */ + +/* + * Initialize a resizable array of fixed-size elements. This performs no + * allocation and can't fail. + */ +LTTNG_HIDDEN +void lttng_dynamic_pointer_array_init( + struct lttng_dynamic_pointer_array *array); + +/* + * Returns the number of pointers in the dynamic pointer array. + */ +static inline +size_t lttng_dynamic_pointer_array_get_count( + const struct lttng_dynamic_pointer_array *array) +{ + return lttng_dynamic_array_get_count(&array->array); +} + +/* + * Returns a pointer to the element. Mutating operations on the array invalidate + * the returned pointer. + */ +static inline +void *lttng_dynamic_pointer_array_get_pointer( + const struct lttng_dynamic_pointer_array *array, size_t index) +{ + void **element = lttng_dynamic_array_get_element(&array->array, index); + + return *element; +} + +/* + * Add a pointer to the end of a dynamic pointer array. The array's element + * count is increased by one and its underlying capacity is adjusted + * automatically. + */ +static inline +int lttng_dynamic_pointer_array_add_pointer( + struct lttng_dynamic_pointer_array *array, void *pointer) +{ + return lttng_dynamic_array_add_element(&array->array, &pointer); +} + +/* Release any memory used by the dynamic array. */ +LTTNG_HIDDEN +void lttng_dynamic_pointer_array_reset( + struct lttng_dynamic_pointer_array *array, + lttng_dynamic_pointer_array_destructor destructor); + +#endif /* LTTNG_DYNAMIC_ARRAY_H */ diff --git a/src/common/optional.h b/src/common/optional.h new file mode 100644 index 000000000..1da5fda5d --- /dev/null +++ b/src/common/optional.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2019 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef LTTNG_OPTIONAL_H +#define LTTNG_OPTIONAL_H + +#include +#include + +/* + * Define wrapper structure representing an optional value. + * + * This macro defines an "is_set" boolean field that must be checked + * when accessing the optional field. This "is_set" field provides + * the semantics that would be expected of a typical "raw pointer" field + * which would be checked for NULL. + * + * Prefer using this macro where "special" values would be used, e.g. + * -1ULL for uint64_t types. + * + * LTTNG_OPTIONAL should be combined with the LTTNG_PACKED macro when + * used for IPC / network communication. + * + * Declaration example: + * struct my_struct { + * int a; + * LTTNG_OPTIONAL(int, b); + * }; + * + * Usage example: + * struct my_struct foo = LTTNG_OPTIONAL_INIT; + * + * LTTNG_OPTIONAL_SET(&foo.b, 42); + * if (foo.b.is_set) { + * printf("%d", foo.b.value); + * } + * + * LTTNG_OPTIONAL_UNSET(&foo.b); + */ +#define LTTNG_OPTIONAL(type) \ + struct { \ + uint8_t is_set; \ + type value; \ + } + +/* + * This macro is available as a 'convenience' to allow sites that assume + * an optional value is set to assert() that it is set when accessing it. + * + * Since this returns the 'optional' by value, it is not suitable for all + * wrapped optional types. It is meant to be used with PODs. + */ +#define LTTNG_OPTIONAL_GET(optional) \ + ({ \ + assert(optional.is_set); \ + optional.value; \ + }) + +/* + * Initialize an optional field. + * + * The wrapped field is set to the value it would gave if it had static storage + * duration. + */ +#define LTTNG_OPTIONAL_INIT { .is_set = 0 } + +/* Set the value of an optional field. */ +#define LTTNG_OPTIONAL_SET(field_ptr, val) \ + (field_ptr)->value = val; \ + (field_ptr)->is_set = 1; + +/* Put an optional field in the "unset" (NULL-ed) state. */ +#define LTTNG_OPTIONAL_UNSET(field_ptr) \ + (field_ptr)->is_set = 0; + +#endif /* LTTNG_OPTIONAL_H */ diff --git a/src/common/trace-chunk-registry.h b/src/common/trace-chunk-registry.h new file mode 100644 index 000000000..721453a2e --- /dev/null +++ b/src/common/trace-chunk-registry.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2019 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LTTNG_TRACE_CHUNK_REGISTRY_H +#define LTTNG_TRACE_CHUNK_REGISTRY_H + +#include +#include +#include +#include +#include + +struct lttng_trace_chunk_registry; + +/* + * Create an lttng_trace_chunk registry. + * + * A trace chunk registry maintains an association between a + * (session_id, chunk_id) tuple and a trace chunk object. The chunk_id can + * be "unset" in the case of an anonymous trace chunk. + * + * Note that a trace chunk registry holds no ownership of its trace + * chunks. Trace chunks are unpublished when their last reference is released. + * See the documentation of lttng_trace_chunk. + * + * Returns a trace chunk registry on success, NULL on error. + * + * Note that a trace chunk registry may only be accessed by an RCU thread. + */ +LTTNG_HIDDEN +struct lttng_trace_chunk_registry *lttng_trace_chunk_registry_create(void); + +/* + * Destroy an lttng trace chunk registry. The registry must be emptied + * (i.e. all references to the trace chunks it contains must be released) before + * it is destroyed. + */ +LTTNG_HIDDEN +void lttng_trace_chunk_registry_destroy( + struct lttng_trace_chunk_registry *registry); + +/* + * Publish a trace chunk for a given session id. + * A reference is acquired on behalf of the caller. + * + * The trace chunk that is returned is the published version of the trace + * chunk. The chunk provided should be discarded on success and it's + * published version used in its place. + * + * See the documentation of lttng_trace_chunk for more information on + * the usage of the various parameters. + * + * Returns an lttng_trace_chunk on success, NULL on error. + */ +LTTNG_HIDDEN +struct lttng_trace_chunk *lttng_trace_chunk_registry_publish_chunk( + struct lttng_trace_chunk_registry *registry, + uint64_t session_id, struct lttng_trace_chunk *chunk); + +/* + * Look-up a trace chunk by session_id and chunk_id. + * A reference is acquired on behalf of the caller. + * + * Returns an lttng_trace_chunk on success, NULL if the chunk does not exist. + */ +LTTNG_HIDDEN +struct lttng_trace_chunk * +lttng_trace_chunk_registry_find_chunk( + const struct lttng_trace_chunk_registry *registry, + uint64_t session_id, uint64_t chunk_id); + +/* + * Look-up an anonymous trace chunk by session_id. + * A reference is acquired on behalf of the caller. + * + * Returns an lttng_trace_chunk on success, NULL if the chunk does not exist. + */ +LTTNG_HIDDEN +struct lttng_trace_chunk * +lttng_trace_chunk_registry_find_anonymous_chunk( + const struct lttng_trace_chunk_registry *registry, + uint64_t session_id); + +#endif /* LTTNG_TRACE_CHUNK_REGISTRY_H */ diff --git a/src/common/trace-chunk.c b/src/common/trace-chunk.c new file mode 100644 index 000000000..a15f0ddaa --- /dev/null +++ b/src/common/trace-chunk.c @@ -0,0 +1,1233 @@ +/* + * Copyright (C) 2019 - Jérémie Galarneau + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * Two ISO 8601-compatible timestamps, separated by a hypen, followed an + * index, i.e. --. + */ +#define GENERATED_CHUNK_NAME_LEN (2 * sizeof("YYYYmmddTHHMMSS+HHMM") + MAX_INT_DEC_LEN(uint64_t)) +#define DIR_CREATION_MODE (S_IRWXU | S_IRWXG) + +enum trace_chunk_mode { + TRACE_CHUNK_MODE_USER, + TRACE_CHUNK_MODE_OWNER, +}; + +/* + * Callback to invoke on release of a trace chunk. Note that there is no + * need to 'lock' the trace chunk during the execution of these callbacks + * since only one thread may access a chunk during its destruction (the last + * to release its reference to the chunk). + */ +typedef void (*chunk_close_command)(struct lttng_trace_chunk *trace_chunk); + +/* Move a completed trace chunk to the 'completed' trace archive folder. */ +static +void lttng_trace_chunk_move_to_completed(struct lttng_trace_chunk *trace_chunk); + +struct chunk_credentials { + bool use_current_user; + struct lttng_credentials user; +}; + +struct lttng_trace_chunk { + pthread_mutex_t lock; + struct urcu_ref ref; + LTTNG_OPTIONAL(enum trace_chunk_mode) mode; + /* + * First-level directories created within the trace chunk. + * Elements are of type 'char *'. + */ + struct lttng_dynamic_pointer_array top_level_directories; + /* Is contained within an lttng_trace_chunk_registry_element? */ + bool in_registry_element; + bool name_overriden; + char *name; + /* An unset id means the chunk is anonymous. */ + LTTNG_OPTIONAL(uint64_t) id; + LTTNG_OPTIONAL(time_t) timestamp_creation; + LTTNG_OPTIONAL(time_t) timestamp_close; + LTTNG_OPTIONAL(struct chunk_credentials) credentials; + LTTNG_OPTIONAL(struct lttng_directory_handle) session_output_directory; + LTTNG_OPTIONAL(struct lttng_directory_handle) chunk_directory; + LTTNG_OPTIONAL(enum lttng_trace_chunk_command_type) close_command; +}; + +/* A trace chunk is uniquely identified by its (session id, chunk id) tuple. */ +struct lttng_trace_chunk_registry_element { + uint64_t session_id; + struct lttng_trace_chunk chunk; + /* Weak and only set when added. */ + struct lttng_trace_chunk_registry *registry; + struct cds_lfht_node trace_chunk_registry_ht_node; + /* call_rcu delayed reclaim. */ + struct rcu_head rcu_node; +}; + +struct lttng_trace_chunk_registry { + struct cds_lfht *ht; +}; + +const char *close_command_names[] = { + [LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED] = + "move to completed chunk folder", +}; + +chunk_close_command close_command_funcs[] = { + [LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED] = + lttng_trace_chunk_move_to_completed, +}; + +static +bool lttng_trace_chunk_registry_element_equals( + const struct lttng_trace_chunk_registry_element *a, + const struct lttng_trace_chunk_registry_element *b) +{ + if (a->session_id != b->session_id) { + goto not_equal; + } + if (a->chunk.id.is_set != b->chunk.id.is_set) { + goto not_equal; + } + if (a->chunk.id.is_set && a->chunk.id.value != b->chunk.id.value) { + goto not_equal; + } + return true; +not_equal: + return false; +} + +static +int lttng_trace_chunk_registry_element_match(struct cds_lfht_node *node, + const void *key) +{ + const struct lttng_trace_chunk_registry_element *element_a, *element_b; + + element_a = (const struct lttng_trace_chunk_registry_element *) key; + element_b = caa_container_of(node, typeof(*element_b), + trace_chunk_registry_ht_node); + return lttng_trace_chunk_registry_element_equals(element_a, element_b); +} + +static +unsigned long lttng_trace_chunk_registry_element_hash( + const struct lttng_trace_chunk_registry_element *element) +{ + unsigned long hash = hash_key_u64(&element->session_id, + lttng_ht_seed); + + if (element->chunk.id.is_set) { + hash ^= hash_key_u64(&element->chunk.id.value, lttng_ht_seed); + } + + return hash; +} + +static +char *generate_chunk_name(uint64_t chunk_id, time_t creation_timestamp, + const time_t *close_timestamp) +{ + int ret = 0; + char *new_name= NULL; + char start_datetime[sizeof("YYYYmmddTHHMMSS+HHMM")] = {}; + char end_datetime_suffix[sizeof("-YYYYmmddTHHMMSS+HHMM")] = {}; + + ret = time_to_iso8601_str( + creation_timestamp, + start_datetime, sizeof(start_datetime)); + if (ret) { + ERR("Failed to format trace chunk start date time"); + goto error; + } + if (close_timestamp) { + *end_datetime_suffix = '-'; + ret = time_to_iso8601_str( + *close_timestamp, + end_datetime_suffix + 1, + sizeof(end_datetime_suffix)); + if (ret) { + ERR("Failed to format trace chunk end date time"); + goto error; + } + } + new_name = zmalloc(GENERATED_CHUNK_NAME_LEN); + if (!new_name) { + ERR("Failed to allocate buffer for automatically-generated trace chunk name"); + goto error; + } + ret = snprintf(new_name, GENERATED_CHUNK_NAME_LEN, "%s%s-%" PRIu64, + start_datetime, end_datetime_suffix, chunk_id); + if (ret >= GENERATED_CHUNK_NAME_LEN || ret == -1) { + ERR("Failed to format trace chunk name"); + goto error; + } + + return new_name; +error: + free(new_name); + return NULL; +} + +static +void lttng_trace_chunk_init(struct lttng_trace_chunk *chunk) +{ + urcu_ref_init(&chunk->ref); + pthread_mutex_init(&chunk->lock, NULL); + lttng_dynamic_pointer_array_init(&chunk->top_level_directories); +} + +static +void lttng_trace_chunk_fini(struct lttng_trace_chunk *chunk) +{ + if (chunk->session_output_directory.is_set) { + lttng_directory_handle_fini( + &chunk->session_output_directory.value); + } + if (chunk->chunk_directory.is_set) { + lttng_directory_handle_fini(&chunk->chunk_directory.value); + } + free(chunk->name); + chunk->name = NULL; + lttng_dynamic_pointer_array_reset(&chunk->top_level_directories, free); + pthread_mutex_destroy(&chunk->lock); +} + +static +struct lttng_trace_chunk *lttng_trace_chunk_allocate(void) +{ + struct lttng_trace_chunk *chunk = NULL; + + chunk = zmalloc(sizeof(*chunk)); + if (!chunk) { + ERR("Failed to allocate trace chunk"); + goto end; + } + lttng_trace_chunk_init(chunk); +end: + return chunk; +} + +LTTNG_HIDDEN +struct lttng_trace_chunk *lttng_trace_chunk_create_anonymous(void) +{ + DBG("Creating anonymous trace chunk"); + return lttng_trace_chunk_allocate(); +} + +LTTNG_HIDDEN +struct lttng_trace_chunk *lttng_trace_chunk_create( + uint64_t chunk_id, time_t chunk_creation_time) +{ + struct lttng_trace_chunk *chunk; + char chunk_creation_datetime_buf[16] = {}; + const char *chunk_creation_datetime_str = "(formatting error)"; + struct tm timeinfo_buf, *timeinfo; + + timeinfo = localtime_r(&chunk_creation_time, &timeinfo_buf); + if (timeinfo) { + size_t strftime_ret; + + /* Don't fail because of this; it is only used for logging. */ + strftime_ret = strftime(chunk_creation_datetime_buf, + sizeof(chunk_creation_datetime_buf), + "%Y%m%d-%H%M%S", timeinfo); + if (strftime_ret) { + chunk_creation_datetime_str = + chunk_creation_datetime_buf; + } + } + + DBG("Creating trace chunk: chunk_id = %" PRIu64 ", creation time = %s", + chunk_id, chunk_creation_datetime_str); + chunk = lttng_trace_chunk_allocate(); + if (!chunk) { + goto end; + } + + LTTNG_OPTIONAL_SET(&chunk->id, chunk_id); + LTTNG_OPTIONAL_SET(&chunk->timestamp_creation, chunk_creation_time); + if (chunk_id != 0) { + chunk->name = generate_chunk_name(chunk_id, + chunk_creation_time, NULL); + if (!chunk->name) { + ERR("Failed to allocate trace chunk name storage"); + goto error; + } + } + + DBG("Chunk name set to \"%s\"", chunk->name ? : "(none)"); +end: + return chunk; +error: + lttng_trace_chunk_put(chunk); + return NULL; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_id( + struct lttng_trace_chunk *chunk, uint64_t *id) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + pthread_mutex_lock(&chunk->lock); + if (chunk->id.is_set) { + *id = chunk->id.value; + } else { + status = LTTNG_TRACE_CHUNK_STATUS_NONE; + } + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_creation_timestamp( + struct lttng_trace_chunk *chunk, time_t *creation_ts) + +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + pthread_mutex_lock(&chunk->lock); + if (chunk->timestamp_creation.is_set) { + *creation_ts = chunk->timestamp_creation.value; + } else { + status = LTTNG_TRACE_CHUNK_STATUS_NONE; + } + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_close_timestamp( + struct lttng_trace_chunk *chunk, time_t *close_ts) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + pthread_mutex_lock(&chunk->lock); + if (chunk->timestamp_close.is_set) { + *close_ts = chunk->timestamp_close.value; + } else { + status = LTTNG_TRACE_CHUNK_STATUS_NONE; + } + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_close_timestamp( + struct lttng_trace_chunk *chunk, time_t close_ts) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + pthread_mutex_lock(&chunk->lock); + if (!chunk->timestamp_creation.is_set) { + ERR("Failed to set trace chunk close timestamp: creation timestamp is unset"); + status = LTTNG_TRACE_CHUNK_STATUS_INVALID_OPERATION; + goto end; + } + if (chunk->timestamp_creation.value > close_ts) { + ERR("Failed to set trace chunk close timestamp: close timestamp is before creation timestamp"); + status = LTTNG_TRACE_CHUNK_STATUS_INVALID_ARGUMENT; + goto end; + } + LTTNG_OPTIONAL_SET(&chunk->timestamp_close, close_ts); + free(chunk->name); + chunk->name = generate_chunk_name(LTTNG_OPTIONAL_GET(chunk->id), + LTTNG_OPTIONAL_GET(chunk->timestamp_creation), + &close_ts); + if (!chunk->name) { + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + } +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_name( + struct lttng_trace_chunk *chunk, const char **name, + bool *name_overriden) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + pthread_mutex_lock(&chunk->lock); + if (name_overriden) { + *name_overriden = chunk->name_overriden; + } + if (!chunk->name) { + status = LTTNG_TRACE_CHUNK_STATUS_NONE; + goto end; + } + *name = chunk->name; +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_override_name( + struct lttng_trace_chunk *chunk, const char *name) + +{ + char *new_name; + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + if (!name || !*name || strnlen(name, LTTNG_NAME_MAX) == LTTNG_NAME_MAX) { + ERR("Attempted to set an invalid name on a trace chunk: name = %s", + name ? : "NULL"); + status = LTTNG_TRACE_CHUNK_STATUS_INVALID_ARGUMENT; + goto end; + } + + pthread_mutex_lock(&chunk->lock); + if (!chunk->id.is_set) { + ERR("Attempted to set an override name on an anonymous trace chunk: name = %s", + name); + status = LTTNG_TRACE_CHUNK_STATUS_INVALID_OPERATION; + goto end_unlock; + } + new_name = strdup(name); + if (!new_name) { + ERR("Failed to allocate new trace chunk name"); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end_unlock; + } + free(chunk->name); + chunk->name = new_name; + chunk->name_overriden = true; +end_unlock: + pthread_mutex_unlock(&chunk->lock); +end: + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_credentials( + struct lttng_trace_chunk *chunk, + struct lttng_credentials *credentials) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + pthread_mutex_lock(&chunk->lock); + if (chunk->credentials.is_set) { + if (chunk->credentials.value.use_current_user) { + credentials->uid = geteuid(); + credentials->gid = getegid(); + } else { + *credentials = chunk->credentials.value.user; + } + } else { + status = LTTNG_TRACE_CHUNK_STATUS_NONE; + } + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_credentials( + struct lttng_trace_chunk *chunk, + const struct lttng_credentials *user_credentials) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + const struct chunk_credentials credentials = { + .user = *user_credentials, + .use_current_user = false, + }; + + pthread_mutex_lock(&chunk->lock); + if (chunk->credentials.is_set) { + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + LTTNG_OPTIONAL_SET(&chunk->credentials, credentials); +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_credentials_current_user( + struct lttng_trace_chunk *chunk) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + const struct chunk_credentials credentials = { + .use_current_user = true, + }; + + pthread_mutex_lock(&chunk->lock); + if (chunk->credentials.is_set) { + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + LTTNG_OPTIONAL_SET(&chunk->credentials, credentials); +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_as_owner( + struct lttng_trace_chunk *chunk, + struct lttng_directory_handle *session_output_directory) +{ + int ret; + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + struct lttng_directory_handle chunk_directory_handle; + + pthread_mutex_lock(&chunk->lock); + if (chunk->mode.is_set) { + status = LTTNG_TRACE_CHUNK_STATUS_INVALID_OPERATION; + goto end; + } + if (!chunk->credentials.is_set) { + /* + * Fatal error, credentials must be set before a + * directory is created. + */ + ERR("Credentials of trace chunk are unset: refusing to set session output directory"); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + + if (chunk->name) { + /* + * A nameless chunk does not need its own output directory. + * The session's output directory will be used. + */ + ret = lttng_directory_handle_create_subdirectory_as_user( + session_output_directory, + chunk->name, + DIR_CREATION_MODE, + !chunk->credentials.value.use_current_user ? + &chunk->credentials.value.user : NULL); + if (ret) { + PERROR("Failed to create chunk output directory \"%s\"", + chunk->name); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + } + ret = lttng_directory_handle_init_from_handle(&chunk_directory_handle, + chunk->name, + session_output_directory); + if (ret) { + /* The function already logs on all error paths. */ + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + LTTNG_OPTIONAL_SET(&chunk->session_output_directory, + lttng_directory_handle_move(session_output_directory)); + LTTNG_OPTIONAL_SET(&chunk->chunk_directory, + lttng_directory_handle_move(&chunk_directory_handle)); + LTTNG_OPTIONAL_SET(&chunk->mode, TRACE_CHUNK_MODE_OWNER); +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_as_user( + struct lttng_trace_chunk *chunk, + struct lttng_directory_handle *chunk_directory) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + pthread_mutex_lock(&chunk->lock); + if (chunk->mode.is_set) { + status = LTTNG_TRACE_CHUNK_STATUS_INVALID_OPERATION; + goto end; + } + if (!chunk->credentials.is_set) { + ERR("Credentials of trace chunk are unset: refusing to set chunk output directory"); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + LTTNG_OPTIONAL_SET(&chunk->chunk_directory, + lttng_directory_handle_move(chunk_directory)); + LTTNG_OPTIONAL_SET(&chunk->mode, TRACE_CHUNK_MODE_USER); +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_chunk_directory_handle( + struct lttng_trace_chunk *chunk, + const struct lttng_directory_handle **handle) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + pthread_mutex_lock(&chunk->lock); + if (!chunk->chunk_directory.is_set) { + status = LTTNG_TRACE_CHUNK_STATUS_NONE; + goto end; + } + + *handle = &chunk->chunk_directory.value; +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + +/* Add a top-level directory to the trace chunk if it was previously unknown. */ +static +int add_top_level_directory_unique(struct lttng_trace_chunk *chunk, + const char *new_path) +{ + int ret = 0; + bool found = false; + size_t i, count = lttng_dynamic_pointer_array_get_count( + &chunk->top_level_directories); + const char *new_path_separator_pos = strchr(new_path, '/'); + const ptrdiff_t new_path_top_level_len = new_path_separator_pos ? + new_path_separator_pos - new_path : strlen(new_path); + + for (i = 0; i < count; i++) { + const char *path = lttng_dynamic_pointer_array_get_pointer( + &chunk->top_level_directories, i); + const ptrdiff_t path_top_level_len = strlen(path); + + if (path_top_level_len != new_path_top_level_len) { + continue; + } + if (!strncmp(path, new_path, path_top_level_len)) { + found = true; + break; + } + } + + if (!found) { + char *copy = strndup(new_path, new_path_top_level_len); + + DBG("Adding new top-level directory \"%s\" to trace chunk \"%s\"", + new_path, chunk->name ? : "(unnamed)"); + if (!copy) { + PERROR("Failed to copy path"); + ret = -1; + goto end; + } + ret = lttng_dynamic_pointer_array_add_pointer( + &chunk->top_level_directories, copy); + if (ret) { + ERR("Allocation failure while adding top-level directory entry to a trace chunk"); + free(copy); + goto end; + } + } +end: + return ret; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_create_subdirectory( + struct lttng_trace_chunk *chunk, + const char *path) +{ + int ret; + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + DBG("Creating trace chunk subdirectory \"%s\"", path); + pthread_mutex_lock(&chunk->lock); + if (!chunk->credentials.is_set) { + /* + * Fatal error, credentials must be set before a + * directory is created. + */ + ERR("Credentials of trace chunk are unset: refusing to create subdirectory \"%s\"", + path); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + if (!chunk->mode.is_set || + chunk->mode.value != TRACE_CHUNK_MODE_OWNER) { + ERR("Attempted to create trace chunk subdirectory \"%s\" through a non-owner chunk", + path); + status = LTTNG_TRACE_CHUNK_STATUS_INVALID_OPERATION; + goto end; + } + if (!chunk->chunk_directory.is_set) { + ERR("Attempted to create trace chunk subdirectory \"%s\" before setting the chunk output directory", + path); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + if (*path == '/') { + ERR("Refusing to create absolute trace chunk directory \"%s\"", + path); + status = LTTNG_TRACE_CHUNK_STATUS_INVALID_ARGUMENT; + goto end; + } + ret = lttng_directory_handle_create_subdirectory_recursive_as_user( + &chunk->chunk_directory.value, path, + DIR_CREATION_MODE, + chunk->credentials.value.use_current_user ? + NULL : &chunk->credentials.value.user); + if (ret) { + PERROR("Failed to create trace chunk subdirectory \"%s\"", + path); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + ret = add_top_level_directory_unique(chunk, path); + if (ret) { + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_open_file( + struct lttng_trace_chunk *chunk, const char *file_path, + int flags, mode_t mode, int *out_fd) +{ + int ret; + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + DBG("Opening trace chunk file \"%s\"", file_path); + pthread_mutex_lock(&chunk->lock); + if (!chunk->credentials.is_set) { + /* + * Fatal error, credentials must be set before a + * file is created. + */ + ERR("Credentials of trace chunk are unset: refusing to open file \"%s\"", + file_path); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + if (!chunk->chunk_directory.is_set) { + ERR("Attempted to open trace chunk file \"%s\" before setting the chunk output directory", + file_path); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + ret = lttng_directory_handle_open_file_as_user( + &chunk->chunk_directory.value, file_path, flags, mode, + chunk->credentials.value.use_current_user ? + NULL : &chunk->credentials.value.user); + if (ret < 0) { + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + *out_fd = ret; +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + +LTTNG_HIDDEN +int lttng_trace_chunk_unlink_file(struct lttng_trace_chunk *chunk, + const char *file_path) +{ + int ret; + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + DBG("Unlinking trace chunk file \"%s\"", file_path); + pthread_mutex_lock(&chunk->lock); + if (!chunk->credentials.is_set) { + /* + * Fatal error, credentials must be set before a + * directory is created. + */ + ERR("Credentials of trace chunk are unset: refusing to unlink file \"%s\"", + file_path); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + if (!chunk->chunk_directory.is_set) { + ERR("Attempted to unlink trace chunk file \"%s\" before setting the chunk output directory", + file_path); + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } + ret = lttng_directory_handle_unlink_file_as_user( + &chunk->chunk_directory.value, file_path, + chunk->credentials.value.use_current_user ? + NULL : &chunk->credentials.value.user); + if (ret < 0) { + status = LTTNG_TRACE_CHUNK_STATUS_ERROR; + goto end; + } +end: + pthread_mutex_unlock(&chunk->lock); + return status; +} + +static +void lttng_trace_chunk_move_to_completed(struct lttng_trace_chunk *trace_chunk) +{ + int ret; + char *directory_to_rename = NULL; + bool free_directory_to_rename = false; + const int session_dirfd = + trace_chunk->session_output_directory.value.dirfd; + char *archived_chunk_name = NULL; + const uint64_t chunk_id = LTTNG_OPTIONAL_GET(trace_chunk->id); + const time_t creation_timestamp = + LTTNG_OPTIONAL_GET(trace_chunk->timestamp_creation); + const time_t close_timestamp = + LTTNG_OPTIONAL_GET(trace_chunk->timestamp_close); + LTTNG_OPTIONAL(struct lttng_directory_handle) archived_chunks_directory; + + assert(trace_chunk->mode.is_set); + assert(trace_chunk->mode.value == TRACE_CHUNK_MODE_OWNER); + assert(!trace_chunk->name_overriden); + + /* + * The fist trace chunk of a session is directly output to the + * session's output folder. In this case, the top level directories + * must be moved to a temporary folder before that temporary directory + * is renamed to match the chunk's name. + */ + if (chunk_id == 0) { + struct lttng_directory_handle temporary_rename_directory; + size_t i, count = lttng_dynamic_pointer_array_get_count( + &trace_chunk->top_level_directories); + + ret = lttng_directory_handle_create_subdirectory_as_user( + &trace_chunk->session_output_directory.value, + DEFAULT_TEMPORARY_CHUNK_RENAME_DIRECTORY, + DIR_CREATION_MODE, + !trace_chunk->credentials.value.use_current_user ? + &trace_chunk->credentials.value.user : NULL); + if (ret) { + PERROR("Failed to create temporary trace chunk rename directory \"%s\"", + DEFAULT_TEMPORARY_CHUNK_RENAME_DIRECTORY); + } + + ret = lttng_directory_handle_init_from_handle(&temporary_rename_directory, + DEFAULT_TEMPORARY_CHUNK_RENAME_DIRECTORY, + &trace_chunk->session_output_directory.value); + if (ret) { + ERR("Failed to get handle to temporary trace chunk rename directory"); + goto end; + } + + for (i = 0; i < count; i++) { + const int temp_dirfd = temporary_rename_directory.dirfd; + const char *top_level_name = + lttng_dynamic_pointer_array_get_pointer( + &trace_chunk->top_level_directories, i); + + /* + * FIXME replace renamat() use by directory handle + * wrapper for non-POSIX 2008 systems. + */ + ret = renameat(session_dirfd, top_level_name, + temp_dirfd, top_level_name); + if (ret) { + PERROR("Failed to move \"%s\" to temporary trace chunk rename directory", + top_level_name); + lttng_directory_handle_fini( + &temporary_rename_directory); + goto end; + } + } + lttng_directory_handle_fini(&temporary_rename_directory); + directory_to_rename = DEFAULT_TEMPORARY_CHUNK_RENAME_DIRECTORY; + free_directory_to_rename = false; + } else { + directory_to_rename = generate_chunk_name(chunk_id, + creation_timestamp, NULL); + if (!directory_to_rename) { + ERR("Failed to generate initial trace chunk name while renaming trace chunk"); + } + free_directory_to_rename = true; + } + + archived_chunk_name = generate_chunk_name(chunk_id, creation_timestamp, + &close_timestamp); + if (!archived_chunk_name) { + ERR("Failed to generate archived trace chunk name while renaming trace chunk"); + goto end; + } + + ret = lttng_directory_handle_create_subdirectory_as_user( + &trace_chunk->session_output_directory.value, + DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY, + DIR_CREATION_MODE, + !trace_chunk->credentials.value.use_current_user ? + &trace_chunk->credentials.value.user : + NULL); + if (ret) { + PERROR("Failed to create \"" DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY + "\" directory for archived trace chunks"); + goto end; + } + + ret = lttng_directory_handle_init_from_handle( + &archived_chunks_directory.value, + DEFAULT_ARCHIVED_TRACE_CHUNKS_DIRECTORY, + &trace_chunk->session_output_directory.value); + if (ret) { + PERROR("Failed to get handle to archived trace chunks directory"); + goto end; + } + archived_chunks_directory.is_set = true; + + /* + * FIXME replace renamat() use by directory handle + * wrapper for non-POSIX 2008 systems. + */ + ret = renameat(session_dirfd, directory_to_rename, + archived_chunks_directory.value.dirfd, + archived_chunk_name); + if (ret) { + PERROR("Failed to rename folder \"%s\" to \"%s\"", + directory_to_rename, archived_chunk_name); + } + +end: + if (archived_chunks_directory.is_set) { + lttng_directory_handle_fini(&archived_chunks_directory.value); + } + free(archived_chunk_name); + if (free_directory_to_rename) { + free(directory_to_rename); + } +} + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_close_command( + struct lttng_trace_chunk *chunk, + enum lttng_trace_chunk_command_type close_command) +{ + enum lttng_trace_chunk_status status = LTTNG_TRACE_CHUNK_STATUS_OK; + + if (close_command < LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED || + close_command >= LTTNG_TRACE_CHUNK_COMMAND_TYPE_MAX) { + status = LTTNG_TRACE_CHUNK_STATUS_INVALID_ARGUMENT; + goto end_unlock; + } + + pthread_mutex_lock(&chunk->lock); + if (chunk->close_command.is_set) { + DBG("Overriding trace chunk close command from \"%s\" to \"%s\"", + close_command_names[chunk->close_command.value], + close_command_names[close_command]); + } else { + DBG("Setting trace chunk close command to \"%s\"", + close_command_names[close_command]); + } + LTTNG_OPTIONAL_SET(&chunk->close_command, close_command); + pthread_mutex_unlock(&chunk->lock); +end_unlock: + return status; +} + +LTTNG_HIDDEN +bool lttng_trace_chunk_get(struct lttng_trace_chunk *chunk) +{ + return urcu_ref_get_unless_zero(&chunk->ref); +} + +static +void free_lttng_trace_chunk_registry_element(struct rcu_head *node) +{ + struct lttng_trace_chunk_registry_element *element = + container_of(node, typeof(*element), rcu_node); + + lttng_trace_chunk_fini(&element->chunk); + free(element); +} + +static +void lttng_trace_chunk_release(struct urcu_ref *ref) +{ + struct lttng_trace_chunk *chunk = container_of(ref, typeof(*chunk), + ref); + + if (chunk->close_command.is_set) { + close_command_funcs[chunk->close_command.value](chunk); + } + + if (chunk->in_registry_element) { + struct lttng_trace_chunk_registry_element *element; + + element = container_of(chunk, typeof(*element), chunk); + if (element->registry) { + rcu_read_lock(); + cds_lfht_del(element->registry->ht, + &element->trace_chunk_registry_ht_node); + rcu_read_unlock(); + call_rcu(&element->rcu_node, + free_lttng_trace_chunk_registry_element); + } else { + /* Never published, can be free'd immediately. */ + free_lttng_trace_chunk_registry_element( + &element->rcu_node); + } + } else { + /* Not RCU-protected, free immediately. */ + lttng_trace_chunk_fini(chunk); + free(chunk); + } +} + +LTTNG_HIDDEN +void lttng_trace_chunk_put(struct lttng_trace_chunk *chunk) +{ + if (!chunk) { + return; + } + assert(chunk->ref.refcount); + urcu_ref_put(&chunk->ref, lttng_trace_chunk_release); +} + +LTTNG_HIDDEN +struct lttng_trace_chunk_registry *lttng_trace_chunk_registry_create(void) +{ + struct lttng_trace_chunk_registry *registry; + + registry = zmalloc(sizeof(*registry)); + if (!registry) { + goto end; + } + + registry->ht = cds_lfht_new(DEFAULT_HT_SIZE, 1, 0, + CDS_LFHT_AUTO_RESIZE | CDS_LFHT_ACCOUNTING, NULL); + if (!registry->ht) { + goto error; + } +end: + return registry; +error: + lttng_trace_chunk_registry_destroy(registry); + goto end; +} + +LTTNG_HIDDEN +void lttng_trace_chunk_registry_destroy( + struct lttng_trace_chunk_registry *registry) +{ + if (!registry) { + return; + } + if (registry->ht) { + int ret = cds_lfht_destroy(registry->ht, NULL); + assert(!ret); + } + free(registry); +} + +static +struct lttng_trace_chunk_registry_element * +lttng_trace_chunk_registry_element_create_from_chunk( + struct lttng_trace_chunk *chunk, uint64_t session_id) +{ + struct lttng_trace_chunk_registry_element *element = + zmalloc(sizeof(*element)); + + if (!element) { + goto end; + } + cds_lfht_node_init(&element->trace_chunk_registry_ht_node); + element->session_id = session_id; + + element->chunk = *chunk; + lttng_trace_chunk_init(&element->chunk); + if (chunk->session_output_directory.is_set) { + element->chunk.session_output_directory.value = + lttng_directory_handle_move( + &chunk->session_output_directory.value); + } + if (chunk->chunk_directory.is_set) { + element->chunk.chunk_directory.value = + lttng_directory_handle_move( + &chunk->chunk_directory.value); + } + /* + * The original chunk becomes invalid; the name attribute is transferred + * to the new chunk instance. + */ + chunk->name = NULL; + element->chunk.in_registry_element = true; +end: + return element; +} + +LTTNG_HIDDEN +struct lttng_trace_chunk * +lttng_trace_chunk_registry_publish_chunk( + struct lttng_trace_chunk_registry *registry, + uint64_t session_id, struct lttng_trace_chunk *chunk) +{ + struct lttng_trace_chunk_registry_element *element; + unsigned long element_hash; + + pthread_mutex_lock(&chunk->lock); + element = lttng_trace_chunk_registry_element_create_from_chunk(chunk, + session_id); + pthread_mutex_unlock(&chunk->lock); + if (!element) { + goto end; + } + /* + * chunk is now invalid, the only valid operation is a 'put' from the + * caller. + */ + chunk = NULL; + element_hash = lttng_trace_chunk_registry_element_hash(element); + + rcu_read_lock(); + while (1) { + struct cds_lfht_node *published_node; + struct lttng_trace_chunk *published_chunk; + struct lttng_trace_chunk_registry_element *published_element; + + published_node = cds_lfht_add_unique(registry->ht, + element_hash, + lttng_trace_chunk_registry_element_match, + element, + &element->trace_chunk_registry_ht_node); + if (published_node == &element->trace_chunk_registry_ht_node) { + /* Successfully published the new element. */ + element->registry = registry; + /* Acquire a reference for the caller. */ + if (lttng_trace_chunk_get(&element->chunk)) { + break; + } else { + /* + * Another thread concurrently unpublished the + * trace chunk. This is currently unexpected. + * + * Re-attempt to publish. + */ + ERR("Attemp to publish a trace chunk to the chunk registry raced with a trace chunk deletion"); + continue; + } + } + + /* + * An equivalent trace chunk was published before this trace + * chunk. Attempt to acquire a reference to the one that was + * already published and release the reference to the copy we + * created if successful. + */ + published_element = container_of(published_node, + typeof(*published_element), + trace_chunk_registry_ht_node); + published_chunk = &published_element->chunk; + if (lttng_trace_chunk_get(published_chunk)) { + lttng_trace_chunk_put(&element->chunk); + element = published_element; + break; + } + /* + * A reference to the previously published trace chunk could not + * be acquired. Hence, retry to publish our copy of the trace + * chunk. + */ + } + rcu_read_unlock(); +end: + return element ? &element->chunk : NULL; +} + +/* + * Note that the caller must be registered as an RCU thread. + * However, it does not need to hold the RCU read lock. The RCU read lock is + * acquired to perform the look-up in the registry's hash table and held until + * after a reference to the "found" trace chunk is acquired. + * + * IOW, holding a reference guarantees the existence of the object for the + * caller. + */ +static +struct lttng_trace_chunk *_lttng_trace_chunk_registry_find_chunk( + const struct lttng_trace_chunk_registry *registry, + uint64_t session_id, uint64_t *chunk_id) +{ + const struct lttng_trace_chunk_registry_element target_element = { + .chunk.id.is_set = !!chunk_id, + .chunk.id.value = chunk_id ? *chunk_id : 0, + .session_id = session_id, + }; + const unsigned long element_hash = + lttng_trace_chunk_registry_element_hash( + &target_element); + struct cds_lfht_node *published_node; + struct lttng_trace_chunk_registry_element *published_element; + struct lttng_trace_chunk *published_chunk = NULL; + struct cds_lfht_iter iter; + + rcu_read_lock(); + cds_lfht_lookup(registry->ht, + element_hash, + lttng_trace_chunk_registry_element_match, + &target_element, + &iter); + published_node = cds_lfht_iter_get_node(&iter); + if (!published_node) { + goto end; + } + + published_element = container_of(published_node, + typeof(*published_element), + trace_chunk_registry_ht_node); + if (lttng_trace_chunk_get(&published_element->chunk)) { + published_chunk = &published_element->chunk; + } +end: + rcu_read_unlock(); + return published_chunk; +} + +LTTNG_HIDDEN +struct lttng_trace_chunk * +lttng_trace_chunk_registry_find_chunk( + const struct lttng_trace_chunk_registry *registry, + uint64_t session_id, uint64_t chunk_id) +{ + return _lttng_trace_chunk_registry_find_chunk(registry, + session_id, &chunk_id); +} + +LTTNG_HIDDEN +struct lttng_trace_chunk * +lttng_trace_chunk_registry_find_anonymous_chunk( + const struct lttng_trace_chunk_registry *registry, + uint64_t session_id) +{ + return _lttng_trace_chunk_registry_find_chunk(registry, + session_id, NULL); +} diff --git a/src/common/trace-chunk.h b/src/common/trace-chunk.h new file mode 100644 index 000000000..a318341b5 --- /dev/null +++ b/src/common/trace-chunk.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2019 - Jérémie Galarneau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License, version 2.1 only, + * as published by the Free Software Foundation. + * + * This 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser 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 + */ + +#ifndef LTTNG_TRACE_CHUNK_H +#define LTTNG_TRACE_CHUNK_H + +#include +#include +#include +#include +#include +#include + +/* + * 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, + * a snapshot, or a regular LTTng trace are all instances of a trace archive. + * + * A trace chunk is always contained within a session output directory. + * + * This facility is used by the session daemon, consumer daemon(s), and relay + * daemon to: + * - Control file (data stream, metadata, and index) creation relative to + * a given output directory, + * - Track the use of an output directory by other objects in order to + * know if/when an output directory can be safely consumed, renamed, + * deleted, etc. + * + * + * OWNER VS USER + * --- + * + * A trace chunk can either be a owner or a user of its + * "chunk output directory". + * + * A "user" trace chunk is provided with a handle to the chunk output directory + * which can then be used to create subdirectories and files. + * + * An "owner" chunk, on top of being able to perform the operations of a "user" + * chunk can perform operations on its chunk output directory, such as renaming + * or deleting it. + * + * A trace chunk becomes an "owner" or "user" chunk based on which of + * 'lttng_trace_chunk_set_as_owner()' or 'lttng_trace_chunk_set_as_user()' is + * used. These methods are _exclusive_ and must only be used once on a + * trace chunk. + */ + +struct lttng_trace_chunk; + +enum lttng_trace_chunk_status { + LTTNG_TRACE_CHUNK_STATUS_OK, + LTTNG_TRACE_CHUNK_STATUS_NONE, + LTTNG_TRACE_CHUNK_STATUS_INVALID_ARGUMENT, + LTTNG_TRACE_CHUNK_STATUS_INVALID_OPERATION, + LTTNG_TRACE_CHUNK_STATUS_ERROR, +}; + +enum lttng_trace_chunk_command_type { + LTTNG_TRACE_CHUNK_COMMAND_TYPE_MOVE_TO_COMPLETED = 0, + LTTNG_TRACE_CHUNK_COMMAND_TYPE_MAX +}; + +LTTNG_HIDDEN +struct lttng_trace_chunk *lttng_trace_chunk_create_anonymous(void); + +LTTNG_HIDDEN +struct lttng_trace_chunk *lttng_trace_chunk_create( + uint64_t chunk_id, + time_t chunk_creation_time); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_id( + struct lttng_trace_chunk *chunk, uint64_t *id); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_creation_timestamp( + struct lttng_trace_chunk *chunk, time_t *creation_ts); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_close_timestamp( + struct lttng_trace_chunk *chunk, time_t *close_ts); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_close_timestamp( + struct lttng_trace_chunk *chunk, time_t close_ts); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_name( + struct lttng_trace_chunk *chunk, const char **name, + bool *name_overriden); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_override_name( + struct lttng_trace_chunk *chunk, const char *name); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_credentials( + struct lttng_trace_chunk *chunk, + struct lttng_credentials *credentials); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_credentials( + struct lttng_trace_chunk *chunk, + const struct lttng_credentials *credentials); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_credentials_current_user( + struct lttng_trace_chunk *chunk); + +/* session_output_directory ownership is transferred to the chunk on success. */ +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_as_owner( + struct lttng_trace_chunk *chunk, + struct lttng_directory_handle *session_output_directory); + +/* chunk_output_directory ownership is transferred to the chunk on success. */ +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_as_user( + struct lttng_trace_chunk *chunk, + struct lttng_directory_handle *chunk_directory); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_get_chunk_directory_handle( + struct lttng_trace_chunk *chunk, + const struct lttng_directory_handle **handle); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_create_subdirectory( + struct lttng_trace_chunk *chunk, + const char *subdirectory_path); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_open_file( + struct lttng_trace_chunk *chunk, const char *filename, + int flags, mode_t mode, int *out_fd); + +LTTNG_HIDDEN +int lttng_trace_chunk_unlink_file(struct lttng_trace_chunk *chunk, + const char *filename); + +LTTNG_HIDDEN +enum lttng_trace_chunk_status lttng_trace_chunk_set_close_command( + struct lttng_trace_chunk *chunk, + enum lttng_trace_chunk_command_type command_type); + +/* Returns true on success. */ +LTTNG_HIDDEN +bool lttng_trace_chunk_get(struct lttng_trace_chunk *chunk); + +LTTNG_HIDDEN +void lttng_trace_chunk_put(struct lttng_trace_chunk *chunk); + +#endif /* LTTNG_TRACE_CHUNK_H */ -- 2.34.1