Skip to content

Commit ee8daa0

Browse files
khanayan123claude
andcommitted
feat: add cross-platform session ID shared memory carrier
Add two new crates for propagating stable session identifiers across process boundaries via named shared memory: - datadog-session-id: Rust library built on datadog-ipc's NamedShmHandle (shm_open/memfd on Unix, CreateFileMapping on Windows) - datadog-session-id-ffi: C FFI exposing ddog_session_create, ddog_session_read_parent, ddog_session_read_pid, and ddog_session_carrier_drop SDKs that link against libdatadog can use the FFI to create/read session carriers without platform-specific shared memory code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d1eb663 commit ee8daa0

7 files changed

Lines changed: 599 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ members = [
4747
"libdd-dogstatsd-client",
4848
"libdd-log",
4949
"libdd-log-ffi", "libdd-libunwind-sys",
50+
"datadog-session-id",
51+
"datadog-session-id-ffi",
5052
]
5153

5254
# https://doc.rust-lang.org/cargo/reference/resolver.html

datadog-session-id-ffi/Cargo.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
[package]
5+
name = "datadog-session-id-ffi"
6+
version = "0.1.0"
7+
edition.workspace = true
8+
license.workspace = true
9+
publish = false
10+
11+
[lib]
12+
crate-type = ["staticlib", "cdylib", "lib"]
13+
bench = false
14+
15+
[dependencies]
16+
datadog-session-id = { path = "../datadog-session-id" }
17+
libdd-common-ffi = { path = "../libdd-common-ffi", default-features = false }
18+
anyhow = "1.0"
19+
20+
[features]
21+
default = ["cbindgen"]
22+
cbindgen = ["build_common/cbindgen", "libdd-common-ffi/cbindgen"]
23+
24+
[build-dependencies]
25+
build_common = { path = "../build-common" }

datadog-session-id-ffi/build.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
extern crate build_common;
4+
5+
use build_common::generate_and_configure_header;
6+
7+
fn main() {
8+
let header_name = "session-id.h";
9+
generate_and_configure_header(header_name);
10+
}

