Skip to content
Draft
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,14 @@ endif()
include(CheckTypeSize)
check_type_size("long" CMAKE_SIZEOF_LONG)

if(LINUX)
include(CheckSymbolExists)
check_symbol_exists(copy_file_range "unistd.h" HAVE_COPY_FILE_RANGE)
if(HAVE_COPY_FILE_RANGE)
target_compile_definitions(sentry PRIVATE SENTRY_HAVE_COPY_FILE_RANGE)
endif()
endif()

# https://gitlab.kitware.com/cmake/cmake/issues/18393
if(SENTRY_BUILD_SHARED_LIBS)
if(APPLE)
Expand Down
19 changes: 19 additions & 0 deletions examples/example.c
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,25 @@ main(int argc, char **argv)
sentry_options_add_attachment(options, "./CMakeCache.txt");
}

if (has_arg(argc, argv, "large-attachment")) {
const char *large_file = ".sentry-large-attachment";
FILE *f = fopen(large_file, "wb");
if (f) {
// 100 MB = TUS upload threshold
char zeros[4096];
memset(zeros, 0, sizeof(zeros));
size_t remaining = 100 * 1024 * 1024;
while (remaining > 0) {
size_t chunk
= remaining < sizeof(zeros) ? remaining : sizeof(zeros);
fwrite(zeros, 1, chunk, f);
remaining -= chunk;
}
fclose(f);
sentry_options_add_attachment(options, large_file);
}
}

if (has_arg(argc, argv, "stdout")) {
sentry_options_set_transport(
options, sentry_transport_new(print_envelope));
Expand Down
128 changes: 126 additions & 2 deletions src/backends/native/sentry_crash_daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "minidump/sentry_minidump_writer.h"
#include "sentry_alloc.h"
#include "sentry_attachment.h"
#include "sentry_core.h"
#include "sentry_crash_ipc.h"
#include "sentry_database.h"
Expand Down Expand Up @@ -195,6 +196,123 @@ write_attachment_to_envelope(int fd, const char *file_path,
return true;
}

// Returns true if the attachment at `file_path` should be staged to the cache
// and emitted as an attachment-ref item rather than inlined into the envelope.
static bool
attachment_is_external(const char *file_path)
{
sentry_path_t *src = sentry__path_from_str(file_path);
size_t file_size = src ? sentry__path_get_size(src) : 0;
sentry__path_free(src);
return file_size >= SENTRY_LARGE_ATTACHMENT_SIZE;
}

// Stage `src_path` as a sibling of the cached envelope at
// <db>/cache/<event_id>-<filename> (with `-N` collision suffix). Returns the
// chosen basename (caller frees) or NULL on failure.
static char *
stage_external_attachment(const char *src_path, const char *filename,
const char *event_id, const char *db_dir)
{
char cache_dir_buf[SENTRY_CRASH_MAX_PATH];
int n = snprintf(cache_dir_buf, sizeof(cache_dir_buf), "%s/cache", db_dir);
if (n <= 0 || (size_t)n >= sizeof(cache_dir_buf)) {
return NULL;
}
sentry_path_t *cache_path = sentry__path_from_str(cache_dir_buf);
if (!cache_path || sentry__path_create_dir_all(cache_path) != 0) {
sentry__path_free(cache_path);
return NULL;
}
char buf[256];
snprintf(buf, sizeof(buf), "%s-%s", event_id, filename);
char *basename = sentry__path_unique_name(cache_path, buf);
if (!basename) {
sentry__path_free(cache_path);
return NULL;
}
sentry_path_t *dst = sentry__path_join_str(cache_path, basename);
sentry__path_free(cache_path);
sentry_path_t *src = sentry__path_from_str(src_path);
int rv = (src && dst) ? sentry__path_copy(src, dst) : -1;
sentry__path_free(src);
sentry__path_free(dst);
if (rv != 0) {
sentry_free(basename);
return NULL;
}
return basename;
}

// For each large attachment listed in `<run_folder>/__sentry-attachments`,
// stage it to <db>/cache and append an `attachment-ref` item to `envelope`.
// The transport then uploads via TUS at send time. Small attachments were
// already inlined during envelope writing.
static void
add_external_attachment_refs(sentry_envelope_t *envelope,
const sentry_path_t *run_folder, const char *db_dir)
{
if (!envelope || !run_folder || !db_dir) {
return;
}
sentry_path_t *attach_list_path
= sentry__path_join_str(run_folder, "__sentry-attachments");
if (!attach_list_path) {
return;
}
size_t attach_json_len = 0;
char *attach_json
= sentry__path_read_to_buffer(attach_list_path, &attach_json_len);
sentry__path_free(attach_list_path);
if (!attach_json) {
return;
}
sentry_value_t list = attach_json_len > 0
? sentry__value_from_json(attach_json, attach_json_len)
: sentry_value_new_null();
sentry_free(attach_json);
if (sentry_value_is_null(list)) {
return;
}

sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope);
if (sentry_uuid_is_nil(&event_id)) {
sentry_value_decref(list);
return;
}
char event_id_str[37];
sentry_uuid_as_string(&event_id, event_id_str);

size_t len = sentry_value_get_length(list);
for (size_t i = 0; i < len; i++) {
sentry_value_t info = sentry_value_get_by_index(list, i);
const char *path
= sentry_value_as_string(sentry_value_get_by_key(info, "path"));
const char *filename
= sentry_value_as_string(sentry_value_get_by_key(info, "filename"));
const char *content_type = sentry_value_as_string(
sentry_value_get_by_key(info, "content_type"));
if (!path || !*path || !filename || !*filename
|| !attachment_is_external(path)) {
continue;
}
sentry_path_t *src = sentry__path_from_str(path);
size_t file_size = src ? sentry__path_get_size(src) : 0;
sentry__path_free(src);
char *basename
= stage_external_attachment(path, filename, event_id_str, db_dir);
if (!basename) {
SENTRY_WARNF("Failed to stage large attachment: %s", path);
continue;
}
sentry__envelope_add_attachment_ref(envelope, basename, NULL, filename,
(content_type && *content_type) ? content_type : NULL, ATTACHMENT,
sentry_value_new_uint64((uint64_t)file_size));
sentry_free(basename);
}
sentry_value_decref(list);
}

