diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a8186a3f..64e5f1d63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +**Features**: + +- The `sentry_attach_file`, `sentry_scope_attach_file` (and their wide-string variants), and `sentry_remove_attachment` have been added to modify the list of attachments that are sent along with sentry events after a call to `sentry_init`. ([#1266](https://github.com/getsentry/sentry-native/pull/1266)) + - NOTE: When using the `crashpad` backend on macOS, the list of attachments that will be added at the time of a hard crash will be frozen at the time of `sentry_init`, and later modifications will not be reflected. + ## 0.9.0 **Breaking changes**: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 805fbc233..43d63d5be 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,6 +155,7 @@ The example currently supports the following commands: instead of a hardcoded value. - `attachment`: Adds an attachment, which is currently defined as the `CMakeCache.txt` file, which is part of the CMake build folder. +- `attach-after-init`: Same as `attachment` but after the SDK has been initialized. - `stdout`: Uses a custom transport which dumps all envelopes to `stdout`. - `no-setup`: Skips all scope and breadcrumb initialization code. - `start-session`: Starts a new release-health session. @@ -185,6 +186,7 @@ The example currently supports the following commands: This file can be found in `./tests/fixtures/view-hierachy.json`. - `set-trace`: Sets the scope `propagation_context`'s trace data to the given `trace_id="aaaabbbbccccddddeeeeffff00001111"` and `parent_span_id=""f0f0f0f0f0f0f0f0"`. - `capture-with-scope`: Captures an event with a local scope. +- `attach-to-scope`: Same as `attachment` but attaches the file to the local scope. Only on Linux using crashpad: - `crashpad-wait-for-upload`: Couples application shutdown to complete the upload in the `crashpad_handler`. diff --git a/README.md b/README.md index 45426af5f..f0a2c9028 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,8 @@ Other important configuration options include: But since this process bypasses SEH, the application local exception handler is no longer invoked, which also means that for these kinds of crashes, `before_send` and `on_crash` will not be invoked before sending the minidump and thus have no effect. +- When using the crashpad backend on macOS, the list of attachments that will be sent + along with crashes is frozen at the time of `sentry_init`. ## Benchmarks diff --git a/examples/example.c b/examples/example.c index b6897cfaf..e60c24d4f 100644 --- a/examples/example.c +++ b/examples/example.c @@ -399,6 +399,12 @@ main(int argc, char **argv) sentry_set_trace(direct_trace_id, direct_parent_span_id); } + if (has_arg(argc, argv, "attach-after-init")) { + // assuming the example / test is run directly from the cmake build + // directory + sentry_attach_file("./CMakeCache.txt"); + } + if (has_arg(argc, argv, "start-session")) { sentry_start_session(); } @@ -424,6 +430,12 @@ main(int argc, char **argv) sentry_value_t debug_crumb = create_debug_crumb("scoped crumb"); sentry_scope_add_breadcrumb(scope, debug_crumb); + if (has_arg(argc, argv, "attach-to-scope")) { + // assuming the example / test is run directly from the cmake build + // directory + sentry_scope_attach_file(scope, "./CMakeCache.txt"); + } + sentry_capture_event_with_scope(event, scope); } diff --git a/external/crashpad b/external/crashpad index b2653919b..2e8ce8b68 160000 --- a/external/crashpad +++ b/external/crashpad @@ -1 +1 @@ -Subproject commit b2653919b42cf310482a9e33620cf82e952b5d2d +Subproject commit 2e8ce8b6832aaaa8c4fbdd3b66d2b80ef4ca585c diff --git a/include/sentry.h b/include/sentry.h index 6314c374c..91c584418 100644 --- a/include/sentry.h +++ b/include/sentry.h @@ -13,6 +13,17 @@ * encoding, typically ANSI on Windows, UTF-8 macOS, and the locale encoding on * Linux; and they provide wchar-compatible alternatives on Windows which are * preferred. + * + * NOTE on attachments: + * + * Attachments are read lazily at the time of `sentry_capture_event`, + * `sentry_capture_event_with_scope`, or at time of a hard crash. Relative + * attachment paths will be resolved according to the current working directory + * at the time of envelope creation. When adding and removing attachments, they + * are matched according to their given `path`. No normalization is performed. + * When using the `crashpad` backend on macOS, the list of attachments that will + * be added at the time of a hard crash will be frozen at the time of + * `sentry_init`, and later modifications will not be reflected. */ #ifndef SENTRY_H_INCLUDED @@ -1215,6 +1226,8 @@ SENTRY_API int sentry_options_get_symbolize_stacktraces( * `path` is assumed to be in platform-specific filesystem path encoding. * API Users on windows are encouraged to use `sentry_options_add_attachmentw` * instead. + * + * See the NOTE on attachments above for restrictions of this API. */ SENTRY_API void sentry_options_add_attachment( sentry_options_t *opts, const char *path); @@ -1771,6 +1784,55 @@ SENTRY_EXPERIMENTAL_API void sentry_options_set_handler_strategy( #endif // SENTRY_PLATFORM_LINUX +/** + * A sentry Attachment. + * + * See https://develop.sentry.dev/sdk/data-model/envelope-items/#attachment + */ +struct sentry_attachment_s; +typedef struct sentry_attachment_s sentry_attachment_t; + +/** + * Adds a new attachment to be sent along. + * + * `path` is assumed to be in platform-specific filesystem path encoding. + * API Users on windows are encouraged to use `sentry_attach_filew` or + * `sentry_scope_attach_filew` instead. + * + * The returned `sentry_attachment_t` is owned by the SDK and will remain valid + * until the attachment is removed with `sentry_remove_attachment` or + * `sentry_close` is called + * + * See the NOTE on attachments above for restrictions of this API. + */ +SENTRY_API sentry_attachment_t *sentry_attach_file(const char *path); +SENTRY_API sentry_attachment_t *sentry_attach_file_n( + const char *path, size_t path_len); +SENTRY_API sentry_attachment_t *sentry_scope_attach_file( + sentry_scope_t *scope, const char *path); +SENTRY_API sentry_attachment_t *sentry_scope_attach_file_n( + sentry_scope_t *scope, const char *path, size_t path_len); + +/** + * Removes and frees a previously added attachment. + * + * See the NOTE on attachments above for restrictions of this API. + */ +SENTRY_API void sentry_remove_attachment(sentry_attachment_t *attachment); + +#ifdef SENTRY_PLATFORM_WINDOWS +/** + * Wide char versions of `sentry_attach_file` and `sentry_scope_attach_file`. + */ +SENTRY_API sentry_attachment_t *sentry_attach_filew(const wchar_t *path); +SENTRY_API sentry_attachment_t *sentry_attach_filew_n( + const wchar_t *path, size_t path_len); +SENTRY_API sentry_attachment_t *sentry_scope_attach_filew( + sentry_scope_t *scope, const wchar_t *path); +SENTRY_API sentry_attachment_t *sentry_scope_attach_filew_n( + sentry_scope_t *scope, const wchar_t *path, size_t path_len); +#endif + /* -- Session APIs -- */ typedef enum { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 700f79753..d60bdd158 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,8 @@ sentry_target_sources_cwd(sentry sentry_alloc.c sentry_alloc.h + sentry_attachment.c + sentry_attachment.h sentry_backend.c sentry_backend.h sentry_boot.h diff --git a/src/backends/sentry_backend_breakpad.cpp b/src/backends/sentry_backend_breakpad.cpp index 07e0f6361..bac8d6534 100644 --- a/src/backends/sentry_backend_breakpad.cpp +++ b/src/backends/sentry_backend_breakpad.cpp @@ -170,7 +170,7 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor, = sentry__screenshot_get_path(options); if (sentry__screenshot_capture(screenshot_path)) { sentry__envelope_add_attachment( - envelope, screenshot_path, nullptr); + envelope, screenshot_path, ATTACHMENT, nullptr); } sentry__path_free(screenshot_path); } diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index 84a7b0796..81d2d930d 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -2,6 +2,7 @@ extern "C" { #include "sentry_boot.h" #include "sentry_alloc.h" +#include "sentry_attachment.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" @@ -660,6 +661,30 @@ crashpad_backend_prune_database(sentry_backend_t *backend) crashpad::PruneCrashReportDatabase(data->db, &condition); } +#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) +static void +crashpad_backend_add_attachment( + sentry_backend_t *backend, const sentry_path_t *attachment) +{ + auto *data = static_cast(backend->data); + if (!data || !data->client) { + return; + } + data->client->AddAttachment(base::FilePath(attachment->path)); +} + +static void +crashpad_backend_remove_attachment( + sentry_backend_t *backend, const sentry_path_t *attachment) +{ + auto *data = static_cast(backend->data); + if (!data || !data->client) { + return; + } + data->client->RemoveAttachment(base::FilePath(attachment->path)); +} +#endif + sentry_backend_t * sentry__backend_new(void) { @@ -687,6 +712,10 @@ sentry__backend_new(void) backend->user_consent_changed_func = crashpad_backend_user_consent_changed; backend->get_last_crash_func = crashpad_backend_last_crash; backend->prune_database_func = crashpad_backend_prune_database; +#if defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_LINUX) + backend->add_attachment_func = crashpad_backend_add_attachment; + backend->remove_attachment_func = crashpad_backend_remove_attachment; +#endif backend->data = data; backend->can_capture_after_shutdown = true; diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index b03982df1..f4b8bd554 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -602,7 +602,7 @@ handle_ucontext(const sentry_ucontext_t *uctx) = sentry__screenshot_get_path(options); if (sentry__screenshot_capture(screenshot_path)) { sentry__envelope_add_attachment( - envelope, screenshot_path, NULL); + envelope, screenshot_path, ATTACHMENT, NULL); } sentry__path_free(screenshot_path); } diff --git a/src/path/sentry_path.c b/src/path/sentry_path.c index 4084a8988..9eefdf383 100644 --- a/src/path/sentry_path.c +++ b/src/path/sentry_path.c @@ -11,6 +11,19 @@ sentry__path_free(sentry_path_t *path) sentry_free(path); } +bool +sentry__path_eq(const sentry_path_t *path_a, const sentry_path_t *path_b) +{ + size_t i = 0; + while (path_a->path[i] == path_b->path[i]) { + if (path_a->path[i] == (sentry_pathchar_t)0) { + return true; + } + i++; + } + return false; +} + int sentry__path_remove_all(const sentry_path_t *path) { diff --git a/src/sentry_attachment.c b/src/sentry_attachment.c new file mode 100644 index 000000000..b30630774 --- /dev/null +++ b/src/sentry_attachment.c @@ -0,0 +1,89 @@ +#include "sentry_attachment.h" +#include "sentry_alloc.h" +#include "sentry_path.h" + +static void +attachment_free(sentry_attachment_t *attachment) +{ + if (!attachment) { + return; + } + sentry__path_free(attachment->path); + sentry_free(attachment); +} + +void +sentry__attachments_free(sentry_attachment_t *attachments) +{ + sentry_attachment_t *it = attachments; + while (it) { + sentry_attachment_t *attachment = it; + it = attachment->next; + + attachment_free(attachment); + } +} + +sentry_attachment_t * +sentry__attachments_add(sentry_attachment_t **attachments_ptr, + sentry_path_t *path, sentry_attachment_type_t attachment_type, + const char *content_type) +{ + if (!path) { + return NULL; + } + sentry_attachment_t *attachment = SENTRY_MAKE(sentry_attachment_t); + if (!attachment) { + sentry__path_free(path); + return NULL; + } + attachment->path = path; + attachment->next = NULL; + attachment->type = attachment_type; + attachment->content_type = content_type; + + sentry_attachment_t **next_ptr = attachments_ptr; + + for (sentry_attachment_t *it = *attachments_ptr; it; it = it->next) { + if (sentry__path_eq(it->path, path)) { + attachment_free(attachment); + return it; + } + + next_ptr = &it->next; + } + + *next_ptr = attachment; + return attachment; +} + +void +sentry__attachments_remove( + sentry_attachment_t **attachments_ptr, sentry_attachment_t *attachment) +{ + if (!attachment) { + return; + } + + sentry_attachment_t **next_ptr = attachments_ptr; + + for (sentry_attachment_t *it = *attachments_ptr; it; it = it->next) { + if (it == attachment || sentry__path_eq(it->path, attachment->path)) { + *next_ptr = it->next; + attachment_free(it); + return; + } + + next_ptr = &it->next; + } +} + +void +sentry__attachments_extend( + sentry_attachment_t **attachments_ptr, sentry_attachment_t *attachments) +{ + for (sentry_attachment_t *it = attachments; it; it = it->next) { + sentry__attachments_add(attachments_ptr, sentry__path_clone(it->path), + it->type, it->content_type); + } +} diff --git a/src/sentry_attachment.h b/src/sentry_attachment.h new file mode 100644 index 000000000..cc69263e1 --- /dev/null +++ b/src/sentry_attachment.h @@ -0,0 +1,53 @@ +#ifndef SENTRY_ATTACHMENT_H_INCLUDED +#define SENTRY_ATTACHMENT_H_INCLUDED + +#include "sentry_boot.h" + +#include "sentry_path.h" + +/** + * The attachment_type. + */ +typedef enum { + ATTACHMENT, + MINIDUMP, + VIEW_HIERARCHY, +} sentry_attachment_type_t; + +/** + * This is a linked list of all the attachments registered via + * `sentry_options_add_attachment`. + */ +struct sentry_attachment_s { + sentry_path_t *path; + sentry_attachment_type_t type; + const char *content_type; + sentry_attachment_t *next; +}; + +/** + * Frees the linked list of `attachments`. + */ +void sentry__attachments_free(sentry_attachment_t *attachments); + +/** + * Adds an attachment to the attachments list at `attachments_ptr`. + */ +sentry_attachment_t *sentry__attachments_add( + sentry_attachment_t **attachments_ptr, sentry_path_t *path, + sentry_attachment_type_t attachment_type, const char *content_type); + +/** + * Removes an attachment from the attachments list at `attachments_ptr`. + */ +void sentry__attachments_remove( + sentry_attachment_t **attachments_ptr, sentry_attachment_t *attachment); + +/** + * Extends the linked list of attachments at `attachments_ptr` with all + * attachments in `attachments`. + */ +void sentry__attachments_extend( + sentry_attachment_t **attachments_ptr, sentry_attachment_t *attachments); + +#endif diff --git a/src/sentry_backend.h b/src/sentry_backend.h index e3c96b0f5..14e96afcc 100644 --- a/src/sentry_backend.h +++ b/src/sentry_backend.h @@ -25,6 +25,8 @@ struct sentry_backend_s { void (*user_consent_changed_func)(sentry_backend_t *); uint64_t (*get_last_crash_func)(sentry_backend_t *); void (*prune_database_func)(sentry_backend_t *); + void (*add_attachment_func)(sentry_backend_t *, const sentry_path_t *); + void (*remove_attachment_func)(sentry_backend_t *, const sentry_path_t *); void *data; // Whether this backend still runs after shutdown_func was called. bool can_capture_after_shutdown; diff --git a/src/sentry_core.c b/src/sentry_core.c index 24d7a94bf..bac6b2e80 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -3,6 +3,7 @@ #include #include +#include "sentry_attachment.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" @@ -211,6 +212,8 @@ sentry_init(sentry_options_t *options) } sentry_value_freeze(scope->client_sdk); initialize_propagation_context(&scope->propagation_context); + scope->attachments = options->attachments; + options->attachments = NULL; } if (backend && backend->user_consent_changed_func) { backend->user_consent_changed_func(backend); @@ -545,22 +548,6 @@ static return send; } -static const char * -str_from_attachment_type(sentry_attachment_type_t attachment_type) -{ - switch (attachment_type) { - case ATTACHMENT: - return "event.attachment"; - case MINIDUMP: - return "event.minidump"; - case VIEW_HIERARCHY: - return "event.view_hierarchy"; - default: - UNREACHABLE("Unknown attachment type"); - return "event.attachment"; - } -} - sentry_envelope_t * sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry_uuid_t *event_id, bool invoke_before_send, @@ -572,10 +559,12 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, sentry__record_errors_on_current_session(1); } + sentry_attachment_t *all_attachments = NULL; if (local_scope) { SENTRY_DEBUG("merging local scope into event"); sentry_scope_mode_t mode = SENTRY_SCOPE_BREADCRUMBS; sentry__scope_apply_to_event(local_scope, options, event, mode); + sentry__attachments_extend(&all_attachments, local_scope->attachments); sentry__scope_free(local_scope); } @@ -585,6 +574,9 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, if (!options->symbolize_stacktraces) { mode &= ~SENTRY_SCOPE_STACKTRACES; } + if (all_attachments) { + sentry__attachments_extend(&all_attachments, scope->attachments); + } sentry__scope_apply_to_event(scope, options, event, mode); } @@ -604,32 +596,18 @@ sentry__prepare_event(const sentry_options_t *options, sentry_value_t event, goto fail; } - SENTRY_DEBUG("adding attachments to envelope"); - for (sentry_attachment_t *attachment = options->attachments; attachment; - attachment = attachment->next) { - sentry_envelope_item_t *item = sentry__envelope_add_from_path( - envelope, attachment->path, "attachment"); - if (!item) { - continue; - } - if (attachment->type != ATTACHMENT) { // don't need to set the default - sentry__envelope_item_set_header(item, "attachment_type", - sentry_value_new_string( - str_from_attachment_type(attachment->type))); - } - if (attachment->content_type) { - sentry__envelope_item_set_header(item, "content_type", - sentry_value_new_string(attachment->content_type)); + SENTRY_WITH_SCOPE (scope) { + if (all_attachments) { + // all attachments merged from multiple scopes + sentry__envelope_add_attachments(envelope, all_attachments); + } else { + // only global scope has attachments + sentry__envelope_add_attachments(envelope, scope->attachments); } - sentry__envelope_item_set_header(item, "filename", -#ifdef SENTRY_PLATFORM_WINDOWS - sentry__value_new_string_from_wstr( -#else - sentry_value_new_string( -#endif - sentry__path_filename(attachment->path))); } + sentry__attachments_free(all_attachments); + return envelope; fail: @@ -1449,3 +1427,68 @@ sentry_capture_minidump_n(const char *path, size_t path_len) return sentry_uuid_nil(); } + +sentry_attachment_t * +sentry_attach_file(const char *path) +{ + return sentry_attach_file_n(path, sentry__guarded_strlen(path)); +} + +sentry_attachment_t * +sentry_attach_file_n(const char *path, size_t path_len) +{ + sentry_path_t *attachment_path = sentry__path_from_str_n(path, path_len); + SENTRY_WITH_OPTIONS (options) { + if (options->backend && options->backend->add_attachment_func) { + options->backend->add_attachment_func( + options->backend, attachment_path); + } + } + sentry_attachment_t *attachment = NULL; + SENTRY_WITH_SCOPE_MUT (scope) { + attachment = sentry__attachments_add( + &scope->attachments, attachment_path, ATTACHMENT, NULL); + } + return attachment; +} + +void +sentry_remove_attachment(sentry_attachment_t *attachment) +{ + SENTRY_WITH_OPTIONS (options) { + if (options->backend && options->backend->remove_attachment_func) { + options->backend->remove_attachment_func( + options->backend, attachment->path); + } + } + SENTRY_WITH_SCOPE_MUT (scope) { + sentry__attachments_remove(&scope->attachments, attachment); + } +} + +#ifdef SENTRY_PLATFORM_WINDOWS +sentry_attachment_t * +sentry_attach_filew(const wchar_t *path) +{ + size_t path_len = path ? wcslen(path) : 0; + return sentry_attach_filew_n(path, path_len); +} + +sentry_attachment_t * +sentry_attach_filew_n(const wchar_t *path, size_t path_len) +{ + sentry_path_t *attachment_path = sentry__path_from_wstr_n(path, path_len); + SENTRY_WITH_OPTIONS (options) { + if (options->backend && options->backend->add_attachment_func) { + options->backend->add_attachment_func( + options->backend, attachment_path); + } + } + sentry_attachment_t *attachment = NULL; + SENTRY_WITH_SCOPE_MUT (scope) { + attachment = sentry__attachments_add( + &scope->attachments, attachment_path, ATTACHMENT, NULL); + } + return attachment; +} +#endif diff --git a/src/sentry_envelope.c b/src/sentry_envelope.c index 137115c86..0f39575e5 100644 --- a/src/sentry_envelope.c +++ b/src/sentry_envelope.c @@ -8,6 +8,7 @@ #include "sentry_string.h" #include "sentry_transport.h" #include "sentry_value.h" +#include #include struct sentry_envelope_item_s { @@ -345,9 +346,26 @@ sentry__envelope_add_session( envelope, payload, payload_len, "session"); } +static const char * +str_from_attachment_type(sentry_attachment_type_t attachment_type) +{ + switch (attachment_type) { + case ATTACHMENT: + return "event.attachment"; + case MINIDUMP: + return "event.minidump"; + case VIEW_HIERARCHY: + return "event.view_hierarchy"; + default: + UNREACHABLE("Unknown attachment type"); + return "event.attachment"; + } +} + sentry_envelope_item_t * sentry__envelope_add_attachment(sentry_envelope_t *envelope, - const sentry_path_t *attachment, const char *type) + const sentry_path_t *attachment, sentry_attachment_type_t type, + const char *content_type) { if (!envelope || !attachment) { return NULL; @@ -355,11 +373,17 @@ sentry__envelope_add_attachment(sentry_envelope_t *envelope, sentry_envelope_item_t *item = sentry__envelope_add_from_path(envelope, attachment, "attachment"); - if (type) { + if (!item) { + return NULL; + } + if (type != ATTACHMENT) { // don't need to set the default + sentry__envelope_item_set_header(item, "attachment_type", + sentry_value_new_string(str_from_attachment_type(type))); + } + if (content_type) { sentry__envelope_item_set_header( - item, "attachment_type", sentry_value_new_string(type)); + item, "content_type", sentry_value_new_string(content_type)); } - sentry__envelope_item_set_header(item, "filename", #ifdef SENTRY_PLATFORM_WINDOWS sentry__value_new_string_from_wstr( @@ -371,6 +395,22 @@ sentry__envelope_add_attachment(sentry_envelope_t *envelope, return item; } +void +sentry__envelope_add_attachments( + sentry_envelope_t *envelope, const sentry_attachment_t *attachments) +{ + if (!envelope || !attachments) { + return; + } + + SENTRY_DEBUG("adding attachments to envelope"); + for (const sentry_attachment_t *attachment = attachments; attachment; + attachment = attachment->next) { + sentry__envelope_add_attachment(envelope, attachment->path, + attachment->type, attachment->content_type); + } +} + sentry_envelope_item_t * sentry__envelope_add_from_buffer(sentry_envelope_t *envelope, const char *buf, size_t buf_len, const char *type) @@ -494,7 +534,9 @@ sentry_envelope_serialize(const sentry_envelope_t *envelope, size_t *size_out) sentry__envelope_serialize_into_stringbuilder(envelope, &sb); - *size_out = sentry__stringbuilder_len(&sb); + if (size_out) { + *size_out = sentry__stringbuilder_len(&sb); + } return sentry__stringbuilder_into_string(&sb); } diff --git a/src/sentry_envelope.h b/src/sentry_envelope.h index 842cbb475..269eb485c 100644 --- a/src/sentry_envelope.h +++ b/src/sentry_envelope.h @@ -4,6 +4,7 @@ #include "sentry_boot.h" #include "sentry_core.h" +#include "sentry_attachment.h" #include "sentry_path.h" #include "sentry_ratelimiter.h" #include "sentry_session.h" @@ -59,7 +60,13 @@ sentry_envelope_item_t *sentry__envelope_add_session( */ sentry_envelope_item_t *sentry__envelope_add_attachment( sentry_envelope_t *envelope, const sentry_path_t *attachment, - const char *type); + sentry_attachment_type_t type, const char *content_type); + +/** + * Add attachments to this envelope. + */ +void sentry__envelope_add_attachments( + sentry_envelope_t *envelope, const sentry_attachment_t *attachments); /** * This will add the file contents from `path` as an envelope item of type diff --git a/src/sentry_options.c b/src/sentry_options.c index 0bfb70b10..593c64ac7 100644 --- a/src/sentry_options.c +++ b/src/sentry_options.c @@ -1,5 +1,6 @@ #include "sentry_options.h" #include "sentry_alloc.h" +#include "sentry_attachment.h" #include "sentry_backend.h" #include "sentry_database.h" #include "sentry_logger.h" @@ -79,13 +80,6 @@ sentry__options_incref(sentry_options_t *options) return options; } -static void -attachment_free(sentry_attachment_t *attachment) -{ - sentry__path_free(attachment->path); - sentry_free(attachment); -} - void sentry_options_free(sentry_options_t *opts) { @@ -105,14 +99,7 @@ sentry_options_free(sentry_options_t *opts) sentry__path_free(opts->handler_path); sentry_transport_free(opts->transport); sentry__backend_free(opts->backend); - - sentry_attachment_t *next_attachment = opts->attachments; - while (next_attachment) { - sentry_attachment_t *attachment = next_attachment; - next_attachment = attachment->next; - - attachment_free(attachment); - } + sentry__attachments_free(opts->attachments); sentry__run_free(opts->run); sentry_free(opts); @@ -496,52 +483,35 @@ sentry_options_get_shutdown_timeout(sentry_options_t *opts) return opts->shutdown_timeout; } -static void -add_attachment(sentry_options_t *opts, sentry_path_t *path, - sentry_attachment_type_t attachment_type, const char *content_type) -{ - if (!path) { - return; - } - sentry_attachment_t *attachment = SENTRY_MAKE(sentry_attachment_t); - if (!attachment) { - sentry__path_free(path); - return; - } - attachment->path = path; - attachment->next = opts->attachments; - attachment->type = attachment_type; - attachment->content_type = content_type; - opts->attachments = attachment; -} - void sentry_options_add_attachment(sentry_options_t *opts, const char *path) { - add_attachment(opts, sentry__path_from_str(path), ATTACHMENT, NULL); + sentry__attachments_add( + &opts->attachments, sentry__path_from_str(path), ATTACHMENT, NULL); } void sentry_options_add_attachment_n( sentry_options_t *opts, const char *path, size_t path_len) { - add_attachment( - opts, sentry__path_from_str_n(path, path_len), ATTACHMENT, NULL); + sentry__attachments_add(&opts->attachments, + sentry__path_from_str_n(path, path_len), ATTACHMENT, NULL); } void sentry_options_add_view_hierarchy(sentry_options_t *opts, const char *path) { - add_attachment( - opts, sentry__path_from_str(path), VIEW_HIERARCHY, "application/json"); + sentry__attachments_add(&opts->attachments, sentry__path_from_str(path), + VIEW_HIERARCHY, "application/json"); } void sentry_options_add_view_hierarchy_n( sentry_options_t *opts, const char *path, size_t path_len) { - add_attachment(opts, sentry__path_from_str_n(path, path_len), - VIEW_HIERARCHY, "application/json"); + sentry__attachments_add(&opts->attachments, + sentry__path_from_str_n(path, path_len), VIEW_HIERARCHY, + "application/json"); } void @@ -585,8 +555,8 @@ void sentry_options_add_attachmentw_n( sentry_options_t *opts, const wchar_t *path, size_t path_len) { - add_attachment( - opts, sentry__path_from_wstr_n(path, path_len), ATTACHMENT, NULL); + sentry__attachments_add(&opts->attachments, + sentry__path_from_wstr_n(path, path_len), ATTACHMENT, NULL); } void @@ -606,8 +576,9 @@ void sentry_options_add_view_hierarchyw_n( sentry_options_t *opts, const wchar_t *path, size_t path_len) { - add_attachment(opts, sentry__path_from_wstr_n(path, path_len), - VIEW_HIERARCHY, "application/json"); + sentry__attachments_add(&opts->attachments, + sentry__path_from_wstr_n(path, path_len), VIEW_HIERARCHY, + "application/json"); } void diff --git a/src/sentry_options.h b/src/sentry_options.h index 0185bc67c..5316b69e5 100644 --- a/src/sentry_options.h +++ b/src/sentry_options.h @@ -3,6 +3,7 @@ #include "sentry_boot.h" +#include "sentry_attachment.h" #include "sentry_database.h" #include "sentry_logger.h" #include "sentry_session.h" @@ -13,25 +14,6 @@ #define SENTRY_DEFAULT_SHUTDOWN_TIMEOUT 2000 struct sentry_backend_s; -/** - * The attachment_type. - */ -typedef enum { - ATTACHMENT, - MINIDUMP, - VIEW_HIERARCHY, -} sentry_attachment_type_t; -/** - * This is a linked list of all the attachments registered via - * `sentry_options_add_attachment`. - */ -typedef struct sentry_attachment_s sentry_attachment_t; -struct sentry_attachment_s { - sentry_path_t *path; - sentry_attachment_type_t type; - const char *content_type; - sentry_attachment_t *next; -}; /** * This is the main options struct, which is being accessed throughout all of diff --git a/src/sentry_path.h b/src/sentry_path.h index 9cd72dc32..5a6cdea4f 100644 --- a/src/sentry_path.h +++ b/src/sentry_path.h @@ -94,6 +94,11 @@ void sentry__path_free(sentry_path_t *path); */ const sentry_pathchar_t *sentry__path_filename(const sentry_path_t *path); +/** + * Returns whether the two paths are equal. + */ +bool sentry__path_eq(const sentry_path_t *path_a, const sentry_path_t *path_b); + /** * Returns whether the last path segment matches `filename`. */ diff --git a/src/sentry_scope.c b/src/sentry_scope.c index 7109cd984..cc1c03697 100644 --- a/src/sentry_scope.c +++ b/src/sentry_scope.c @@ -1,5 +1,6 @@ #include "sentry_scope.h" #include "sentry_alloc.h" +#include "sentry_attachment.h" #include "sentry_backend.h" #include "sentry_core.h" #include "sentry_database.h" @@ -77,6 +78,7 @@ init_scope(sentry_scope_t *scope) scope->breadcrumbs = sentry_value_new_list(); scope->level = SENTRY_LEVEL_ERROR; scope->client_sdk = sentry_value_new_null(); + scope->attachments = NULL; scope->transaction_object = NULL; scope->span = NULL; } @@ -109,6 +111,7 @@ cleanup_scope(sentry_scope_t *scope) sentry_value_decref(scope->propagation_context); sentry_value_decref(scope->breadcrumbs); sentry_value_decref(scope->client_sdk); + sentry__attachments_free(scope->attachments); sentry__transaction_decref(scope->transaction_object); sentry__span_decref(scope->span); } @@ -669,3 +672,37 @@ sentry_scope_set_level(sentry_scope_t *scope, sentry_level_t level) { scope->level = level; } + +sentry_attachment_t * +sentry_scope_attach_file(sentry_scope_t *scope, const char *path) +{ + return sentry_scope_attach_file_n( + scope, path, sentry__guarded_strlen(path)); +} + +sentry_attachment_t * +sentry_scope_attach_file_n( + sentry_scope_t *scope, const char *path, size_t path_len) +{ + sentry_path_t *attachment = sentry__path_from_str_n(path, path_len); + return sentry__attachments_add( + &scope->attachments, attachment, ATTACHMENT, NULL); +} + +#ifdef SENTRY_PLATFORM_WINDOWS +sentry_attachment_t * +sentry_scope_attach_filew(sentry_scope_t *scope, const wchar_t *path) +{ + size_t path_len = path ? wcslen(path) : 0; + return sentry_scope_attach_filew_n(scope, path, path_len); +} + +sentry_attachment_t * +sentry_scope_attach_filew_n( + sentry_scope_t *scope, const wchar_t *path, size_t path_len) +{ + sentry_path_t *attachment = sentry__path_from_wstr_n(path, path_len); + return sentry__attachments_add( + &scope->attachments, attachment, ATTACHMENT, NULL); +} +#endif diff --git a/src/sentry_scope.h b/src/sentry_scope.h index eaf7eb37d..e9d248c48 100644 --- a/src/sentry_scope.h +++ b/src/sentry_scope.h @@ -3,6 +3,7 @@ #include "sentry_boot.h" +#include "sentry_attachment.h" #include "sentry_session.h" #include "sentry_value.h" @@ -20,6 +21,7 @@ struct sentry_scope_s { sentry_value_t breadcrumbs; sentry_level_t level; sentry_value_t client_sdk; + sentry_attachment_t *attachments; // The span attached to this scope, if any. // diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index d1e0cacf0..2ef6ef4ec 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -289,11 +289,11 @@ def test_crashpad_wer_crash(cmake, httpserver, run_args): "run_args,build_args", [ # if we crash, we want a dump - ([], {"SENTRY_TRANSPORT_COMPRESSION": "Off"}), - ([], {"SENTRY_TRANSPORT_COMPRESSION": "On"}), + (["attachment"], {"SENTRY_TRANSPORT_COMPRESSION": "Off"}), + (["attachment"], {"SENTRY_TRANSPORT_COMPRESSION": "On"}), # if we crash and before-send doesn't discard, we want a dump pytest.param( - ["before-send"], + ["attachment", "before-send"], {}, marks=pytest.mark.skipif( sys.platform == "darwin", @@ -302,13 +302,21 @@ def test_crashpad_wer_crash(cmake, httpserver, run_args): ), # if on_crash() is non-discarding, a discarding before_send() is overruled, so we get a dump pytest.param( - ["discarding-before-send", "on-crash"], + ["attachment", "discarding-before-send", "on-crash"], {}, marks=pytest.mark.skipif( sys.platform == "darwin", reason="crashpad doesn't provide SetFirstChanceExceptionHandler on macOS", ), ), + pytest.param( + ["attach-after-init"], + {}, + marks=pytest.mark.skipif( + sys.platform == "darwin", + reason="crashpad doesn't support dynamic attachments on macOS", + ), + ), ], ) def test_crashpad_dumping_crash(cmake, httpserver, run_args, build_args): @@ -329,7 +337,6 @@ def test_crashpad_dumping_crash(cmake, httpserver, run_args, build_args): [ "log", "start-session", - "attachment", "attach-view-hierarchy", "overflow-breadcrumbs", "crash", diff --git a/tests/test_integration_http.py b/tests/test_integration_http.py index cea0f1940..146d14ec7 100644 --- a/tests/test_integration_http.py +++ b/tests/test_integration_http.py @@ -1173,7 +1173,7 @@ def test_capture_with_scope(cmake, httpserver): run( tmp_path, "sentry_example", - ["log", "attachment", "capture-with-scope"], + ["log", "attach-to-scope", "capture-with-scope"], check=True, env=dict(os.environ, SENTRY_DSN=make_dsn(httpserver)), ) diff --git a/tests/unit/test_attachments.c b/tests/unit/test_attachments.c index 3c2ddbef2..08eb59086 100644 --- a/tests/unit/test_attachments.c +++ b/tests/unit/test_attachments.c @@ -1,5 +1,7 @@ +#include "sentry_attachment.h" #include "sentry_envelope.h" #include "sentry_path.h" +#include "sentry_scope.h" #include "sentry_string.h" #include "sentry_testsupport.h" @@ -88,3 +90,220 @@ SENTRY_TEST(lazy_attachments) TEST_CHECK_INT_EQUAL(testdata.called, 2); } + +SENTRY_TEST(attachments_add_dedupe) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".a.txt"); + sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".b.txt"); + + sentry_init(options); + + sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".a.txt"); + sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".b.txt"); + sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".c.txt"); +#ifdef SENTRY_PLATFORM_WINDOWS + sentry_attach_filew(L".a.txt"); + sentry_attach_filew(L".b.txt"); + sentry_attach_filew(L".c.txt"); +#endif + + sentry_path_t *path_a + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".a.txt"); + sentry_path_t *path_b + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".b.txt"); + sentry_path_t *path_c + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".c.txt"); + + sentry__path_write_buffer(path_a, "aaa", 3); + sentry__path_write_buffer(path_b, "bbb", 3); + sentry__path_write_buffer(path_c, "ccc", 3); + + sentry_envelope_t *envelope = sentry__envelope_new(); + SENTRY_WITH_SCOPE (scope) { + sentry__envelope_add_attachments(envelope, scope->attachments); + } + char *serialized = sentry_envelope_serialize(envelope, NULL); + sentry_envelope_free(envelope); + + TEST_CHECK_STRING_EQUAL(serialized, + "{}\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".a.txt\"}\naaa\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".b.txt\"}\nbbb\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".c.txt\"}" + "\nccc"); + + sentry_free(serialized); + + sentry_shutdown(); + + sentry__path_remove(path_a); + sentry__path_remove(path_b); + sentry__path_remove(path_c); + + sentry__path_free(path_a); + sentry__path_free(path_b); + sentry__path_free(path_c); +} + +SENTRY_TEST(attachments_add_remove) +{ + sentry_options_t *options = sentry_options_new(); + sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".a.txt"); + sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".c.txt"); + sentry_options_add_attachment(options, SENTRY_TEST_PATH_PREFIX ".b.txt"); + + sentry_init(options); + + sentry_attachment_t *attachment_c + = sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".c.txt"); + sentry_attachment_t *attachment_d + = sentry_attach_file(SENTRY_TEST_PATH_PREFIX ".d.txt"); +#ifdef SENTRY_PLATFORM_WINDOWS + sentry_attachment_t *attachment_ew = sentry_attach_filew(L".e.txt"); +#endif + + sentry_remove_attachment(attachment_c); + sentry_remove_attachment(attachment_d); +#ifdef SENTRY_PLATFORM_WINDOWS + sentry_remove_attachment(attachment_ew); +#endif + + sentry_path_t *path_a + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".a.txt"); + sentry_path_t *path_b + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".b.txt"); + sentry_path_t *path_c + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".c.txt"); + + sentry__path_write_buffer(path_a, "aaa", 3); + sentry__path_write_buffer(path_b, "bbb", 3); + sentry__path_write_buffer(path_c, "ccc", 3); + + sentry_envelope_t *envelope; + char *serialized; + + envelope = sentry__envelope_new(); + SENTRY_WITH_SCOPE (scope) { + sentry__envelope_add_attachments(envelope, scope->attachments); + } + serialized = sentry_envelope_serialize(envelope, NULL); + sentry_envelope_free(envelope); + + TEST_CHECK_STRING_EQUAL(serialized, + "{}\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".a.txt\"}\naaa\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".b.txt\"}" + "\nbbb"); + + sentry_free(serialized); + + sentry_shutdown(); + + sentry__path_remove(path_a); + sentry__path_remove(path_b); + sentry__path_remove(path_c); + + sentry__path_free(path_a); + sentry__path_free(path_b); + sentry__path_free(path_c); +} + +SENTRY_TEST(attachments_extend) +{ + SENTRY_TEST_OPTIONS_NEW(options); + sentry_init(options); + + sentry_path_t *path_a + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".a.txt"); + sentry_path_t *path_b + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".b.txt"); + sentry_path_t *path_c + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".c.txt"); + sentry_path_t *path_d + = sentry__path_from_str(SENTRY_TEST_PATH_PREFIX ".d.txt"); + + sentry__path_write_buffer(path_a, "aaa", 3); + sentry__path_write_buffer(path_b, "bbb", 3); + sentry__path_write_buffer(path_c, "ccc", 3); + sentry__path_write_buffer(path_d, "ddd", 3); + + sentry_attachment_t *attachments_abc = NULL; + sentry__attachments_add( + &attachments_abc, sentry__path_clone(path_a), ATTACHMENT, NULL); + sentry__attachments_add( + &attachments_abc, sentry__path_clone(path_b), ATTACHMENT, NULL); + sentry__attachments_add( + &attachments_abc, sentry__path_clone(path_c), ATTACHMENT, NULL); + + sentry_attachment_t *attachments_bcd = NULL; + sentry__attachments_add( + &attachments_bcd, sentry__path_clone(path_b), ATTACHMENT, NULL); + sentry__attachments_add( + &attachments_bcd, sentry__path_clone(path_c), ATTACHMENT, NULL); + sentry__attachments_add( + &attachments_bcd, sentry__path_clone(path_d), ATTACHMENT, NULL); + + sentry_attachment_t *all_attachments = NULL; + sentry__attachments_extend(&all_attachments, attachments_abc); + TEST_CHECK(all_attachments != NULL); + + SENTRY_WITH_SCOPE (scope) { + sentry_envelope_t *envelope = sentry__envelope_new(); + sentry__envelope_add_attachments(envelope, all_attachments); + + char *serialized = sentry_envelope_serialize(envelope, NULL); + + TEST_CHECK_STRING_EQUAL(serialized, + "{}\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".a.txt\"}" + "\naaa\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".b.txt\"}" + "\nbbb\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".c.txt\"}" + "\nccc"); + + sentry_free(serialized); + sentry_envelope_free(envelope); + } + + sentry__attachments_extend(&all_attachments, attachments_bcd); + TEST_CHECK(all_attachments != NULL); + + SENTRY_WITH_SCOPE (scope) { + sentry_envelope_t *envelope = sentry__envelope_new(); + sentry__envelope_add_attachments(envelope, all_attachments); + + char *serialized = sentry_envelope_serialize(envelope, NULL); + + TEST_CHECK_STRING_EQUAL(serialized, + "{}\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".a.txt\"}" + "\naaa\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".b.txt\"}" + "\nbbb\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".c.txt\"}" + "\nccc\n" + "{\"type\":\"attachment\",\"length\":3,\"filename\":\".d.txt\"}" + "\nddd"); + + sentry_free(serialized); + sentry_envelope_free(envelope); + } + + sentry_close(); + + sentry__attachments_free(attachments_abc); + sentry__attachments_free(attachments_bcd); + sentry__attachments_free(all_attachments); + + sentry__path_remove(path_a); + sentry__path_remove(path_b); + sentry__path_remove(path_c); + sentry__path_remove(path_d); + + sentry__path_free(path_a); + sentry__path_free(path_b); + sentry__path_free(path_c); + sentry__path_free(path_d); +} diff --git a/tests/unit/tests.inc b/tests/unit/tests.inc index 048f554c5..9ca44d8ad 100644 --- a/tests/unit/tests.inc +++ b/tests/unit/tests.inc @@ -1,6 +1,9 @@ XX(assert_sdk_name) XX(assert_sdk_user_agent) XX(assert_sdk_version) +XX(attachments_add_dedupe) +XX(attachments_add_remove) +XX(attachments_extend) XX(background_worker) XX(basic_consent_tracking) XX(basic_function_transport)