Skip to content

Commit 3baf0c7

Browse files
feat: add linux distributions to os context (#963)
* feat: add linux distributions to os context * Clean up test-allocations like a good boy * separate os-dependent unit-test sources * platform-specfic testing is decided in the translation-units * Exclude Android too * Add documented fallback * Ensure we exhausted the snapshot * DeMorgan * format * add valgrind exception for false positive when memmove is an ifunc resolving to memcpy * Make sure that distributions are only added in Linux not any Unix * Make sure we don't leak in the error paths * Clean up the fall-back from /etc/os-release to /usr/lib/os-release * attribute derivative work * apply PR feedback * Extract slice to buffer handling in a new sentry_slice_t function. * Make sure we can read a full line of max value and max key. This is a hugely hypothetical target, since the max values are far beyond all the test data we currently observed, but still. * fix classic buffer continuity bug. * extract parse_line_into_object... ...to separate parsing/output from buffer management. * ensure line update if we hit the buffer-end correctly. * add support for os-release files that don't end with a newline. * Add comments to the subtler buffer management aspects. * Add a list of tested distribution names for documentation * Add back eof-newline to valgrind exceptions. * Adapt test to new entry in fixture. * include sentry_utils in sentry_os only on Linux * Ensure we find close() in unistd.h by including it explicitly. * include sentry_utils in sentry_os on Windows too * use unistd.h in sentry_os only for Linux * flattened distribution payload fields * format * incref for get_by_key calls * decref no longer copied os_dist --------- Co-authored-by: JoshuaMoelans <60878493+JoshuaMoelans@users.noreply.github.com>
1 parent 055465f commit 3baf0c7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+1684
-3
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115

116116
**Features**:
117117

118+
- Add Linux distributions to the OS context. [#963](https://github.com/getsentry/sentry-native/pull/963)
118119
- Change the timestamp resolution to microseconds. ([#995](https://github.com/getsentry/sentry-native/pull/995))
119120

120121
**Internal**:

src/modulefinder/sentry_modulefinder_linux.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "sentry_path.h"
88
#include "sentry_string.h"
99
#include "sentry_sync.h"
10+
#include "sentry_utils.h"
1011
#include "sentry_value.h"
1112

1213
#include <arpa/inet.h>
@@ -31,8 +32,6 @@ process_vm_readv(pid_t __pid, const struct iovec *__local_iov,
3132
}
3233
#endif
3334

34-
#define MIN(a, b) ((a) < (b) ? (a) : (b))
35-
3635
#define ENSURE(Ptr) \
3736
if (!Ptr) \
3837
goto fail

src/sentry_os.c

+151-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
#include "sentry_os.h"
2+
#include "sentry_slice.h"
23
#include "sentry_string.h"
3-
#include "sentry_utils.h"
4+
#if defined(SENTRY_PLATFORM_LINUX) || defined(SENTRY_PLATFORM_WINDOWS)
5+
# include "sentry_utils.h"
6+
#endif
7+
#ifdef SENTRY_PLATFORM_LINUX
8+
# include <unistd.h>
9+
#endif
410

511
#ifdef SENTRY_PLATFORM_WINDOWS
612

@@ -256,8 +262,123 @@ sentry__get_os_context(void)
256262
}
257263
#elif defined(SENTRY_PLATFORM_UNIX)
258264

265+
# include <fcntl.h>
259266
# include <sys/utsname.h>
260267

268+
# if defined(SENTRY_PLATFORM_LINUX)
269+
# define OS_RELEASE_MAX_LINE_SIZE 256
270+
# define OS_RELEASE_MAX_KEY_SIZE 64
271+
# define OS_RELEASE_MAX_VALUE_SIZE 128
272+
273+
static int
274+
parse_os_release_line(const char *line, char *key, char *value)
275+
{
276+
const char *equals = strchr(line, '=');
277+
if (equals == NULL)
278+
return 1;
279+
280+
unsigned long key_length = MIN(equals - line, OS_RELEASE_MAX_KEY_SIZE - 1);
281+
strncpy(key, line, key_length);
282+
key[key_length] = '\0';
283+
284+
sentry_slice_t value_slice
285+
= { .ptr = equals + 1, .len = strlen(equals + 1) };
286+
287+
// some values are wrapped in double quotes
288+
if (value_slice.ptr[0] == '\"') {
289+
value_slice.ptr++;
290+
value_slice.len -= 2;
291+
}
292+
293+
sentry__slice_to_buffer(value_slice, value, OS_RELEASE_MAX_VALUE_SIZE);
294+
295+
return 0;
296+
}
297+
298+
static void
299+
parse_line_into_object(const char *line, sentry_value_t os_dist)
300+
{
301+
char value[OS_RELEASE_MAX_VALUE_SIZE];
302+
char key[OS_RELEASE_MAX_KEY_SIZE];
303+
304+
if (parse_os_release_line(line, key, value) == 0) {
305+
if (strcmp(key, "ID") == 0) {
306+
sentry_value_set_by_key(
307+
os_dist, "name", sentry_value_new_string(value));
308+
}
309+
310+
if (strcmp(key, "VERSION_ID") == 0) {
311+
sentry_value_set_by_key(
312+
os_dist, "version", sentry_value_new_string(value));
313+
}
314+
315+
if (strcmp(key, "PRETTY_NAME") == 0) {
316+
sentry_value_set_by_key(
317+
os_dist, "pretty_name", sentry_value_new_string(value));
318+
}
319+
}
320+
}
321+
322+
# ifndef SENTRY_UNITTEST
323+
static
324+
# endif
325+
sentry_value_t
326+
get_linux_os_release(const char *os_rel_path)
327+
{
328+
const int fd = open(os_rel_path, O_RDONLY);
329+
if (fd == -1) {
330+
return sentry_value_new_null();
331+
}
332+
333+
sentry_value_t os_dist = sentry_value_new_object();
334+
char buffer[OS_RELEASE_MAX_LINE_SIZE];
335+
ssize_t bytes_read;
336+
ssize_t buffer_rest = 0;
337+
const char *line = buffer;
338+
while ((bytes_read = read(
339+
fd, buffer + buffer_rest, sizeof(buffer) - buffer_rest - 1))
340+
> 0) {
341+
ssize_t buffer_end = buffer_rest + bytes_read;
342+
buffer[buffer_end] = '\0';
343+
344+
// extract all lines from the valid buffer-range and parse them
345+
for (char *p = buffer; *p; ++p) {
346+
if (*p != '\n') {
347+
continue;
348+
}
349+
*p = '\0';
350+
parse_line_into_object(line, os_dist);
351+
line = p + 1;
352+
}
353+
354+
if (line < buffer + buffer_end) {
355+
// move the remaining partial line to the start of the buffer
356+
buffer_rest = buffer + buffer_end - line;
357+
memmove(buffer, line, buffer_rest);
358+
} else {
359+
// reset buffer_rest: the line-end coincided with the buffer-end
360+
buffer_rest = 0;
361+
}
362+
line = buffer;
363+
}
364+
365+
if (bytes_read == -1) {
366+
// read() failed and we can't assume to have valid data
367+
sentry_value_decref(os_dist);
368+
os_dist = sentry_value_new_null();
369+
} else if (buffer_rest > 0) {
370+
// the file ended w/o a new-line; we still have a line left to parse
371+
buffer[buffer_rest] = '\0';
372+
parse_line_into_object(line, os_dist);
373+
}
374+
375+
close(fd);
376+
377+
return os_dist;
378+
}
379+
380+
# endif // defined(SENTRY_PLATFORM_LINUX)
381+
261382
sentry_value_t
262383
sentry__get_os_context(void)
263384
{
@@ -298,6 +419,35 @@ sentry__get_os_context(void)
298419
sentry_value_set_by_key(
299420
os, "version", sentry_value_new_string(uts.release));
300421

422+
# if defined(SENTRY_PLATFORM_LINUX)
423+
/**
424+
* The file /etc/os-release takes precedence over /usr/lib/os-release.
425+
* Applications should check for the former, and exclusively use its data if
426+
* it exists, and only fall back to /usr/lib/os-release if it is missing.
427+
* Applications should not read data from both files at the same time.
428+
*
429+
* From:
430+
* https://www.freedesktop.org/software/systemd/man/latest/os-release.html#Description
431+
*/
432+
sentry_value_t os_dist = get_linux_os_release("/etc/os-release");
433+
if (sentry_value_is_null(os_dist)) {
434+
os_dist = get_linux_os_release("/usr/lib/os-release");
435+
if (sentry_value_is_null(os_dist)) {
436+
return os;
437+
}
438+
}
439+
sentry_value_set_by_key(
440+
os, "distribution_name", sentry_value_get_by_key(os_dist, "name"));
441+
sentry_value_set_by_key(os, "distribution_version",
442+
sentry_value_get_by_key(os_dist, "version"));
443+
sentry_value_set_by_key(os, "distribution_pretty_name",
444+
sentry_value_get_by_key(os_dist, "pretty_name"));
445+
sentry_value_incref(sentry_value_get_by_key(os_dist, "name"));
446+
sentry_value_incref(sentry_value_get_by_key(os_dist, "version"));
447+
sentry_value_incref(sentry_value_get_by_key(os_dist, "pretty_name"));
448+
sentry_value_decref(os_dist);
449+
# endif // defined(SENTRY_PLATFORM_LINUX)
450+
301451
return os;
302452

303453
fail:

src/sentry_slice.c

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "sentry_slice.h"
22
#include "sentry_string.h"
3+
#include "sentry_utils.h"
34
#include <stdlib.h>
45
#include <string.h>
56

@@ -18,6 +19,14 @@ sentry__slice_to_owned(sentry_slice_t slice)
1819
return sentry__string_clone_n_unchecked(slice.ptr, slice.len);
1920
}
2021

22+
void
23+
sentry__slice_to_buffer(sentry_slice_t slice, char *buffer, size_t buffer_len)
24+
{
25+
size_t copy_len = MIN(slice.len, buffer_len - 1);
26+
strncpy(buffer, slice.ptr, copy_len);
27+
buffer[copy_len] = 0;
28+
}
29+
2130
bool
2231
sentry__slice_eq(sentry_slice_t a, sentry_slice_t b)
2332
{

src/sentry_slice.h

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ typedef struct {
2020
*/
2121
sentry_slice_t sentry__slice_from_str(const char *str);
2222

23+
/**
24+
* Copies a slice to a pre-allocated buffer. The resulting buffer will contain a
25+
* zero-terminated string. `buffer_len` is expected to be the full length of the
26+
* buffer, so the resulting string can at maximum be `buffer_len - 1` long.
27+
*/
28+
void sentry__slice_to_buffer(
29+
sentry_slice_t slice, char *buffer, size_t buffer_len);
30+
2331
/**
2432
* Creates an owned copy from a slice.
2533
*/

src/sentry_utils.h

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
# include <time.h>
1616
#endif
1717

18+
#define MIN(a, b) ((a) < (b) ? (a) : (b))
19+
#define MAX(a, b) ((a) > (b) ? (a) : (b))
20+
1821
/**
1922
* This represents a URL parsed into its different parts.
2023
*/

tests/assertions.py

+2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ def assert_event_meta(
114114
event["contexts"]["os"],
115115
{"name": "Linux", "version": version, "build": build},
116116
)
117+
assert "distribution_name" in event["contexts"]["os"]
118+
assert "distribution_version" in event["contexts"]["os"]
117119
elif sys.platform == "darwin":
118120
version = platform.mac_ver()[0].split(".")
119121
if len(version) < 3:

0 commit comments

Comments
 (0)