@@ -42,7 +42,8 @@ static const otel_process_ctx_data empty_data = {
4242 .telemetry_sdk_language = NULL ,
4343 .telemetry_sdk_version = NULL ,
4444 .telemetry_sdk_name = NULL ,
45- .resources = NULL
45+ .resource_attributes = NULL ,
46+ .extra_attributes = NULL
4647};
4748
4849#if (defined(OTEL_PROCESS_CTX_NOOP ) && OTEL_PROCESS_CTX_NOOP ) || !defined(__linux__ )
@@ -75,10 +76,10 @@ static const otel_process_ctx_data empty_data = {
7576 * An outside-of-process reader will read this struct + otel_process_payload to get the data.
7677 */
7778typedef struct __attribute__((packed , aligned (8 ))) {
78- char otel_process_ctx_signature [8 ]; // Always OTEL_CTX_SIGNATURE
79+ char otel_process_ctx_signature [8 ]; // Always "OTEL_CTX"
7980 uint32_t otel_process_ctx_version ; // Always > 0, incremented when the data structure changes, currently v2
8081 uint32_t otel_process_payload_size ; // Always > 0, size of storage
81- uint64_t otel_process_ctx_published_at_ns ; // Always > 0, timestamp from when the context was published in nanoseconds since epoch
82+ uint64_t otel_process_ctx_published_at_ns ; // Timestamp from when the context was published in nanoseconds since epoch. 0 during updates.
8283 char * otel_process_payload ; // Always non-null, points to the storage for the data; expected to be a protobuf map of string key/value pairs, null-terminated
8384} otel_process_ctx_mapping ;
8485
@@ -144,6 +145,10 @@ otel_process_ctx_result otel_process_ctx_publish(const otel_process_ctx_data *da
144145 const ssize_t mapping_size = sizeof (otel_process_ctx_mapping );
145146 published_state .publisher_pid = getpid (); // This allows us to detect in forks that we shouldn't touch the mapping
146147 int fd = memfd_create ("OTEL_CTX" , MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_NOEXEC_SEAL );
148+ if (fd < 0 ) {
149+ // MFD_NOEXEC_SEAL is a newer flag; older kernels reject unknown flags, so let's retry without it
150+ fd = memfd_create ("OTEL_CTX" , MFD_CLOEXEC | MFD_ALLOW_SEALING );
151+ }
147152 bool failed_to_close_fd = false;
148153 if (fd >= 0 ) {
149154 // Try to create mapping from memfd
@@ -178,24 +183,24 @@ otel_process_ctx_result otel_process_ctx_publish(const otel_process_ctx_data *da
178183 }
179184
180185 // Step: Populate the mapping
181- // The payload and any extra fields must come first and not be reordered with the signature by the compiler.
186+ // The payload and any extra fields must come first and not be reordered with the published_at_ns by the compiler.
182187 * published_state .mapping = (otel_process_ctx_mapping ) {
183- .otel_process_ctx_signature = {0 }, // Set in "Step: Populate the signature into the mapping" below
188+ .otel_process_ctx_signature = { 'O' , 'T' , 'E' , 'L' , '_' , 'C' , 'T' , 'X' },
184189 .otel_process_ctx_version = 2 ,
185190 .otel_process_payload_size = payload_size ,
186- .otel_process_ctx_published_at_ns = published_at_ns ,
191+ .otel_process_ctx_published_at_ns = 0 , // Set in "Step: Populate the published_at_ns into the mapping" below
187192 .otel_process_payload = published_state .payload
188193 };
189194
190- // Step: Synchronization - Mapping has been filled and is missing signature
191- // Make sure the initialization of the mapping + payload above does not get reordered with setting the signature below. Setting
192- // the signature is what tells an outside reader that the context is fully published.
195+ // Step: Synchronization - Mapping has been filled and is missing published_at_ns
196+ // Make sure the initialization of the mapping + payload above does not get reordered with setting the published_at_ns below. Setting
197+ // the published_at_ns is what tells an outside reader that the context is fully published.
193198 atomic_thread_fence (memory_order_seq_cst );
194199
195- // Step: Populate the signature into the mapping
196- // The signature must come last and not be reordered with the fields above by the compiler. After this step, external readers
197- // can read the signature and know that the payload is ready to be read.
198- memcpy ( published_state .mapping -> otel_process_ctx_signature , OTEL_CTX_SIGNATURE , sizeof ( published_state . mapping -> otel_process_ctx_signature )) ;
200+ // Step: Populate the published_at_ns into the mapping
201+ // The published_at_ns must come last and not be reordered with the fields above by the compiler. After this step, external readers
202+ // can read the published_at_ns and know that the payload is ready to be read.
203+ published_state .mapping -> otel_process_ctx_published_at_ns = published_at_ns ;
199204
200205 // Step: Attempt to name the mapping so outside readers can:
201206 // * Find it by name
@@ -242,6 +247,11 @@ static otel_process_ctx_result otel_process_ctx_update(uint64_t published_at_ns,
242247 return (otel_process_ctx_result ) {.success = false, .error_message = "Unexpected: otel_process_ctx_data is NULL or context is not published (" __FILE__ ":" ADD_QUOTES (__LINE__ ) ")" };
243248 }
244249
250+ if (published_at_ns == published_state .mapping -> otel_process_ctx_published_at_ns ) {
251+ // Advance published_at_ns to allow readers to detect the update
252+ published_at_ns ++ ;
253+ }
254+
245255 // Step: Prepare the new payload to be published
246256 // The payload SHOULD be ready and valid before trying to actually update the mapping.
247257 uint32_t payload_size = 0 ;
@@ -345,8 +355,8 @@ static void write_protobuf_tag(char **ptr, uint8_t field_number) {
345355 * (* ptr )++ = (char )((field_number << 3 ) | 2 ); // Field type is always 2 (LEN)
346356}
347357
348- static void write_attribute (char * * ptr , const char * key , const char * value ) {
349- write_protobuf_tag (ptr , 1 ); // Resource.attributes (field 1)
358+ static void write_attribute (char * * ptr , uint8_t field_number , const char * key , const char * value ) {
359+ write_protobuf_tag (ptr , field_number );
350360 write_protobuf_varint (ptr , protobuf_otel_keyvalue_string_size (key , value ));
351361
352362 // KeyValue
@@ -381,15 +391,20 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
381391 otel_process_ctx_result validation_result = validate_and_calculate_protobuf_payload_size (& pairs_size , (const char * * ) pairs );
382392 if (!validation_result .success ) return validation_result ;
383393
384- size_t resources_pairs_size = 0 ;
385- if (data .resources != NULL ) {
386- validation_result = validate_and_calculate_protobuf_payload_size (& resources_pairs_size , data .resources );
394+ size_t resource_attributes_pairs_size = 0 ;
395+ if (data .resource_attributes != NULL ) {
396+ validation_result = validate_and_calculate_protobuf_payload_size (& resource_attributes_pairs_size , data .resource_attributes );
387397 if (!validation_result .success ) return validation_result ;
388398 }
389399
390- size_t resource_size = pairs_size + resources_pairs_size ;
391- // ProcessContext wrapper: tag (1 byte) + resource length varint + resource content
392- size_t total_size = 1 + protobuf_varint_size (resource_size ) + resource_size ;
400+ size_t extra_attributes_pairs_size = 0 ;
401+ if (data .extra_attributes != NULL ) {
402+ validation_result = validate_and_calculate_protobuf_payload_size (& extra_attributes_pairs_size , data .extra_attributes );
403+ if (!validation_result .success ) return validation_result ;
404+ }
405+
406+ size_t resource_size = pairs_size + resource_attributes_pairs_size ;
407+ size_t total_size = protobuf_record_size (resource_size ) + extra_attributes_pairs_size ;
393408
394409 char * encoded = (char * ) calloc (total_size , 1 );
395410 if (!encoded ) {
@@ -402,11 +417,15 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
402417 write_protobuf_varint (& ptr , resource_size );
403418
404419 for (size_t i = 0 ; pairs [i * 2 ] != NULL ; i ++ ) {
405- write_attribute (& ptr , pairs [i * 2 ], pairs [i * 2 + 1 ]);
420+ write_attribute (& ptr , 1 , pairs [i * 2 ], pairs [i * 2 + 1 ]);
406421 }
407422
408- for (size_t i = 0 ; data .resources != NULL && data .resources [i * 2 ] != NULL ; i ++ ) {
409- write_attribute (& ptr , data .resources [i * 2 ], data .resources [i * 2 + 1 ]);
423+ for (size_t i = 0 ; data .resource_attributes != NULL && data .resource_attributes [i * 2 ] != NULL ; i ++ ) {
424+ write_attribute (& ptr , 1 , data .resource_attributes [i * 2 ], data .resource_attributes [i * 2 + 1 ]);
425+ }
426+
427+ for (size_t i = 0 ; data .extra_attributes != NULL && data .extra_attributes [i * 2 ] != NULL ; i ++ ) {
428+ write_attribute (& ptr , 2 , data .extra_attributes [i * 2 ], data .extra_attributes [i * 2 + 1 ]);
410429 }
411430
412431 * out = encoded ;
@@ -439,7 +458,7 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
439458 if (!fp ) return result ;
440459
441460 while (fgets (line , sizeof (line ), fp )) {
442- bool is_process_ctx = strstr (line , "[anon_shmem:OTEL_CTX]" ) != NULL || strstr (line , "/memfd:OTEL_CTX" ) != NULL ;
461+ bool is_process_ctx = strstr (line , "[anon_shmem:OTEL_CTX]" ) != NULL || strstr (line , "[anon:OTEL_CTX]" ) != NULL || strstr ( line , " /memfd:OTEL_CTX" ) != NULL ;
443462 if (is_process_ctx ) {
444463 result = (otel_process_ctx_mapping * )parse_mapping_start (line );
445464 break ;
@@ -495,6 +514,34 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
495514 return wire_type == 2 ; // We only need the LEN wire type for now
496515 }
497516
517+ // Reads an OTel KeyValue message (key string + AnyValue-wrapped string) into the provided buffers.
518+ static bool read_protobuf_keyvalue (char * * ptr , char * end_ptr , char * key_buffer , char * value_buffer ) {
519+ bool key_found = false;
520+ bool value_found = false;
521+
522+ while (* ptr < end_ptr ) {
523+ uint8_t kv_field ;
524+ if (!read_protobuf_tag (ptr , end_ptr , & kv_field )) return false;
525+
526+ if (kv_field == 1 ) { // KeyValue.key
527+ if (!read_protobuf_string (ptr , end_ptr , key_buffer )) return false;
528+ key_found = true;
529+ } else if (kv_field == 2 ) { // KeyValue.value (AnyValue)
530+ uint16_t _any_len ; // Unused, but we still need to consume + validate the varint
531+ if (!read_protobuf_varint (ptr , end_ptr , & _any_len )) return false;
532+ uint8_t any_field ;
533+ if (!read_protobuf_tag (ptr , end_ptr , & any_field )) return false;
534+
535+ if (any_field == 1 ) { // AnyValue.string_value
536+ if (!read_protobuf_string (ptr , end_ptr , value_buffer )) return false;
537+ value_found = true;
538+ }
539+ }
540+ }
541+
542+ return key_found && value_found ;
543+ }
544+
498545 // Simplified protobuf decoder to match the exact encoder above. If the protobuf data doesn't match the encoder, this will
499546 // return false.
500547 static bool otel_process_ctx_decode_payload (char * payload , uint32_t payload_size , otel_process_ctx_data * data_out , char * key_buffer , char * value_buffer ) {
@@ -514,8 +561,13 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
514561
515562 size_t resource_index = 0 ;
516563 size_t resource_capacity = 201 ; // Allocate space for 100 pairs + NULL terminator entry
517- data_out -> resources = (const char * * ) calloc (resource_capacity , sizeof (char * ));
518- if (data_out -> resources == NULL ) return false;
564+ data_out -> resource_attributes = (const char * * ) calloc (resource_capacity , sizeof (char * ));
565+ if (data_out -> resource_attributes == NULL ) return false;
566+
567+ size_t extra_attributes_index = 0 ;
568+ size_t extra_attributes_capacity = 201 ; // Allocate space for 100 pairs + NULL terminator entry
569+ data_out -> extra_attributes = (const char * * ) calloc (extra_attributes_capacity , sizeof (char * ));
570+ if (data_out -> extra_attributes == NULL ) return false;
519571
520572 while (ptr < resource_end ) {
521573 uint8_t field_number ;
@@ -526,31 +578,7 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
526578 char * kv_end = ptr + kv_len ;
527579 if (kv_end > resource_end ) return false;
528580
529- bool key_found = false;
530- bool value_found = false;
531-
532- // Parse KeyValue
533- while (ptr < kv_end ) {
534- uint8_t kv_field ;
535- if (!read_protobuf_tag (& ptr , kv_end , & kv_field )) return false;
536-
537- if (kv_field == 1 ) { // KeyValue.key
538- if (!read_protobuf_string (& ptr , kv_end , key_buffer )) return false;
539- key_found = true;
540- } else if (kv_field == 2 ) { // KeyValue.value (AnyValue)
541- uint16_t _any_len ; // Unused, but we still need to consume + validate the varint
542- if (!read_protobuf_varint (& ptr , kv_end , & _any_len )) return false;
543- uint8_t any_field ;
544- if (!read_protobuf_tag (& ptr , kv_end , & any_field )) return false;
545-
546- if (any_field == 1 ) { // AnyValue.string_value
547- if (!read_protobuf_string (& ptr , kv_end , value_buffer )) return false;
548- value_found = true;
549- }
550- }
551- }
552-
553- if (!key_found || !value_found ) return false;
581+ if (!read_protobuf_keyvalue (& ptr , kv_end , key_buffer , value_buffer )) return false;
554582
555583 char * value = strdup (value_buffer );
556584 if (!value ) return false;
@@ -571,12 +599,35 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
571599 free (value );
572600 return false;
573601 }
574- data_out -> resources [resource_index ] = key ;
575- data_out -> resources [resource_index + 1 ] = value ;
602+ data_out -> resource_attributes [resource_index ] = key ;
603+ data_out -> resource_attributes [resource_index + 1 ] = value ;
576604 resource_index += 2 ;
577605 }
578606 }
579607
608+ while (ptr < end_ptr ) {
609+ uint8_t extra_ctx_field ;
610+ if (!read_protobuf_tag (& ptr , end_ptr , & extra_ctx_field ) || extra_ctx_field != 2 ) return false;
611+
612+ uint16_t kv_len ;
613+ if (!read_protobuf_varint (& ptr , end_ptr , & kv_len )) return false;
614+ char * kv_end = ptr + kv_len ;
615+ if (kv_end > end_ptr ) return false;
616+
617+ if (!read_protobuf_keyvalue (& ptr , kv_end , key_buffer , value_buffer )) return false;
618+
619+ char * key = strdup (key_buffer );
620+ char * value = strdup (value_buffer );
621+ if (!key || !value || extra_attributes_index + 2 >= extra_attributes_capacity ) {
622+ free (key );
623+ free (value );
624+ return false;
625+ }
626+ data_out -> extra_attributes [extra_attributes_index ] = key ;
627+ data_out -> extra_attributes [extra_attributes_index + 1 ] = value ;
628+ extra_attributes_index += 2 ;
629+ }
630+
580631 // Validate all required fields were found
581632 return data_out -> deployment_environment_name != NULL &&
582633 data_out -> service_instance_id != NULL &&
@@ -595,9 +646,13 @@ static otel_process_ctx_result otel_process_ctx_encode_protobuf_payload(char **o
595646 if (data .telemetry_sdk_language ) free ((void * )data .telemetry_sdk_language );
596647 if (data .telemetry_sdk_version ) free ((void * )data .telemetry_sdk_version );
597648 if (data .telemetry_sdk_name ) free ((void * )data .telemetry_sdk_name );
598- if (data .resources ) {
599- for (int i = 0 ; data .resources [i ] != NULL ; i ++ ) free ((void * )data .resources [i ]);
600- free ((void * )data .resources );
649+ if (data .resource_attributes ) {
650+ for (int i = 0 ; data .resource_attributes [i ] != NULL ; i ++ ) free ((void * )data .resource_attributes [i ]);
651+ free ((void * )data .resource_attributes );
652+ }
653+ if (data .extra_attributes ) {
654+ for (int i = 0 ; data .extra_attributes [i ] != NULL ; i ++ ) free ((void * )data .extra_attributes [i ]);
655+ free ((void * )data .extra_attributes );
601656 }
602657 }
603658
0 commit comments