datadog-session-id-ffi/src/lib.rs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! C FFI for the cross-platform session ID shared memory carrier.
5+
//!
6+
//! SDKs that link against libdatadog can call these functions to create
7+
//! and read session carriers without dealing with platform-specific
8+
//! shared memory details.
9+
//!
10+
//! ## Typical usage from C
11+
//!
12+
//! ### Writer (parent process, before exec):
13+
//! ```c
14+
//! DdogSessionCarrier *carrier = NULL;
15+
//! DdogMaybeError err = ddog_session_create(
16+
//! "550e8400-e29b-41d4-a716-446655440000",
17+
//! "660e8400-e29b-41d4-a716-446655440001", // or NULL
18+
//! &carrier
19+
//! );
20+
//! // ... keep `carrier` alive until children have read it ...
21+
//! ddog_session_carrier_drop(carrier);
22+
//! ```
23+
//!
24+
//! ### Reader (child process, at init):
25+
//! ```c
26+
//! DdogSessionResult result;
27+
//! DdogMaybeError err = ddog_session_read_parent(&result);
28+
//! if (result.found) {
29+
//! printf("session: %s\n", result.session_id);
30+
//! if (result.parent_session_id[0] != '\0') {
31+
//! printf("parent: %s\n", result.parent_session_id);
32+
//! }
33+
//! }
34+
//! ```
35+
36+
#![cfg_attr(not(test), deny(clippy::panic))]
37+
#![cfg_attr(not(test), deny(clippy::unwrap_used))]
38+
#![cfg_attr(not(test), deny(clippy::expect_used))]
39+
#![cfg_attr(not(test), deny(clippy::todo))]
40+
#![cfg_attr(not(test), deny(clippy::unimplemented))]
41+
42+
use libdd_common_ffi as ffi;
43+
use std::ffi::CStr;
44+
use std::os::raw::c_char;
45+
46+
macro_rules! try_c {
47+
($failable:expr) => {
48+
match $failable {
49+
Ok(o) => o,
50+
Err(e) => return ffi::MaybeError::Some(ffi::Error::from(format!("{e:?}"))),
51+
}
52+
};
53+
}
54+
55+
/// Maximum length for session ID strings (including null terminator).
56+
/// UUIDs are 36 chars; we allow some headroom.
57+
const SESSION_ID_MAX_LEN: usize = 128;
58+
59+
/// Opaque handle returned by [`ddog_session_create`]. Must be kept alive
60+
/// (not dropped) for as long as child processes need to read the session.
61+
pub struct DdogSessionCarrier {
62+
_inner: datadog_session_id::SessionCarrier,
63+
}
64+
65+
/// Result struct returned by [`ddog_session_read_parent`] and
66+
/// [`ddog_session_read_pid`].
67+
#[repr(C)]
68+
pub struct DdogSessionResult {
69+
/// `true` if a session segment was found and read successfully.
70+
pub found: bool,
71+
/// The session ID string (null-terminated). Empty if `found` is false.
72+
pub session_id: [c_char; SESSION_ID_MAX_LEN],
73+
/// The parent session ID string (null-terminated). Empty if not set
74+
/// or if `found` is false.
75+
pub parent_session_id: [c_char; SESSION_ID_MAX_LEN],
76+
}
77+
78+
impl Default for DdogSessionResult {
79+
fn default() -> Self {
80+
Self {
81+
found: false,
82+
session_id: [0; SESSION_ID_MAX_LEN],
83+
parent_session_id: [0; SESSION_ID_MAX_LEN],
84+
}
85+
}
86+
}
87+
88+
fn copy_str_to_buf(src: &str, dst: &mut [c_char; SESSION_ID_MAX_LEN]) {
89+
let bytes = src.as_bytes();
90+
let copy_len = bytes.len().min(SESSION_ID_MAX_LEN - 1);
91+
for (i, &b) in bytes[..copy_len].iter().enumerate() {
92+
dst[i] = b as c_char;
93+
}
94+
dst[copy_len] = 0;
95+
}
96+
97+
/// Create a session carrier for the current process.
98+
///
99+
/// # Safety
100+
/// - `session_id` must be a valid null-terminated C string.
101+
/// - `parent_session_id` may be null (no parent).
102+
/// - `out` must be a valid pointer to a `*mut DdogSessionCarrier`.
103+
///
104+
/// The caller must eventually call [`ddog_session_carrier_drop`] on the
105+
/// returned handle.
106+
#[no_mangle]
107+
pub unsafe extern "C" fn ddog_session_create(
108+
session_id: *const c_char,
109+
parent_session_id: *const c_char,
110+
out: *mut *mut DdogSessionCarrier,
111+
) -> ffi::MaybeError {
112+
if session_id.is_null() || out.is_null() {
113+
return ffi::MaybeError::Some(ffi::Error::from(
114+
"ddog_session_create: null session_id or out pointer".to_string(),
115+
));
116+
}
117+
118+
let sid = unsafe { CStr::from_ptr(session_id) };
119+
let sid_str = try_c!(sid.to_str().map_err(|e| anyhow::anyhow!("{e}")));
120+
121+
let parent_str = if parent_session_id.is_null() {
122+
None
123+
} else {
124+
let psid = unsafe { CStr::from_ptr(parent_session_id) };
125+
Some(try_c!(psid.to_str().map_err(|e| anyhow::anyhow!("{e}"))))
126+
};
127+
128+
let carrier = try_c!(datadog_session_id::create_session_carrier(
129+
sid_str,
130+
parent_str,
131+
));
132+
133+
unsafe {
134+
*out = Box::into_raw(Box::new(DdogSessionCarrier { _inner: carrier }));
135+
}
136+
137+
ffi::MaybeError::None
138+
}
139+
140+
/// Drop (free) a session carrier previously created with
141+
/// [`ddog_session_create`]. After this call the shared memory segment
142+
/// is unmapped and children can no longer read it.
143+
///
144+
/// # Safety
145+
/// - `carrier` must have been returned by `ddog_session_create`, or be null.
146+
#[no_mangle]
147+
pub unsafe extern "C" fn ddog_session_carrier_drop(carrier: *mut DdogSessionCarrier) {
148+
if !carrier.is_null() {
149+
unsafe {
150+
drop(Box::from_raw(carrier));
151+
}
152+
}
153+
}
154+
155+
/// Read session data from the **parent process**.
156+
///
157+
/// # Safety
158+
/// - `out` must be a valid pointer to a `DdogSessionResult`.
159+
#[no_mangle]
160+
pub unsafe extern "C" fn ddog_session_read_parent(
161+
out: *mut DdogSessionResult,
162+
) -> ffi::MaybeError {
163+
if out.is_null() {
164+
return ffi::MaybeError::Some(ffi::Error::from(
165+
"ddog_session_read_parent: null out pointer".to_string(),
166+
));
167+
}
168+
169+
let mut result = DdogSessionResult::default();
170+
171+
match datadog_session_id::read_parent_session() {
172+
Ok(Some(payload)) => {
173+
result.found = true;
174+
copy_str_to_buf(&payload.session_id, &mut result.session_id);
175+
if let Some(ref psid) = payload.parent_session_id {
176+
copy_str_to_buf(psid, &mut result.parent_session_id);
177+
}
178+
}
179+
Ok(None) => {
180+
result.found = false;
181+
}
182+
Err(e) => {
183+
unsafe { *out = result; }
184+
return ffi::MaybeError::Some(ffi::Error::from(format!("{e:?}")));
185+
}
186+
}
187+
188+
unsafe { *out = result; }
189+
ffi::MaybeError::None
190+
}
191+
192+
/// Read session data from a **specific process** by PID.
193+
///
194+
/// # Safety
195+
/// - `out` must be a valid pointer to a `DdogSessionResult`.
196+
#[no_mangle]
197+
pub unsafe extern "C" fn ddog_session_read_pid(
198+
pid: u32,
199+
out: *mut DdogSessionResult,
200+
) -> ffi::MaybeError {
201+
if out.is_null() {
202+
return ffi::MaybeError::Some(ffi::Error::from(
203+
"ddog_session_read_pid: null out pointer".to_string(),
204+
));
205+
}
206+
207+
let mut result = DdogSessionResult::default();
208+
209+
match datadog_session_id::read_session_for_pid(pid) {
210+
Ok(Some(payload)) => {
211+
result.found = true;
212+
copy_str_to_buf(&payload.session_id, &mut result.session_id);
213+
if let Some(ref psid) = payload.parent_session_id {
214+
copy_str_to_buf(psid, &mut result.parent_session_id);
215+
}
216+
}
217+
Ok(None) => {
218+
result.found = false;
219+
}
220+
Err(e) => {
221+
unsafe { *out = result; }
222+
return ffi::MaybeError::Some(ffi::Error::from(format!("{e:?}")));
223+
}
224+
}
225+
226+
unsafe { *out = result; }
227+
ffi::MaybeError::None
228+
}

datadog-session-id/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
[package]
5+
name = "datadog-session-id"
6+
version = "0.1.0"
7+
edition.workspace = true
8+
license.workspace = true
9+
publish = false
10+
11+
[dependencies]
12+
datadog-ipc = { path = "../datadog-ipc" }
13+
anyhow = "1.0"
14+
serde = { version = "1", features = ["derive"] }
15+
serde_json = "1"
16+
17+
[target.'cfg(unix)'.dependencies]
18+
libc = "0.2"

0 commit comments

Comments
 (0)