diff --git a/examples/example.c b/examples/example.c index 505601b1b..afcc5a5f7 100644 --- a/examples/example.c +++ b/examples/example.c @@ -699,7 +699,7 @@ main(int argc, char **argv) } if (has_arg(argc, argv, "cache-keep")) { sentry_options_set_cache_keep(options, true); - sentry_options_set_cache_max_size(options, 4 * 1024 * 1024); // 4 MB + sentry_options_set_cache_max_size(options, 16 * 1024 * 1024); // 16 MB sentry_options_set_cache_max_age(options, 5 * 24 * 60 * 60); // 5 days sentry_options_set_cache_max_items(options, 5); } diff --git a/src/backends/native/sentry_crash_context.h b/src/backends/native/sentry_crash_context.h index 4cb642845..07a484853 100644 --- a/src/backends/native/sentry_crash_context.h +++ b/src/backends/native/sentry_crash_context.h @@ -273,6 +273,11 @@ typedef struct { bool debug_enabled; // Debug logging enabled in parent process bool attach_screenshot; // Screenshot attachment enabled in parent process bool cache_keep; + bool require_user_consent; + + // Atomic user consent (sentry_user_consent_t), updated whenever user + // consent changes so the daemon can honor it at crash time. + volatile long user_consent; // Platform-specific crash context #if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID) diff --git a/src/backends/native/sentry_crash_daemon.c b/src/backends/native/sentry_crash_daemon.c index fea4fe03a..cc304a3de 100644 --- a/src/backends/native/sentry_crash_daemon.c +++ b/src/backends/native/sentry_crash_daemon.c @@ -2909,6 +2909,11 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc) } #endif + if (options->run) { + sentry__atomic_store(&options->run->user_consent, + sentry__atomic_fetch(&ctx->user_consent)); + } + sentry_path_t *env_path = sentry__path_from_str(envelope_path); if (!env_path) { SENTRY_WARN("Failed to create envelope path"); @@ -2923,15 +2928,14 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc) goto cleanup; } - SENTRY_DEBUG("Envelope loaded, sending via transport"); + SENTRY_DEBUG("Envelope loaded, capturing"); - // Send directly via transport, or to external crash reporter + // Capture directly, or pass to external crash reporter if (!sentry__launch_external_crash_reporter(options, envelope)) { - // Send directly via transport - if (options && options->transport) { - SENTRY_DEBUG("Calling transport send_envelope"); - sentry__transport_send_envelope(options->transport, envelope); - SENTRY_DEBUG("Crash envelope sent to transport (queued)"); + if (options && options->transport && options->run) { + SENTRY_DEBUG("Capturing crash envelope"); + sentry__capture_envelope(options->transport, envelope, options); + SENTRY_DEBUG("Crash envelope captured (queued)"); } else { SENTRY_WARN("No transport available for sending envelope"); sentry_envelope_free(envelope); @@ -2952,7 +2956,7 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc) cleanup: // Send all other envelopes from run folder (logs, etc.) before cleanup - if (run_folder && options && options->transport) { + if (run_folder && options && options->transport && options->run) { SENTRY_DEBUG("Checking for additional envelopes in run folder"); sentry_pathiter_t *piter = sentry__path_iter_directory(run_folder); if (piter) { @@ -2969,8 +2973,8 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc) sentry_envelope_t *run_envelope = sentry__envelope_from_path(file_path); if (run_envelope) { - sentry__transport_send_envelope( - options->transport, run_envelope); + sentry__capture_envelope( + options->transport, run_envelope, options); envelope_count++; } else { SENTRY_WARNF("Failed to load envelope: %s", path_str); @@ -3214,6 +3218,10 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle, sentry_path_t *db_path = sentry__path_from_str(ipc->shmem->database_path); if (db_path) { options->run = sentry__run_new(db_path); + if (options->run) { + options->run->require_user_consent + = ipc->shmem->require_user_consent; + } sentry__path_free(db_path); } diff --git a/src/backends/sentry_backend_breakpad.cpp b/src/backends/sentry_backend_breakpad.cpp index db329eb0d..20d9f878f 100644 --- a/src/backends/sentry_backend_breakpad.cpp +++ b/src/backends/sentry_backend_breakpad.cpp @@ -205,7 +205,7 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor, // capture the envelope with the disk transport sentry_transport_t *disk_transport = sentry_new_disk_transport(options->run); - sentry__capture_envelope(disk_transport, envelope); + sentry__capture_envelope(disk_transport, envelope, options); sentry__transport_dump_queue(disk_transport, options->run); sentry_transport_free(disk_transport); } diff --git a/src/backends/sentry_backend_crashpad.cpp b/src/backends/sentry_backend_crashpad.cpp index fa967763f..d308f1903 100644 --- a/src/backends/sentry_backend_crashpad.cpp +++ b/src/backends/sentry_backend_crashpad.cpp @@ -381,7 +381,7 @@ crashpad_handler(int signum, siginfo_t *info, ucontext_t *user_context) // capture the envelope with the disk transport sentry_transport_t *disk_transport = sentry_new_disk_transport(options->run); - sentry__capture_envelope(disk_transport, envelope); + sentry__capture_envelope(disk_transport, envelope, options); sentry__transport_dump_queue(disk_transport, options->run); sentry_transport_free(disk_transport); } diff --git a/src/backends/sentry_backend_inproc.c b/src/backends/sentry_backend_inproc.c index e2b1d97a3..6b7d6b395 100644 --- a/src/backends/sentry_backend_inproc.c +++ b/src/backends/sentry_backend_inproc.c @@ -1206,7 +1206,7 @@ process_ucontext_deferred(const sentry_ucontext_t *uctx, // capture the envelope with the disk transport sentry_transport_t *disk_transport = sentry_new_disk_transport(options->run); - sentry__capture_envelope(disk_transport, envelope); + sentry__capture_envelope(disk_transport, envelope, options); sentry__transport_dump_queue(disk_transport, options->run); sentry_transport_free(disk_transport); } diff --git a/src/backends/sentry_backend_native.c b/src/backends/sentry_backend_native.c index 3a144a30d..4d745ec53 100644 --- a/src/backends/sentry_backend_native.c +++ b/src/backends/sentry_backend_native.c @@ -192,6 +192,9 @@ native_backend_startup( ctx->debug_enabled = options->debug; ctx->attach_screenshot = options->attach_screenshot; ctx->cache_keep = options->cache_keep; + ctx->require_user_consent = options->require_user_consent; + sentry__atomic_store( + &ctx->user_consent, sentry__atomic_fetch(&options->run->user_consent)); // Set up event and breadcrumb paths sentry_path_t *run_path = options->run->run_path; @@ -549,6 +552,21 @@ native_backend_shutdown(sentry_backend_t *backend) SENTRY_DEBUG("native backend shutdown complete"); } +static void +native_backend_user_consent_changed(sentry_backend_t *backend) +{ + native_backend_state_t *state = (native_backend_state_t *)backend->data; + if (!state || !state->ipc || !state->ipc->shmem) { + return; + } + SENTRY_WITH_OPTIONS (options) { + if (options->run) { + sentry__atomic_store(&state->ipc->shmem->user_consent, + sentry__atomic_fetch(&options->run->user_consent)); + } + } +} + static void native_backend_free(sentry_backend_t *backend) { @@ -938,7 +956,8 @@ native_backend_except(sentry_backend_t *backend, const sentry_ucontext_t *uctx) if (disk_transport) { // sentry__capture_envelope takes ownership of // envelope - sentry__capture_envelope(disk_transport, envelope); + sentry__capture_envelope( + disk_transport, envelope, options); sentry__transport_dump_queue( disk_transport, options->run); sentry_transport_free(disk_transport); @@ -985,6 +1004,7 @@ sentry__backend_new(void) backend->flush_scope_func = native_backend_flush_scope; backend->add_breadcrumb_func = native_backend_add_breadcrumb; backend->add_attachment_func = native_backend_add_attachment; + backend->user_consent_changed_func = native_backend_user_consent_changed; backend->can_capture_after_shutdown = false; return backend; diff --git a/src/sentry_core.c b/src/sentry_core.c index ce6a4aa8f..bf70d714c 100644 --- a/src/sentry_core.c +++ b/src/sentry_core.c @@ -482,22 +482,20 @@ sentry_user_consent_is_required(void) } void -sentry__capture_envelope( - sentry_transport_t *transport, sentry_envelope_t *envelope) +sentry__capture_envelope(sentry_transport_t *transport, + sentry_envelope_t *envelope, const sentry_options_t *options) { - if (!sentry__should_skip_upload()) { + if (!sentry__run_should_skip_upload(options->run)) { sentry__transport_send_envelope(transport, envelope); return; } bool cached = false; - SENTRY_WITH_OPTIONS (options) { - if (options->cache_keep || options->http_retry) { - cached = sentry__run_write_cache(options->run, envelope, 0); - if (cached && !sentry__run_should_skip_upload(options->run)) { - // consent given meanwhile -> trigger retry to avoid waiting - // until the next retry poll - sentry_transport_retry(options->transport); - } + if (options->cache_keep || options->http_retry) { + cached = sentry__run_write_cache(options->run, envelope, 0); + if (cached && !sentry__run_should_skip_upload(options->run)) { + // consent given meanwhile -> trigger retry to avoid waiting + // until the next retry poll + sentry_transport_retry(options->transport); } } SENTRY_INFO(cached ? "caching envelope due to missing user consent" @@ -512,7 +510,7 @@ sentry_capture_envelope(sentry_envelope_t *envelope) return; } SENTRY_WITH_OPTIONS (options) { - sentry__capture_envelope(options->transport, envelope); + sentry__capture_envelope(options->transport, envelope, options); } } @@ -621,7 +619,7 @@ sentry__capture_event(sentry_value_t event, sentry_scope_t *local_scope) SENTRY_DATA_CATEGORY_ERROR, 1); sentry_envelope_free(envelope); } else { - sentry__capture_envelope(options->transport, envelope); + sentry__capture_envelope(options->transport, envelope, options); was_sent = true; } } @@ -1630,7 +1628,7 @@ sentry_capture_user_feedback(sentry_value_t user_report) SENTRY_WITH_OPTIONS (options) { envelope = prepare_user_report(user_report); if (envelope) { - sentry__capture_envelope(options->transport, envelope); + sentry__capture_envelope(options->transport, envelope, options); } } sentry_value_decref(user_report); @@ -1652,7 +1650,7 @@ sentry_capture_feedback_with_hint( SENTRY_WITH_OPTIONS (options) { envelope = prepare_user_feedback(user_feedback, hint); if (envelope) { - sentry__capture_envelope(options->transport, envelope); + sentry__capture_envelope(options->transport, envelope, options); } } @@ -1765,7 +1763,7 @@ sentry_capture_minidump_n(const char *path, size_t path_len) sentry__envelope_item_set_header(item, "filename", sentry_value_new_string(sentry__path_filename(dump_path))); - sentry__capture_envelope(options->transport, envelope); + sentry__capture_envelope(options->transport, envelope, options); SENTRY_INFOF( "Minidump has been captured: \"%s\"", dump_path->path); diff --git a/src/sentry_core.h b/src/sentry_core.h index 0c0b03e87..c0b6f60fb 100644 --- a/src/sentry_core.h +++ b/src/sentry_core.h @@ -93,8 +93,8 @@ sentry_envelope_t *sentry__prepare_transaction(const sentry_options_t *options, * This function will submit the `envelope` to the given `transport`, first * checking for consent. */ -void sentry__capture_envelope( - sentry_transport_t *transport, sentry_envelope_t *envelope); +void sentry__capture_envelope(sentry_transport_t *transport, + sentry_envelope_t *envelope, const sentry_options_t *options); /** * Generates a new random UUID for events. diff --git a/src/sentry_database.c b/src/sentry_database.c index 6c947a591..67f121ddd 100644 --- a/src/sentry_database.c +++ b/src/sentry_database.c @@ -451,7 +451,7 @@ sentry__process_old_runs(const sentry_options_t *options, uint64_t last_crash) sentry__session_free(session); if ((++session_num) >= SENTRY_MAX_ENVELOPE_SESSIONS) { sentry__capture_envelope( - options->transport, session_envelope); + options->transport, session_envelope, options); session_envelope = NULL; session_num = 0; } @@ -459,7 +459,8 @@ sentry__process_old_runs(const sentry_options_t *options, uint64_t last_crash) } else if (sentry__path_ends_with(file, ".envelope")) { sentry_envelope_t *envelope = sentry__envelope_from_path(file); if (envelope) { - sentry__capture_envelope(options->transport, envelope); + sentry__capture_envelope( + options->transport, envelope, options); } } @@ -473,7 +474,7 @@ sentry__process_old_runs(const sentry_options_t *options, uint64_t last_crash) sentry__pathiter_free(db_iter); if (session_envelope) { - sentry__capture_envelope(options->transport, session_envelope); + sentry__capture_envelope(options->transport, session_envelope, options); } } diff --git a/src/sentry_session.c b/src/sentry_session.c index af1d7a0f6..ccd73582a 100644 --- a/src/sentry_session.c +++ b/src/sentry_session.c @@ -268,7 +268,7 @@ sentry__capture_session(sentry_session_t *session) sentry__envelope_add_session(envelope, session); SENTRY_WITH_OPTIONS (options) { - sentry__capture_envelope(options->transport, envelope); + sentry__capture_envelope(options->transport, envelope, options); } } diff --git a/tests/test_integration_cache.py b/tests/test_integration_cache.py index 88dd340f6..c0b5b46e1 100644 --- a/tests/test_integration_cache.py +++ b/tests/test_integration_cache.py @@ -94,14 +94,14 @@ def test_cache_max_size(cmake, backend, unreachable_dsn): env=env, ) - # 5 x 2mb + # 5 x 4mb assert cache_dir.exists() cache_files = list(cache_dir.glob("*.envelope")) for f in cache_files: with open(f, "r+b") as file: - file.truncate(2 * 1024 * 1024) + file.truncate(4 * 1024 * 1024) - # max 4mb + # max 16mb run( tmp_path, "sentry_example", @@ -110,8 +110,8 @@ def test_cache_max_size(cmake, backend, unreachable_dsn): ) cache_files = list(cache_dir.glob("*.envelope")) - assert len(cache_files) <= 2 - assert sum(f.stat().st_size for f in cache_files) <= 4 * 1024 * 1024 + assert len(cache_files) <= 4 + assert sum(f.stat().st_size for f in cache_files) <= 16 * 1024 * 1024 @pytest.mark.parametrize( diff --git a/tests/test_integration_crashpad.py b/tests/test_integration_crashpad.py index adaf3ccff..c6413f41f 100644 --- a/tests/test_integration_crashpad.py +++ b/tests/test_integration_crashpad.py @@ -871,7 +871,7 @@ def test_crashpad_cache_max_size(cmake, httpserver): cache_dir = tmp_path.joinpath(".sentry-native/cache") env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) - # 5 x 2mb + # 5 x 4mb for i in range(5): httpserver.expect_oneshot_request("/api/123456/minidump/").respond_with_data( "OK" @@ -906,7 +906,7 @@ def test_crashpad_cache_max_size(cmake, httpserver): if cache_dir.exists(): for f in cache_dir.glob("*.envelope"): with open(f, "r+b") as file: - file.truncate(2 * 1024 * 1024) + file.truncate(4 * 1024 * 1024) run( tmp_path, @@ -915,11 +915,11 @@ def test_crashpad_cache_max_size(cmake, httpserver): env=env, ) - # max 4mb + # max 16mb assert cache_dir.exists() cache_files = list(cache_dir.glob("*.envelope")) - assert len(cache_files) <= 2 - assert sum(f.stat().st_size for f in cache_files) <= 4 * 1024 * 1024 + assert len(cache_files) <= 4 + assert sum(f.stat().st_size for f in cache_files) <= 16 * 1024 * 1024 def test_crashpad_cache_max_age(cmake, httpserver): diff --git a/tests/test_integration_native.py b/tests/test_integration_native.py index 5f5870a6f..c0def027b 100644 --- a/tests/test_integration_native.py +++ b/tests/test_integration_native.py @@ -551,6 +551,52 @@ def test_crash_mode_native_with_minidump(cmake, httpserver): assert "debug_meta" in event +def test_native_cache_consent(cmake, httpserver): + """Daemon honors revoked consent: envelope is cached, not sent. Giving + consent in a subsequent run flushes the cached envelope.""" + tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "native"}) + cache_dir = tmp_path / ".sentry-native" / "cache" + env = dict(os.environ, SENTRY_DSN=make_dsn(httpserver)) + + # 1) Crash while consent is revoked. Envelope is cached, no upload. + run_crash( + tmp_path, + "sentry_example", + [ + "log", + "stdout", + "cache-keep", + "http-retry", + "require-user-consent", + "user-consent-revoke", + "crash", + ], + env=env, + ) + + assert wait_for_file(cache_dir / "*.envelope") + assert len(list(cache_dir.glob("*.envelope"))) == 1 + assert len(httpserver.log) == 0 + + # 2) Give consent. The cached envelope should be flushed to the server. + httpserver.expect_oneshot_request("/api/123456/envelope/").respond_with_data("OK") + with httpserver.wait(timeout=10) as waiting: + run( + tmp_path, + "sentry_example", + [ + "log", + "cache-keep", + "http-retry", + "require-user-consent", + "user-consent-give", + ], + env=env, + ) + assert waiting.result + assert len(list(cache_dir.glob("*.envelope"))) == 0 + + @pytest.mark.parametrize("cache_keep", [True, False]) def test_native_cache_keep(cmake, cache_keep, unreachable_dsn): tmp_path = cmake(["sentry_example"], {"SENTRY_BACKEND": "native"})