Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

**Features**:

- Windows: report WINE and Proton metadata in a separate runtime context. ([#1995](https://github.com/getsentry/sentry-native/pull/1995))
- Add a public custom HTTP transport client interface (`sentry_http_transport_new`) so applications can plug in their own HTTP client (e.g. platform-native ones) while sentry-native continues to own request queueing, retry/backoff, offline caching, rate-limiting, and client reports. The built-in curl and WinHTTP transports are now implemented against this same interface. ([#1987](https://github.com/getsentry/sentry-native/pull/1987))
- Native/Windows: capture WER report ID and expose as `contexts.wer.report_id` in crash events when the WER integration is enabled. ([#1970](https://github.com/getsentry/sentry-native/pull/1970))
- Add `sentry_set_tags` and `sentry_scope_set_tags` for updating multiple tags with a single scope flush, improving bulk-update performance. ([#1993](https://github.com/getsentry/sentry-native/pull/1993))
Expand Down
199 changes: 199 additions & 0 deletions src/sentry_os.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "sentry_os.h"
#include "sentry_path.h"
#include "sentry_slice.h"
#include "sentry_string.h"
#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_WINDOWS)
Expand Down Expand Up @@ -178,6 +179,204 @@ sentry__get_windows_version(windows_version_t *win_ver)
return 1;
}

static bool
string_ends_with(const char *value, size_t value_len, const char *suffix)
{
const size_t suffix_len = strlen(suffix);
return value_len >= suffix_len
&& memcmp(value + value_len - suffix_len, suffix, suffix_len) == 0;
}

static char *
make_wine_path(const char *path, const char *suffix)
{
sentry_stringbuilder_t sb;
sentry__stringbuilder_init(&sb);
if (sentry__stringbuilder_append(&sb, "Z:")
|| sentry__stringbuilder_append(&sb, path)
|| sentry__stringbuilder_append(&sb, suffix)) {
sentry__stringbuilder_cleanup(&sb);
return NULL;
}
return sentry__stringbuilder_into_string(&sb);
}

static char *
read_wine_file(const char *path)
{
sentry_path_t *file_path = sentry__path_from_str(path);
char *contents
= file_path ? sentry__path_read_to_buffer(file_path, NULL) : NULL;
sentry__path_free(file_path);
return contents;
}

static char *
get_proton_version(bool *is_proton)
{
*is_proton = false;
char *compat_path
= sentry__string_from_wstr(_wgetenv(L"STEAM_COMPAT_DATA_PATH"));
if (!compat_path || !compat_path[0]) {
sentry_free(compat_path);
return NULL;
}

char *config_path = make_wine_path(compat_path, "/config_info");
Comment thread
cursor[bot] marked this conversation as resolved.
sentry_free(compat_path);
char *config = config_path ? read_wine_file(config_path) : NULL;
sentry_free(config_path);
if (!config) {
return NULL;
}

char *fonts_path = strchr(config, '\n');
if (!fonts_path) {
sentry_free(config);
return NULL;
}
fonts_path++;
while (*fonts_path == ' ' || *fonts_path == '\t') {
fonts_path++;
}
size_t fonts_path_len = strcspn(fonts_path, "\r\n");
while (fonts_path_len > 0
&& (fonts_path[fonts_path_len - 1] == ' '
|| fonts_path[fonts_path_len - 1] == '\t')) {
fonts_path_len--;
}

const char *fonts_suffix = NULL;
if (string_ends_with(fonts_path, fonts_path_len, "/files/share/fonts/")) {
fonts_suffix = "/files/share/fonts/";
} else if (string_ends_with(
fonts_path, fonts_path_len, "/dist/share/fonts/")) {
fonts_suffix = "/dist/share/fonts/";
}
if (!fonts_suffix) {
sentry_free(config);
return NULL;
}

const size_t root_len = fonts_path_len - strlen(fonts_suffix);
char *proton_root = sentry__string_clone_n(fonts_path, root_len);
sentry_free(config);
if (!proton_root) {
return NULL;
}

char *proton_path = make_wine_path(proton_root, "/proton");
sentry_path_t *proton_file = sentry__path_from_str(proton_path);
*is_proton = proton_file && sentry__path_is_file(proton_file);
sentry__path_free(proton_file);
sentry_free(proton_path);

char *version_path = make_wine_path(proton_root, "/version");
sentry_free(proton_root);
char *version_file = version_path ? read_wine_file(version_path) : NULL;
sentry_free(version_path);
if (!version_file) {
return NULL;
}

char *version = strpbrk(version_file, " \t");
if (!version) {
sentry_free(version_file);
return NULL;
}
while (*version == ' ' || *version == '\t') {
version++;
}
size_t version_len = strcspn(version, "\r\n");
while (version_len > 0
&& (version[version_len - 1] == ' '
|| version[version_len - 1] == '\t')) {
version_len--;
}

char *result
= version_len ? sentry__string_clone_n(version, version_len) : NULL;
sentry_free(version_file);
return result;
}

static bool
string_starts_with(const char *value, const char *prefix)
{
const size_t value_len = strlen(value);
const size_t prefix_len = strlen(prefix);
return value_len >= prefix_len && memcmp(value, prefix, prefix_len) == 0;
}

sentry_value_t
sentry__make_wine_context(sentry__wine_get_version_t wine_get_version,
const char *proton_version, bool is_proton)
{
if (!wine_get_version) {
return sentry_value_new_null();
}

const char *runtime_name = "Wine";
const char *runtime_version = wine_get_version();
if (!runtime_version || !runtime_version[0]) {
return sentry_value_new_null();
}

if (proton_version && proton_version[0]) {
runtime_version = proton_version;
if (is_proton) {
if (string_starts_with(proton_version, "proton-")) {
runtime_name = "Proton";
runtime_version += strlen("proton-");
} else if (string_starts_with(proton_version, "experimental-")) {
runtime_name = "Proton Experimental";
runtime_version += strlen("experimental-");
} else if (string_starts_with(proton_version, "GE-Proton")) {
runtime_name = "GE-Proton";
runtime_version += strlen("GE-Proton");
Comment thread
sentry[bot] marked this conversation as resolved.
} else if (string_starts_with(proton_version, "hotfix-")) {
runtime_name = "Proton Hotfix";
runtime_version += strlen("hotfix-");
} else {
runtime_name = "Proton Custom";
}
}
}

sentry_value_t context = sentry_value_new_object();
if (sentry_value_is_null(context)) {
return context;
}
sentry_value_set_by_key(
context, "type", sentry_value_new_string("runtime"));
sentry_value_set_by_key(
context, "name", sentry_value_new_string(runtime_name));
if (runtime_version[0]) {
sentry_value_set_by_key(
context, "version", sentry_value_new_string(runtime_version));
}
sentry_value_freeze(context);
return context;
}

sentry_value_t
sentry__get_wine_context(void)
{
const HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) {
return sentry_value_new_null();
}

const sentry__wine_get_version_t wine_get_version
= (sentry__wine_get_version_t)GetProcAddress(ntdll, "wine_get_version");
bool is_proton = false;
char *proton_version = get_proton_version(&is_proton);
sentry_value_t context = sentry__make_wine_context(
wine_get_version, proton_version, is_proton);
sentry_free(proton_version);
return context;
}

# endif // !defined(SENTRY_PLATFORM_XBOX)

sentry_value_t
Expand Down
10 changes: 10 additions & 0 deletions src/sentry_os.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ typedef struct {

int sentry__get_kernel_version(windows_version_t *win_ver);
int sentry__get_windows_version(windows_version_t *win_ver);

# if !defined(SENTRY_PLATFORM_XBOX)
typedef const char *(CDECL *sentry__wine_get_version_t)(void);

sentry_value_t sentry__make_wine_context(
sentry__wine_get_version_t wine_get_version, const char *proton_version,
bool is_proton);
sentry_value_t sentry__get_wine_context(void);
# endif

void sentry__set_default_thread_stack_guarantee(void);
void sentry__init_cached_kernel32_functions(void);
void sentry__get_system_time(LPFILETIME filetime);
Expand Down
8 changes: 8 additions & 0 deletions src/sentry_scope.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ get_scope(void)
init_scope(&g_scope);
g_scope.user = sentry_value_new_object();
sentry_value_set_by_key(g_scope.contexts, "os", sentry__get_os_context());
#if defined(SENTRY_PLATFORM_WINDOWS) && !defined(SENTRY_PLATFORM_XBOX)
sentry_value_t wine_context = sentry__get_wine_context();
if (!sentry_value_is_null(wine_context)) {
sentry_value_set_by_key(g_scope.contexts, "wine", wine_context);
} else {
sentry_value_decref(wine_context);
}
#endif
g_scope.client_sdk = get_client_sdk();

g_scope_initialized = true;
Expand Down
114 changes: 114 additions & 0 deletions tests/unit/test_os.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,21 @@ extern void(WINAPI *g_kernel32_GetCurrentThreadStackLimits)(
PULONG_PTR, PULONG_PTR);
static size_t g_kernel32_SetThreadStackGuaranteeCalled = 0;

# if !defined(SENTRY_PLATFORM_XBOX)
static const char *CDECL
wine_get_version(void)
{
return "9.0";
}

static const char *CDECL
wine_get_empty_version(void)
{
return "";
}

# endif

static BOOL WINAPI
no_previous_guarantee(PULONG guarantee)
{
Expand Down Expand Up @@ -295,6 +310,105 @@ stack_reserve_exact_factor_minus_one(PULONG_PTR low, PULONG_PTR high)
}
#endif

SENTRY_TEST(wine_context)
{
#if !defined(SENTRY_PLATFORM_WINDOWS) || defined(SENTRY_PLATFORM_XBOX)
SKIP_TEST();
#else
sentry_value_t context
= sentry__make_wine_context(wine_get_version, NULL, false);
TEST_CHECK(!sentry_value_is_null(context));
TEST_CHECK(sentry_value_is_frozen(context));
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "type")),
"runtime");
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "name")),
"Wine");
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "version")),
"9.0");
sentry_value_decref(context);

