Skip to content

Commit e5bd886

Browse files
committed
Add tests
1 parent 87f4825 commit e5bd886

File tree

2 files changed

+160
-8
lines changed

2 files changed

+160
-8
lines changed

internal/otelcontextmapping_linux.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,10 @@ type processContextHeader struct {
4545
}
4646

4747
func CreateOtelProcessContextMapping(data []byte) error {
48-
if existingMappingBytes != nil && publisherPID == os.Getpid() {
49-
// Unmap the previous mapping if it exists
50-
err := unix.Munmap(existingMappingBytes)
51-
if err != nil {
52-
return fmt.Errorf("failed to munmap previous mapping: %w", err)
53-
}
54-
existingMappingBytes = nil
55-
publisherPID = 0
48+
// Clear the previous mapping if it exists
49+
err := RemoveOtelProcessContextMapping()
50+
if err != nil {
51+
return fmt.Errorf("failed to remove previous mapping: %w", err)
5652
}
5753

5854
headerSize := int(unsafe.Sizeof(processContextHeader{}))
@@ -116,3 +112,19 @@ func CreateOtelProcessContextMapping(data []byte) error {
116112
publisherPID = os.Getpid()
117113
return nil
118114
}
115+
116+
func RemoveOtelProcessContextMapping() error {
117+
//Check publisher PID to check that the process has not forked.
118+
//It should not be necessary for Go, but just in case.
119+
if existingMappingBytes == nil || publisherPID != os.Getpid() {
120+
return nil
121+
}
122+
123+
err := unix.Munmap(existingMappingBytes)
124+
if err != nil {
125+
return fmt.Errorf("failed to munmap: %w", err)
126+
}
127+
existingMappingBytes = nil
128+
publisherPID = 0
129+
return nil
130+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2025 Datadog, Inc.
5+
6+
//go:build linux
7+
8+
package internal
9+
10+
import (
11+
"bufio"
12+
"bytes"
13+
"errors"
14+
"fmt"
15+
"io"
16+
"os"
17+
"strconv"
18+
"strings"
19+
"testing"
20+
"unsafe"
21+
22+
"github.com/stretchr/testify/require"
23+
"golang.org/x/sys/unix"
24+
)
25+
26+
func getContextFromMapping(fields []string) []byte {
27+
if fields[1] != "r--p" || fields[4] != "0" || fields[3] != "00:00" {
28+
return nil
29+
}
30+
31+
addrs := strings.SplitN(fields[0], "-", 2)
32+
if len(addrs) < 2 {
33+
return nil
34+
}
35+
vaddr, err := strconv.ParseUint(addrs[0], 16, 64)
36+
if err != nil {
37+
return nil
38+
}
39+
40+
vend, err := strconv.ParseUint(addrs[1], 16, 64)
41+
if err != nil {
42+
return nil
43+
}
44+
45+
length := vend - vaddr
46+
if length != uint64(otelContextMappingSize) {
47+
return nil
48+
}
49+
50+
header := (*processContextHeader)(unsafe.Pointer(uintptr(vaddr)))
51+
if string(header.Signature[:]) != otelContextSignature {
52+
return nil
53+
}
54+
if header.Version != 1 {
55+
return nil
56+
}
57+
58+
payload := make([]byte, header.PayloadSize)
59+
copy(payload, unsafe.Slice((*byte)(unsafe.Pointer(header.PayloadAddr)), header.PayloadSize))
60+
return payload
61+
}
62+
63+
func getContextMapping(mapsFile io.Reader, useMappingNames bool) ([]byte, error) {
64+
scanner := bufio.NewScanner(mapsFile)
65+
for scanner.Scan() {
66+
67+
line := scanner.Text()
68+
fields := strings.Fields(line)
69+
if len(fields) < 6 {
70+
continue
71+
}
72+
73+
if (useMappingNames && fields[5] != "[anon:OTEL_CTX]") || (!useMappingNames && fields[5] != "") {
74+
continue
75+
}
76+
77+
payload := getContextFromMapping(fields)
78+
if payload != nil {
79+
return payload, nil
80+
}
81+
82+
if useMappingNames {
83+
// When using mapping names, we can stop after the first match.
84+
break
85+
}
86+
}
87+
return nil, errors.New("no context mapping found")
88+
}
89+
90+
func readProcessLevelContext(useMappingNames bool) ([]byte, error) {
91+
mapsFile, err := os.Open("/proc/self/maps")
92+
if err != nil {
93+
return nil, err
94+
}
95+
defer mapsFile.Close()
96+
97+
return getContextMapping(mapsFile, useMappingNames)
98+
}
99+
100+
func kernelSupportsNamedAnonymousMappings() (bool, error) {
101+
var uname unix.Utsname
102+
if err := unix.Uname(&uname); err != nil {
103+
return false, fmt.Errorf("could not get Kernel Version: %v", err)
104+
}
105+
var major, minor, patch uint32
106+
_, _ = fmt.Fscanf(bytes.NewReader(uname.Release[:]), "%d.%d.%d", &major, &minor, &patch)
107+
108+
return major > 5 || (major == 5 && minor >= 17), nil
109+
}
110+
111+
func TestCreateOtelProcessContextMapping(t *testing.T) {
112+
RemoveOtelProcessContextMapping()
113+
t.Cleanup(func() {
114+
RemoveOtelProcessContextMapping()
115+
})
116+
117+
payload := []byte("hello world")
118+
err := CreateOtelProcessContextMapping(payload)
119+
require.NoError(t, err)
120+
121+
supportsNamedAnonymousMappings, err := kernelSupportsNamedAnonymousMappings()
122+
require.NoError(t, err)
123+
124+
ctx, err := readProcessLevelContext(supportsNamedAnonymousMappings)
125+
require.NoError(t, err)
126+
require.Equal(t, payload, ctx)
127+
}
128+
129+
func TestCreateOtelProcessContextMappingRejectsOversizedPayload(t *testing.T) {
130+
RemoveOtelProcessContextMapping()
131+
t.Cleanup(func() {
132+
RemoveOtelProcessContextMapping()
133+
})
134+
135+
headerSize := int(unsafe.Sizeof(processContextHeader{}))
136+
oversizedPayload := make([]byte, otelContextMappingSize-headerSize+1)
137+
138+
err := CreateOtelProcessContextMapping(oversizedPayload)
139+
require.Error(t, err)
140+
}

0 commit comments

Comments
 (0)