#if defined(SENTRY_PLATFORM_UNIX)
/**
* Get signal name from signal number (Unix platforms only)
Expand Down Expand Up @@ -2382,7 +2500,7 @@ write_envelope_with_native_stacktrace(const sentry_options_t *options,
const char *content_type
= sentry_value_as_string(content_type_val);

if (path && filename) {
if (path && filename && !attachment_is_external(path)) {
write_attachment_to_envelope(
fd, path, filename, content_type);
}
Expand Down Expand Up @@ -2617,7 +2735,7 @@ write_envelope_with_minidump(const sentry_options_t *options,
const char *content_type
= sentry_value_as_string(content_type_val);

if (path && filename) {
if (path && filename && !attachment_is_external(path)) {
write_attachment_to_envelope(
fd, path, filename, content_type);
}
Expand Down Expand Up @@ -2923,6 +3041,8 @@ sentry__process_crash(const sentry_options_t *options, sentry_crash_ipc_t *ipc)
goto cleanup;
}

add_external_attachment_refs(envelope, run_folder, db_dir);

SENTRY_DEBUG("Envelope loaded, sending via transport");

// Send directly via transport, or to external crash reporter
Expand Down Expand Up @@ -3072,6 +3192,7 @@ sentry__crash_daemon_main(pid_t app_pid, uint64_t app_tid, HANDLE event_handle,
HANDLE ready_event_handle)
#endif
{
SENTRY_SIGNAL_SAFE_LOG("### daemon: main enter");
// Initialize IPC first (attach to shared memory created by parent)
// We need this to get the database path for logging
#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID)
Expand Down Expand Up @@ -3427,12 +3548,14 @@ sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle,

#elif defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_ANDROID)
// Linux: Use fork+exec
SENTRY_SIGNAL_SAFE_LOG("### daemon_start: before fork");
pid_t daemon_pid = fork();

if (daemon_pid < 0) {
SENTRY_WARN("Failed to fork daemon process");
return -1;
} else if (daemon_pid == 0) {
SENTRY_SIGNAL_SAFE_LOG("### daemon: child before exec");
// Child process - exec sentry-crash
setsid();

Expand Down Expand Up @@ -3485,6 +3608,7 @@ sentry__crash_daemon_start(pid_t app_pid, uint64_t app_tid, HANDLE event_handle,
}

// Parent process - return daemon PID
SENTRY_SIGNAL_SAFE_LOG("### daemon_start: parent after fork");
return daemon_pid;

#elif defined(SENTRY_PLATFORM_WINDOWS)
Expand Down
46 changes: 44 additions & 2 deletions src/backends/native/sentry_crash_ipc.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
sentry_crash_ipc_t *
sentry__crash_ipc_init_app(sem_t *init_sem)
{
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: enter");
sentry_crash_ipc_t *ipc = SENTRY_MAKE(sentry_crash_ipc_t);
if (!ipc) {
return NULL;
Expand All @@ -36,14 +37,17 @@ sentry__crash_ipc_init_app(sem_t *init_sem)
snprintf(ipc->shm_name, sizeof(ipc->shm_name), "/s-%08x", id);

// Acquire semaphore for exclusive access during initialization
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: before sem_wait");
if (ipc->init_sem && sem_wait(ipc->init_sem) < 0) {
SENTRY_WARNF(
"failed to acquire initialization semaphore: %s", strerror(errno));
sentry_free(ipc);
return NULL;
}
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after sem_wait");

// Try to create or open shared memory
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: before shm_open");
bool shm_exists = false;
ipc->shm_fd = shm_open(ipc->shm_name, O_CREAT | O_RDWR | O_EXCL, 0600);
if (ipc->shm_fd < 0 && errno == EEXIST) {
Expand All @@ -60,6 +64,7 @@ sentry__crash_ipc_init_app(sem_t *init_sem)
sentry_free(ipc);
return NULL;
}
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after shm_open");

// Verify and resize shared memory (both new and existing)
if (shm_exists) {
Expand Down Expand Up @@ -101,7 +106,10 @@ sentry__crash_ipc_init_app(sem_t *init_sem)
}
}

SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after ftruncate");

// Map shared memory
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: before mmap");
ipc->shmem = mmap(NULL, SENTRY_CRASH_SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, ipc->shm_fd, 0);
if (ipc->shmem == MAP_FAILED) {
Expand All @@ -116,8 +124,10 @@ sentry__crash_ipc_init_app(sem_t *init_sem)
sentry_free(ipc);
return NULL;
}
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after mmap");

// Create eventfd for crash notifications
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: before eventfd(notify)");
ipc->notify_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
if (ipc->notify_fd < 0) {
SENTRY_WARNF("failed to create eventfd: %s", strerror(errno));
Expand All @@ -133,7 +143,10 @@ sentry__crash_ipc_init_app(sem_t *init_sem)
return NULL;
}

SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after eventfd(notify)");

// Create eventfd for daemon ready signal
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: before eventfd(ready)");
ipc->ready_fd = eventfd(0, EFD_CLOEXEC);
if (ipc->ready_fd < 0) {
SENTRY_WARNF("failed to create ready eventfd: %s", strerror(errno));
Expand All @@ -149,24 +162,49 @@ sentry__crash_ipc_init_app(sem_t *init_sem)
sentry_free(ipc);
return NULL;
}
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after eventfd(ready)");

// Initialize shared memory only if newly created
if (!shm_exists) {
memset(ipc->shmem, 0, SENTRY_CRASH_SHM_SIZE);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: memset chunk 1/8");
size_t chunk = SENTRY_CRASH_SHM_SIZE / 8;
memset((char *)ipc->shmem + 0 * chunk, 0, chunk);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: memset chunk 2/8");
memset((char *)ipc->shmem + 1 * chunk, 0, chunk);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: memset chunk 3/8");
memset((char *)ipc->shmem + 2 * chunk, 0, chunk);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: memset chunk 4/8");
memset((char *)ipc->shmem + 3 * chunk, 0, chunk);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: memset chunk 5/8");
memset((char *)ipc->shmem + 4 * chunk, 0, chunk);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: memset chunk 6/8");
memset((char *)ipc->shmem + 5 * chunk, 0, chunk);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: memset chunk 7/8");
memset((char *)ipc->shmem + 6 * chunk, 0, chunk);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: memset chunk 8/8");
memset((char *)ipc->shmem + 7 * chunk, 0,
SENTRY_CRASH_SHM_SIZE - 7 * chunk);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after memset");
ipc->shmem->magic = SENTRY_CRASH_MAGIC;
ipc->shmem->version = SENTRY_CRASH_VERSION;
sentry__atomic_store(&ipc->shmem->state, SENTRY_CRASH_STATE_READY);
sentry__atomic_store(&ipc->shmem->sequence, 0);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after atomic stores");
}

// Release semaphore after initialization
if (ipc->init_sem) {
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: before sem_post");
sem_post(ipc->init_sem);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after sem_post");
}

SENTRY_SIGNAL_SAFE_LOG("### ipc_app: before DEBUGF");
SENTRY_DEBUGF("initialized crash IPC (shm=%s, notify_fd=%d)", ipc->shm_name,
ipc->notify_fd);
SENTRY_SIGNAL_SAFE_LOG("### ipc_app: after DEBUGF");

SENTRY_SIGNAL_SAFE_LOG("### ipc_app: return");
return ipc;
}

Expand Down Expand Up @@ -283,7 +321,11 @@ sentry__crash_ipc_free(sentry_crash_ipc_t *ipc)
close(ipc->shm_fd);
}

if (!ipc->is_daemon && ipc->shm_name[0]) {
// Always attempt shm_unlink: on normal shutdown the app is the first to
// free IPC and does the unlink; if the app crashes before shutdown, the
// daemon outlives it and is the last owner, so it must unlink instead.
// The second unlink (by whichever side runs later) is a harmless ENOENT.
if (ipc->shm_name[0]) {
shm_unlink(ipc->shm_name);
}

Expand Down
10 changes: 10 additions & 0 deletions src/backends/sentry_backend_breakpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,16 @@ breakpad_backend_callback(const google_breakpad::MinidumpDescriptor &descriptor,

sentry__envelope_item_set_header(item, "filename",
sentry_value_new_string(sentry__path_filename(dump_path)));
} else {
sentry_uuid_t event_id
= sentry__envelope_get_event_id(envelope);
sentry_attachment_t tmp;
memset(&tmp, 0, sizeof(tmp));
tmp.path = dump_path;
tmp.type = MINIDUMP;
tmp.next = nullptr;
sentry__cache_external_attachments(envelope, &tmp,
options->run->cache_path, &event_id, nullptr);
}

if (capture_screenshot) {
Expand Down
6 changes: 6 additions & 0 deletions src/backends/sentry_backend_crashpad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,12 @@ report_to_envelope(const crashpad::CrashReportDatabase::Report &report,

if (sentry__envelope_add_event(envelope, event)) {
sentry__envelope_add_attachments(envelope, attachments);
sentry_uuid_t event_id = sentry__envelope_get_event_id(envelope);
if (options->run) {
sentry__cache_external_attachments(envelope, attachments,
options->run->cache_path, &event_id,
options->run->run_path);
}
} else {
sentry_value_decref(event);
sentry_envelope_free(envelope);
Expand Down
Loading
Loading