context
= sentry__make_wine_context(wine_get_version, "proton-10.0-4", true);
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "name")),
"Proton");
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "version")),
"10.0-4");
sentry_value_decref(context);

context = sentry__make_wine_context(
wine_get_version, "experimental-10.0-20260113", true);
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "name")),
"Proton Experimental");
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "version")),
"10.0-20260113");
sentry_value_decref(context);

context
= sentry__make_wine_context(wine_get_version, "GE-Proton9-27", true);
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "name")),
"GE-Proton");
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "version")),
"9-27");
sentry_value_decref(context);

context
= sentry__make_wine_context(wine_get_version, "GE-Proton", true);
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "name")),
"GE-Proton");
TEST_CHECK(sentry_value_is_null(
sentry_value_get_by_key(context, "version")));
sentry_value_decref(context);

context
= sentry__make_wine_context(wine_get_version, "hotfix-20251031", true);
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "name")),
"Proton Hotfix");
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "version")),
"20251031");
sentry_value_decref(context);

context
= sentry__make_wine_context(wine_get_version, "custom-tool-1", true);
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "name")),
"Proton Custom");
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "version")),
"custom-tool-1");
sentry_value_decref(context);

context
= sentry__make_wine_context(wine_get_version, "wine-ge-8-26", false);
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "name")),
"Wine");
TEST_CHECK_STRING_EQUAL(
sentry_value_as_string(sentry_value_get_by_key(context, "version")),
"wine-ge-8-26");
sentry_value_decref(context);

context = sentry__make_wine_context(NULL, NULL, false);
TEST_CHECK(sentry_value_is_null(context));
sentry_value_decref(context);

context = sentry__make_wine_context(wine_get_empty_version, NULL, false);
TEST_CHECK(sentry_value_is_null(context));
sentry_value_decref(context);
#endif
}

SENTRY_TEST(stack_guarantee)
{
#if !defined(SENTRY_PLATFORM_WINDOWS)
Expand Down
1 change: 1 addition & 0 deletions tests/unit/tests.inc
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ XX(value_uint64)
XX(value_unicode)
XX(value_user)
XX(value_wrong_type)
XX(wine_context)
XX(write_envelope_partial_write_fails)
XX(write_raw_envelope_to_file)
XX(writer_byte_count_stops_after_failure)
Expand Down