pax_global_header00006660000000000000000000000064152405764560014530gustar00rootroot0000000000000052 comment=ea9dca1df161b87d48c1c9fb04c052a4062dff3e open-telemetry-opentelemetry-injector-ea9dca1/000077500000000000000000000000001524057645600217575ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/.chloggen/000077500000000000000000000000001524057645600236235ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/.chloggen/TEMPLATE.yaml000066400000000000000000000022461524057645600257260ustar00rootroot00000000000000# Use this changelog template to create an entry for release notes. # One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' change_type: # The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) component: # A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). note: # Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. issues: [] # (Optional) One or more lines of additional information to render under the primary note. # These lines will be padded with 2 spaces and then inserted directly into the document. # Use pipe (|) for multiline entries. subtext: # If your change doesn't affect end users or the exported elements of any package, # you should instead start your pull request title with "chore" or use the "Skip Changelog" label. # Optional: The change log or logs in which this entry should be included. # e.g. '[user]' or '[user, api]' # Include 'user' if the change is relevant to end users. # Include 'api' if there is a change to a library API. # Default: '[user]' change_logs: [] open-telemetry-opentelemetry-injector-ea9dca1/.chloggen/config.yaml000066400000000000000000000021561524057645600257600ustar00rootroot00000000000000# The directory that stores individual changelog entries. # Each entry is stored in a dedicated yaml file. # - 'chloggen new' will copy the 'template_yaml' to this directory as a new entry file. # - 'chloggen validate' will validate that all entry files are valid. # - 'chloggen update' will read and delete all entry files in this directory, and update 'changelog_md'. # Specify as relative path from root of repo. # (Optional) Default: .chloggen entries_dir: .chloggen # This file is used as the input for individual changelog entries. # Specify as relative path from root of repo. # (Optional) Default: .chloggen/TEMPLATE.yaml template_yaml: .chloggen/TEMPLATE.yaml summary_template: .chloggen/summary.tmpl # The CHANGELOG file or files to which 'chloggen update' will write new entries # (Optional) Default filename: CHANGELOG.md change_logs: user: CHANGELOG.md # The default change_log or change_logs to which an entry should be added. # If 'change_logs' is specified in this file, and no value is specified for 'default_change_logs', # then 'change_logs' MUST be specified in every entry file. default_change_logs: [user] open-telemetry-opentelemetry-injector-ea9dca1/.chloggen/summary.tmpl000066400000000000000000000021551524057645600262210ustar00rootroot00000000000000{{- define "entry" -}} - `{{ .Component }}`: {{ .Note }} ( {{- range $i, $issue := .Issues }} {{- if $i }}, {{ end -}} #{{ $issue }} {{- end -}} ) {{- if .SubText }} {{ .SubText | indent 2 }} {{- end }} {{- end }} ## {{ .Version }} {{- if .BreakingChanges }} ### 🛑 Breaking changes 🛑 {{- range $i, $change := .BreakingChanges }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} {{- if .Deprecations }} ### 🚩 Deprecations 🚩 {{- range $i, $change := .Deprecations }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} {{- if .NewComponents }} ### 🚀 New components 🚀 {{- range $i, $change := .NewComponents }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} {{- if .Enhancements }} ### 💡 Enhancements 💡 {{- range $i, $change := .Enhancements }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} {{- if .BugFixes }} ### 🧰 Bug fixes 🧰 {{- range $i, $change := .BugFixes }} {{- if eq $i 0}} {{end}} {{ template "entry" $change }} {{- end }} {{- end }} open-telemetry-opentelemetry-injector-ea9dca1/.github/000077500000000000000000000000001524057645600233175ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/.github/CODEOWNERS000066400000000000000000000011411524057645600247070ustar00rootroot00000000000000##################################################### # # List of approvers for OpenTelemetry Proto repository # ##################################################### # # Learn about membership in OpenTelemetry community: # https://github.com/open-telemetry/community/blob/main/community-membership.md # # # Learn about CODEOWNERS file format: # https://help.github.com/en/articles/about-code-owners # # Global owners, will be the owners for everything in the repo. Membership is tracked via https://github.com/open-telemetry/community/blob/main/community-members.md * @open-telemetry/injector-approvers open-telemetry-opentelemetry-injector-ea9dca1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001524057645600255025ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/.github/ISSUE_TEMPLATE/bug_report.yaml000066400000000000000000000051101524057645600305330ustar00rootroot00000000000000name: Bug report description: Create a report to help us improve labels: ["bug"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! Please make sure to fill out the entire form below, providing as much context as you can in order to help us triage and track down your bug as quickly as possible. Before filing a bug, please be sure you have searched through [existing bugs](https://github.com/open-telemetry/opentelemetry-injector/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Abug) to see if an existing issue covers your bug. - type: textarea attributes: label: What happened? description: Please provide as much detail as you reasonably can. value: | **Describe the bug** **Steps to reproduce** **What did you expect to see?** **What did you see instead?** validations: required: true - type: input attributes: label: Version description: What version did you use? (e.g., `v0.4.0`, `1eb551b`, etc) validations: required: true - type: textarea attributes: label: Environment information description: Please provide any additional information about your installation. value: | ## Environment OS: (e.g., "Ubuntu 20.04") Compiler(if manually compiled): (e.g., "go 14.2") - type: textarea attributes: label: Log output description: | Please copy and paste any relevant log output. render: shell - type: textarea attributes: label: Additional context description: Any additional information you think may be relevant to this issue. - type: dropdown attributes: label: Tip description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. options: - [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with ðŸ‘� to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). default: 0 open-telemetry-opentelemetry-injector-ea9dca1/.github/ISSUE_TEMPLATE/feature_request.yaml000066400000000000000000000027521524057645600315770ustar00rootroot00000000000000name: Feature request description: Suggest an idea for this project labels: ["feature request"] body: - type: textarea attributes: label: Is your feature request related to a problem? Please describe. description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]. validations: required: true - type: textarea attributes: label: Describe the solution you'd like description: A clear and concise description of what you want to happen. validations: required: true - type: textarea attributes: label: Describe alternatives you've considered description: A clear and concise description of any alternative solutions or features you've considered. - type: textarea attributes: label: Additional context description: Add any other context or screenshots about the feature request here. - type: dropdown attributes: label: Tip description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. options: - [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with ðŸ‘� to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). default: 0 open-telemetry-opentelemetry-injector-ea9dca1/.github/ISSUE_TEMPLATE/other.yaml000066400000000000000000000016131524057645600275100ustar00rootroot00000000000000name: Other issue description: Create a new issue to help us improve body: - type: textarea attributes: label: Describe the issue you're reporting description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] validations: required: true - type: dropdown attributes: label: Tip description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. options: - [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with ðŸ‘� to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). default: 0 open-telemetry-opentelemetry-injector-ea9dca1/.github/lychee.toml000066400000000000000000000002751524057645600254710ustar00rootroot00000000000000include_fragments = "full" accept = ["200..=299", "429"] exclude = [ "^http(s)?://localhost", "^http(s)?://example.com" ] # better to be safe and avoid failures max_retries = 6 open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/000077500000000000000000000000001524057645600253545ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/build.yml000066400000000000000000000050511524057645600271770ustar00rootroot00000000000000name: Build on: push: branches: - main tags: - "v[0-9]+.[0-9]+.[0-9]+**" pull_request: permissions: contents: read jobs: verify-and-build-binary: runs-on: ${{ matrix.ARCH == 'amd64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }} strategy: matrix: ARCH: [ "amd64", "arm64"] steps: - name: Check out the codebase. uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 - name: lint source code run: | make lint - name: Run Zig unit tests run: make zig-unit-tests ARCH=${{ matrix.ARCH }} - name: Build binary run: make dist ARCH=${{ matrix.ARCH }} - name: Run Injector integration tests run: make injector-integration-tests-for-one-architecture ARCH=${{ matrix.ARCH }} - name: Upload binary uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: libotelinject-${{ matrix.ARCH }} path: ./dist/*.so if-no-files-found: error publish-stable: runs-on: ubuntu-24.04 if: startsWith(github.ref, 'refs/tags/') && github.repository == 'open-telemetry/opentelemetry-injector' needs: verify-and-build-binary permissions: contents: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - run: mkdir -p artifacts - name: Download artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: ./artifacts/ - name: Prepare release notes run: ./.github/workflows/scripts/prepare-release-notes.sh env: RELEASE_TAG: ${{ steps.github_tag.outputs.tag }} - name: debug release artifacts shell: bash run: | set -x pwd ls -la . ls -laR /home/runner/work/opentelemetry-injector/opentelemetry-injector/artifacts || true ls -laR artifacts || true - name: Release uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 with: body_path: release-notes.md fail_on_unmatched_files: true # RC tags (e.g. v0.10.0-rc.1) land as a draft release so maintainers # can review the notes and artifacts before publishing; stable tags # publish immediately. draft: ${{ contains(github.ref_name, '-rc.') }} files: | artifacts/**/* open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/changelog.yml000066400000000000000000000075701524057645600300370ustar00rootroot00000000000000# This action requires that any PR targeting the main branch should add a # yaml file to the ./.chloggen/ directory. If a CHANGELOG entry is not required, # or if performing maintenance on the Changelog, add either "chore" to the title # of the pull request or add the \"Skip Changelog\" label to disable this action. name: Changelog on: pull_request: types: [opened, synchronize, reopened, labeled, unlabeled] branches: - main permissions: read-all env: # Make sure to exit early if cache segment download times out after 2 minutes. # We limit cache download as a whole to 5 minutes. SEGMENT_DOWNLOAD_TIMEOUT_MINS: 2 concurrency: group: ${{ github.workflow }}-${{ github.head_ref }} cancel-in-progress: true jobs: changelog: runs-on: ubuntu-24.04 if: | ${{ github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' && !contains(github.event.pull_request.labels.*.name, 'dependencies') && !contains(github.event.pull_request.labels.*.name, 'Skip Changelog') && !startsWith(github.event.pull_request.title, 'chore') && !startsWith(github.event.pull_request.title, 'ci') && !startsWith(github.event.pull_request.title, 'test') }} env: PR_HEAD: ${{ github.event.pull_request.head.sha }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: 1.26.6 cache: false - name: Cache Go id: go-cache timeout-minutes: 5 uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/go/bin ~/go/pkg/mod key: changelog-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - name: Ensure no changes to the CHANGELOG.md or CHANGELOG-API.md run: | if [[ $(git diff --name-only $(git merge-base origin/main $PR_HEAD) $PR_HEAD ./CHANGELOG*.md) ]] then echo "CHANGELOG.md and CHANGELOG-API.md should not be directly modified." echo "Please add a .yaml file to the ./.chloggen/ directory." echo "See CONTRIBUTING.md for more details." echo "Alternately, add either \"chore\" to the title of the pull request or add the \"Skip Changelog\" label if this job should be skipped." false else echo "CHANGELOG.md and CHANGELOG-API.md were not modified." fi - name: Ensure ./.chloggen/*.yaml addition(s) run: | if [[ 1 -gt $(git diff --diff-filter=A --name-only $(git merge-base origin/main $PR_HEAD) $PR_HEAD ./.chloggen | grep -c \\.yaml) ]] then echo "No changelog entry was added to the ./.chloggen/ directory." echo "Please add a .yaml file to the ./.chloggen/ directory." echo "See CONTRIBUTING.md for more details." echo "Alternately, add either \"chore\" to the title of the pull request or add the \"Skip Changelog\" label if this job should be skipped." false else echo "A changelog entry was added to the ./.chloggen/ directory." fi - name: Validate ./.chloggen/*.yaml changes run: | make chlog-validate \ || { echo "New ./.chloggen/*.yaml file failed validation."; exit 1; } # In order to validate any links in the yaml file, render the config to markdown - name: Render .chloggen changelog entries run: make chlog-preview > changelog_preview.md - name: Link Checker id: lychee uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 with: args: "--verbose --no-progress ./changelog_preview.md --config .github/lychee.toml" failIfEmpty: false open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/create-tag-for-release.yml000066400000000000000000000033241524057645600323170ustar00rootroot00000000000000name: Automation - Create Tag for Release on: push: branches: - main permissions: contents: write jobs: create-tag-for-release: runs-on: ubuntu-24.04 steps: - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 id: otelbot-token with: client-id: ${{ vars.OTELBOT_INJECTOR_CLIENT_ID }} private-key: ${{ secrets.OTELBOT_INJECTOR_PRIVATE_KEY }} - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 2 # We are deliberately not using the default secrets.GITHUB_TOKEN here, since events from that token do not # trigger subsequent workflows, see # https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow#triggering-a-workflow-from-a-workflow. # I.e., creating and pushing the tag would not trigger the workflow .github/workflows/build.yml. We do need to # trigger the workflow for the tag we are creating here though, as the job publish-stable from build.yaml # actually creates the release and uploads the assets. # See https://github.com/open-telemetry/community/blob/9eaa934620638a2f5537d3d74372b389098a8f5e/assets.md#otelbot. # # Providing the token to the checkout action will result in the token being used in all subsequent git # operations, in particular for creating the tag in .github/workflows/scripts/create-tag-for-release.sh. token: ${{ steps.otelbot-token.outputs.token }} - name: Create tag for release run: .github/workflows/scripts/create-tag-for-release.sh open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/fossa.yml000066400000000000000000000006221524057645600272120ustar00rootroot00000000000000name: FOSSA scanning on: push: branches: - main permissions: contents: read jobs: fossa: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: fossas/fossa-action@29693cc50323968e039056be419b32989fc5880c # v2.0.0 with: api-key: ${{secrets.FOSSA_API_KEY}} team: OpenTelemetry open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/ossf-scorecard.yml000066400000000000000000000030271524057645600310160ustar00rootroot00000000000000name: OSSF Scorecard on: push: branches: - main schedule: - cron: "39 12 * * 2" # once a week workflow_dispatch: permissions: contents: read jobs: analysis: runs-on: ubuntu-latest permissions: # Needed for Code scanning upload security-events: write # Needed for GitHub OIDC token if publish_results is true id-token: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 with: results_file: results.sarif results_format: sarif publish_results: true # Upload the results as artifacts (optional). Commenting out will disable # uploads of run results in SARIF format to the repository Actions tab. # https://docs.github.com/en/actions/advanced-guides/storing-workflow-data-as-artifacts - name: "Upload artifact" uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: results.sarif open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/prepare-release.yml000066400000000000000000000042341524057645600311560ustar00rootroot00000000000000name: Automation - Prepare Release on: workflow_dispatch: inputs: override: description: | Override the auto-derived bump. Leave as "auto" to derive from chloggen entries in .chloggen/, or force a specific bump kind. required: true default: auto type: choice options: - auto - major - minor - patch prerelease: description: | When true, release the next release candidate for the computed version instead of the stable release, tagging v-rc. where N is one greater than the highest existing rc index for the same base version (or 1 if no prior rc exists). required: false default: false type: boolean permissions: contents: read jobs: # Releasing opentelemetry-injector prepare-release: permissions: contents: write # required for pushing changes in prepare-release.sh runs-on: ubuntu-24.04 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-tags: true - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.26.6" cache: false - name: Determine next release version id: version env: OVERRIDE: ${{ inputs.override }} PRERELEASE: ${{ inputs.prerelease }} working-directory: ./tooling/nextversion run: | go run . \ -override "$OVERRIDE" \ -prerelease="$PRERELEASE" \ -chloggen-dir ../../.chloggen - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 id: otelbot-token with: client-id: ${{ vars.OTELBOT_CLIENT_ID }} private-key: ${{ secrets.OTELBOT_PRIVATE_KEY }} - name: Prepare release for injector env: GITHUB_TOKEN: ${{ steps.otelbot-token.outputs.token }} REPO: open-telemetry/opentelemetry-injector VERSION: ${{ steps.version.outputs.version }} run: ./.github/workflows/scripts/prepare-release.sh open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/scripts/000077500000000000000000000000001524057645600270435ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/scripts/create-tag-for-release.sh000077500000000000000000000017421524057645600336240ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/../../.." # Get the most recent commit message COMMIT_MSG=$(git log -1 --pretty=%B) # Check if commit message matches release pattern and extract version if [[ "$COMMIT_MSG" =~ ^docs:\ update\ changelog\ to\ prepare\ release\ (v[0-9]+\.[0-9]+\.[0-9]+(-[[:alnum:]]+)?).*$ ]]; then VERSION="${BASH_REMATCH[1]}" echo "Found release commit for version: $VERSION." # Create and push tag echo "Creating tag for version $VERSION." # See https://github.com/open-telemetry/community/blob/9eaa934620638a2f5537d3d74372b389098a8f5e/assets.md#otelbot git config user.name otelbot git config user.email 197425009+otelbot@users.noreply.github.com git tag "$VERSION" git push origin "$VERSION" echo "Successfully created and pushed tag: $VERSION." else echo "The most recent commit does not seem to be a release commit." exit 0 fi open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/scripts/prepare-release-notes.sh000077500000000000000000000005511524057645600336050ustar00rootroot00000000000000#!/bin/bash -ex # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 touch release-notes.md echo "## Changelog" >> release-notes.md awk '//,//' CHANGELOG.md > tmp-chlog.md # select changelog of latest version only sed '1,3d' tmp-chlog.md >> release-notes.md # delete first 3 lines of file open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/scripts/prepare-release.sh000077500000000000000000000022021524057645600324520ustar00rootroot00000000000000#!/bin/bash -ex # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Usage: VERSION=v1.2.3 ./prepare-release.sh # Calls make chlog-update to compile all changelog entries from .chloggen and add them to the main CHANGELOG.md file, # then creates a PR with this change. PATTERN="^v[0-9]+\.[0-9]+\.[0-9]+.*" if [[ "$VERSION" == [0-9]* ]]; then # normalize the VERSION input without leading "v" VERSION="v$VERSION" fi if ! [[ ${VERSION} =~ $PATTERN ]] then echo "VERSION should follow the semver format (with a leading v), i.e. v1.2.3 or v1.2.3-qualifier." exit 1 fi git config user.name opentelemetrybot git config user.email 107717825+opentelemetrybot@users.noreply.github.com BRANCH="prepare-release-prs/${VERSION}" git checkout -b "${BRANCH}" make chlog-update VERSION="${VERSION}" git add --all git commit -m "docs: update changelog to prepare release ${VERSION}" git push --set-upstream origin "${BRANCH}" gh pr create --head "$(git branch --show-current)" --title "chore: prepare release ${VERSION}" --body " The following commands were run to prepare this release: - make chlog-update VERSION=${VERSION} " open-telemetry-opentelemetry-injector-ea9dca1/.github/workflows/tooling.yml000066400000000000000000000014421524057645600275530ustar00rootroot00000000000000name: Tooling on: push: branches: - main paths: - "tooling/**" - ".github/workflows/tooling.yml" pull_request: paths: - "tooling/**" - ".github/workflows/tooling.yml" permissions: contents: read jobs: nextversion: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 with: go-version: "1.26.6" cache: true cache-dependency-path: tooling/nextversion/go.sum - name: go vet working-directory: ./tooling/nextversion run: go vet ./... - name: go test working-directory: ./tooling/nextversion run: go test -race -count=1 ./... open-telemetry-opentelemetry-injector-ea9dca1/.gitignore000066400000000000000000000000461524057645600237470ustar00rootroot00000000000000.zig-cache zig-out so/ dist/ .tools/open-telemetry-opentelemetry-injector-ea9dca1/.shellcheckrc000066400000000000000000000001071524057645600244100ustar00rootroot00000000000000enable=useless-use-of-cat disable=SC2164 # cd/popd/push failure check open-telemetry-opentelemetry-injector-ea9dca1/.vscode/000077500000000000000000000000001524057645600233205ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/.vscode/settings.json000066400000000000000000000001761524057645600260570ustar00rootroot00000000000000{ "zig.testArgs": [ "test", "-lc", "--test-filter", "${filter}", "${path}" ] }open-telemetry-opentelemetry-injector-ea9dca1/CHANGELOG.md000066400000000000000000000457271524057645600236070ustar00rootroot00000000000000 # Changelog ## v0.11.0 ### 💡 Enhancements 💡 - `dotnet`: Make the minimum .NET major version required for injection configurable. (#409) The injector inspects the `*.runtimeconfig.json` file of the target application and skips the injection of the .NET auto-instrumentation if the application does not target a supported .NET version. The minimum supported .NET major version (previously hardcoded to 8) can now be configured via the configuration file key `dotnet_auto_instrumentation_minimum_dotnet_major_version` or the environment variable `DOTNET_AUTO_INSTRUMENTATION_MINIMUM_DOTNET_MAJOR_VERSION`. The default remains 8, matching the .NET versions supported by the upstream OpenTelemetry .NET auto-instrumentation. Distributions of the .NET auto-instrumentation that support older .NET versions can lower this threshold accordingly. - `injector`: Remove architecture gate from .NET injection; delegate SDK availability checks to downstream adopters. (#411) Previously the injector refused to inject .NET on any architecture other than `amd64` and `arm64`. The injector now constructs the expected SDK path for any architecture and relies on the existing file-existence check to skip injection when the SDK is absent, consistent with how every other runtime (Node.js, Python, JVM, Ruby) behaves. Downstream adopters (system packages, the OpenTelemetry Operator) can install a .NET SDK for any architecture under the standard path and have it picked up automatically. ## v0.10.1 ### 🧰 Bug fixes 🧰 - `injector`: Fix integer overflow panic when a GNU hash bucket is empty during the second-pass library scan on glibc < 2.34 systems. (#399) On pre-glibc-2.34 systems (e.g. RHEL 8, Debian Bullseye) the injector's second-pass scan of `/proc/self/maps` calls `ElfDynLib.lookupAddress` on every mapped shared object. If the GNU hash bloom filter false-positives for a looked-up symbol and the corresponding bucket value is zero (empty bucket) or otherwise less than `symoffset`, the `u32` subtraction underflows and triggers a ReleaseSafe `panic: integer overflow` that aborts the host process with `SIGABRT` before its own code runs. The fix mirrors glibc's `dl-lookup` behaviour: treat any bucket value below `symoffset` as "symbol not present" and return `null`. ## v0.10.0 ### 🛑 Breaking changes 🛑 - `packaging`: The DEB and RPM system packages for the OpenTelemetry Injector have moved to open-telemetry/opentelemetry-packaging. This repository no longer builds or releases .deb / .rpm artifacts; tagged releases from here now contain only the raw libotelinject_.so shared library. (#395) Download the DEB and RPM packages from https://github.com/open-telemetry/opentelemetry-packaging/releases. Packaging bugs and feature requests should be filed at https://github.com/open-telemetry/opentelemetry-packaging. The port over was tracked in open-telemetry/opentelemetry-packaging#5. ### 💡 Enhancements 💡 - `ruby`: Add support for injecting the Ruby OpenTelemetry auto-instrumentation via RUBYOPT. (#367) When a Ruby auto-instrumentation agent path is configured (via ruby_auto_instrumentation_agent_path_prefix or the RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX environment variable), the injector sets RUBYOPT to require the agent's entry file and OTEL_RUBY_ADDITIONAL_GEM_PATH so the gem can locate its bundled dependencies. The path is resolved per libc flavor (glibc/musl). ## v0.9.2 ### 🧰 Bug fixes 🧰 - `injector`: Silently fall back to defaults when `/proc/self/environ` is unreadable. (#361) When the host process drops privileges before the injector runs (e.g. a package postinst script that switches to a service user before starting a daemon), opening `/proc/self/environ` fails with `AccessDenied`. This previously bubbled up as an error-level log entry in the host process's own log stream. The injector now treats `AccessDenied` the same as any other read failure and silently falls back to the default log level and disabled state. ## v0.9.1 ### 💡 Enhancements 💡 - `injector`: Add a build-time option to override which environment variable prefixes are accepted from `all_auto_instrumentation_agents_env_path`. (#351) ### 🧰 Bug fixes 🧰 - `injection`: Skip .NET injection for unsupported runtime versions. (#360) - `injector`: Parse quoted values in injector configuration files with bash-style semantics. (#373) Values in `default_env.conf` and other injector configuration files were previously used verbatim after whitespace trimming, so `KEY="value"` would inject a literal `"` into the target process environment. The parser now follows bash quoting rules for well-formed input: a matching pair of surrounding double or single quotes is stripped; inside double quotes, the escapes `\"`, `\\`, `\$`, and `` \` `` are processed while other backslash sequences (`\n`, `\t`, `\z`, ...) are kept literally; inside single quotes, no escape processing is performed. For malformed input the parser deliberately diverges from shell semantics: values with stray or unbalanced quote characters (e.g. `KEY="value`) are passed through literally rather than rejected, because the parser is line-oriented and cannot continue reading past a newline to find a matching closing quote. A warning is emitted in that case so the mistake is visible. ## v0.9.0 ### 🧰 Bug fixes 🧰 - `injector`: Fix injection failure on glibc < 2.34 when the target binary uses copy relocation on environ (#348) The injector previously resolved `__environ` (a data symbol) from the ELF symbol table, which returned a null pointer on binaries with copy relocation (e.g. Node.js on Debian Bullseye / glibc 2.31), causing pre-existing values of `NODE_OPTIONS`, `OTEL_RESOURCE_ATTRIBUTES`, etc. to be overwritten rather than merged. The fix switches to resolving `getenv` (a function), which is not subject to copy relocation. ## v0.8.0 ### 🧰 Bug fixes 🧰 - `injector`: Do not overwrite existing .NET profiler and startup-hook environment variables when deciding whether to inject .NET auto-instrumentation (#270) If the target process already defines `CORECLR_ENABLE_PROFILING`, `CORECLR_PROFILER`, `CORECLR_PROFILER_PATH`, `DOTNET_ADDITIONAL_DEPS`, `DOTNET_SHARED_STORE`, or `DOTNET_STARTUP_HOOKS`, the injector now stands down for .NET instead of overwriting those values. `OTEL_DOTNET_AUTO_HOME` alone does not block injection. - `injection`: Skip .NET auto-instrumentation for applications that already reference OpenTelemetry packages. (#267) - `injector`: Fix libc detection failure on glibc < 2.34 systems (e.g. RHEL 8) for binaries that do not link libdl.so (#311) On glibc < 2.34 (e.g. RHEL 8 / glibc 2.28), `dlsym` is exported by `libdl.so` rather than `libc.so` itself. Binaries that do not link `libdl.so` (such as `grep`, simple C utilities, and many other system programs) would produce a `failed to identify libc` warning and receive no instrumentation. The fix adds a fallback that looks up `setenv` and `__environ` directly from the ELF symbol table when `dlsym` is not found, which succeeds for `libc.so` on all glibc versions. ## v0.7.0 ### 🛑 Breaking changes 🛑 - `config`: use a directory structure with multiple files as config source (#291) Breaking change: The in-code defaults for the auto-instrumentation agent paths are now empty. Paths must be provided via external configuration. Users who use the injector binary directly, but do not use the system packages published by this project, and who also have previously relied on the default auto-instrumentation agent paths must now explicitly configure the agent paths. This can be done either via the main configuration file (default: `/etc/opentelemetry/injector/injector.conf`), or conf.d-style files in the configuration directory (i.e. files under `/etc/opentelemetry/injector.d`), or via environment variables. The configuration now moves under `/etc/opentelemetry/injector/`, with the main configuration file as `/etc/opentelemetry/injector/injector.conf`, merging with drop-in files under `/etc/opentelemetry/injector/conf.d/`. The environment variable configuration file also moves from `/etc/opentelemetry/default_auto_instrumentation_env.conf` to `/etc/opentelemetry/injector/default_env.conf`. ## v0.6.0 ### 🧰 Bug fixes 🧰 - `injector`: Evaluate include/exclude process filters before attempting libc detection, eliminating spurious "failed to identify libc" warnings for filtered-out processes. (#311) - `injector`: Fix bug where `include_paths` was ignored when `include_with_arguments` was not configured. Each non-empty include/exclude setting now adds an independent condition (AND across settings, OR within entries). (#312) ## v0.5.0 ### 💡 Enhancements 💡 - update all auto-instrumentation agents: | Package | Change | |---------|--------| | [@opentelemetry/auto-instrumentations-node](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/auto-instrumentations-node#readme) ([source](https://redirect.github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/auto-instrumentations-node)) | `v0.59.0` → `0.72.0` | | [open-telemetry/opentelemetry-dotnet-instrumentation](https://redirect.github.com/open-telemetry/opentelemetry-dotnet-instrumentation) | `v1.11.0` → `v1.14.1` | | [open-telemetry/opentelemetry-java-instrumentation](https://redirect.github.com/open-telemetry/opentelemetry-java-instrumentation) | `v2.16.0` → `v2.26.1` | ## v0.4.0 ### 💡 Enhancements 💡 - `injection`: Read `OTEL_INJECTOR_DISABLED` before detecting the libc flavor (#268) Read the environment variable `OTEL_INJECTOR_DISABLED` earlier, before attempting to detect the libc flavor. The advantage is that `OTEL_INJECTOR_DISABLED` can now be used as an escape hatch if the libc detection code causes issues. ## v0.3.0 ### 💡 Enhancements 💡 - `injection`: Add option to disable auto-instrumentation for specific runtimes or all runtimes (#240) When the config file option `auto_instrumentation_disabled` or the environment variable `OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED` is set to `*`, auto-instrumentation for all runtimes will be disabled. When set to a comma-separated list of runtimes, auto-instrumentation for the listed runtimes will be disabled. In contrast to `OTEL_INJECTOR_DISABLED=true` which disables the injector wholesale, injection of additional resource attributes via `OTEL_RESOURCE_ATTRIBUTES` still happens when `auto_instrumentation_disabled` or `OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED` are used. ## v0.2.0 ### 💡 Enhancements 💡 - `injection`: Add option to completely disable the injector with an environment variable (#240) When OTEL_INJECTOR_DISABLED is set to "true" no environment variables will be set by the injector ## v0.1.0 ### 💡 Enhancements 💡 - `logging`: Adds process id to each log line (#200) - `python`: Add opt-in support for Python auto-instrumentation (#216) Add support for prepending to PYTHONPATH to enable Python auto-instrumentation. For now, auto-instrumentation for Python is off by default and no default Python auto-instrumentation agent is included in the system packages. Python auto-instrumentation needs to be enabled explicitly by providing a Python auto-instrumentation agent and a custom otelinject.conf file with python_auto_instrumentation_agent_path_prefix set or by setting PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX. Be aware that it is not always safe to inject the existing zero-code instrumentation for Python due to possible dependency conflicts (protobuf etc.) ## v0.0.3-20260109 ### 🛑 Breaking changes 🛑 - `injector`: Remove link-time dependency on `__environ`, inject environment variables once at startup via setenv instead of overriding libc's getenv function. (#166) This removes the declaration `extern var __environ: [*]u8` from the injector, that is, the dependency on the `__environ` pointer symbol exported by libc. Motivation: The injector may be linked to binaries that, while dynamically linked, do not link any libc. This would result in the injector preventing the program from starting due to a linker error, effectively crashing the application under monitoring. This change introduces a way to look up the `dlsym` symbol, and then in turn through `dlsym` the `__environ` and `setenv` symbols. This happens at runtime in an initialization step, by inspecting the process' memory and finding the `dlsym` symbol in memory. This allows us to read and manipulate the process environment without depending on any external symbols at link time. More importantly, we have the option to stand down if looking up `dlsym`/`__environ`/`setenv` fails, and let the host process continue, instead of crashing it at startup. Also, the injector now no longer overrides libc's `getenv` to inject environment variables but instead unconditionally injects all environment variables by calling `setenv` at startup. This change is necessary, because even when removing the dependency on the `__environ` symbol (see above), there would still be potential to break certain executables: - For example, on some older distributions (Debian 11/bullseye is one), `libc` and `libdl` are two distinct libary files, with `libdl` containing `dlopen`, `dlcose`, `dlsym` and `dlerror`. When a binary only binds `libc`, but not `libdl`, we would not find the `dlsym` symbol (because it is actually really not loaded into memory), hence we wouldn't be able to find the `__environ` or `setenv` symbols. As a consequence, we would not be able to backfill the injector-internal representation of the environment (std.os.environ/std.posix.getenv in the Zig code) and _all_ calls to `getenv` would return null; i.e. we would effectively obliterate the environment of the process. - Another issue is that, if a binary binds `libcrypto` (i.e. uses OpenSSL), it will try to look up the `OPENSSL_armcap` environment variable before the injector init code even has ran. With the `getenv` override, this would always return null, even if `OPENSSL_armcap` is set (independent of the `libdl` issue described above). Hence, overriding `getenv` would break some of OpenSSL's assumptions. - Many runtimes do not use `getenv` to read environment variables. E.g. the JVM as well as the CLR use `getenv` in their native startup code, but when environment variables are read from with the runtime, the reading bypasses `getenv`. Hence, overriding getenv is not feasible to inject reliably into these runtimes. The same is true for Python. Injecting via `setenv` solves this problem. Last but not least, we no longer inject resource attributes into the JVM via the workaround of adding `-Dotel.resource.attributes` to `JAVA_TOOL_OPTIONS`. This was only necessary because the JVM does not read `OTEL_RESOURCE_ATTRIBUTES` via `getenv` (see previous paragraph), hence this was a necessary workaround with the override-getenv approach. With the switch to `setenv`, it is no longer necessary. ### 💡 Enhancements 💡 - `environment`: Limit all_auto_instrumentation_agents_env_path env vars to entries prefixed with OTEL_. (#176) - `config`: add OTEL_INJECTOR_CONFIG_FILE to set config file location (#188) This adds support for a new environment variable `OTEL_INJECTOR_CONFIG_FILE` that determines from which path the injector's configuration file is read. If the variable is not set or empty, the configuration file is read from the default path `/etc/opentelemetry/otelinject.conf`. This is useful in environments where you want to provide a configuration file, but you do not want to make any assumptions about the underlying file system's structure. I.e. you want to keep all assets of the injector (injector binary, auto-instrumentation agents and the injector configuration) completely separate from standard OS directories like `/etc`. ### 🧰 Bug fixes 🧰 - `environment`: Fix out of index error when leveraging all_auto_instrumentation_agents_env_path with more than one env var. (#173) - `config`: Fix handling of overly long lines in config file (#174) Continue to read the rest of the config file after discarding an overly long line ## v0.0.2-20251216 ### 🛑 Breaking changes 🛑 - `systemd`: Remove systemd instructions (#68) We can remove systemD instructions as the LD_PRELOAD solution works just as well and applies to any software. ### 💡 Enhancements 💡 - `injector`: Adds support for selectively applying instrumentation to subset of programs running on a system (#84) The change allows users to implement filters on what to instrument based on the executable name and the command line arguments - `environment`: Add option to pass custom environment variables to agents (#82) Now it is possible to pass custom environment variables to agents. New `all_auto_instrumentation_agents_env_path` configuration option is used to specify path to the `auto_instrumentation_env.conf` - where custom environment variables could be specified. Injector will pass these variables to agents. ### 🧰 Bug fixes 🧰 - `nodejs`: test nodeJS support (#127) Testing identified issues with the RPM and Debian packages. The Debian and RPM packages now contain the nodeJS dependencies organized in a folder structure rather than an archive. The configuration points correctly to the file to load to launch autoinstrumentation. NodeJS tests now run as part of CI. ## v0.0.1-20251030 ### 🛑 Breaking changes 🛑 - `injector`: Replace the C-based OpenTelemetry injector with an injector written in Zig, donated by Dash0. (#230) This new injector will be the foundation for future enhancements of LD_PRELOAD based auto-instrumentation. It replaces the current injector (written in C) with its setenv-based approach with a new injector (written in Zig) that overrides getenv. The previous injector was built in a way that binds to a libc. This works when the injector is distributed as an RPM or DEB package, but is not suitable for instrumenting workloads on systems where the libc flavor (glibc or musl) is not known ahead of time, e.g. instrumenting arbitrary Kubernetes workloads. Furthermore, the new injector behaves more gracefully when the environment variable that is to be injected (e.g. JAVA_TOOL_OPTIONS, or NODE_OPTIONS) is already present, as it merges existing options with the key-value pairs it injects. Since this change is a complete rewrite, it is marked as a breaking change. open-telemetry-opentelemetry-injector-ea9dca1/CONTRIBUTING.md000066400000000000000000000076631524057645600242240ustar00rootroot00000000000000# How to Contribute to the OpenTelemetry Injector We'd love your help! This project is [Apache 2.0 licensed](LICENSE) and accepts contributions via GitHub pull requests. This document outlines some of the conventions on development workflow, contact points and other resources to make it easier to get your contribution accepted. We gratefully welcome improvements to documentation as well as to code. ## Getting Started ### Pre-requisites * Docker version 23.0.0 or greater. ### Local development The injector is written in [Zig](https://ziglang.org/). To work on the injector [source code](src) it is recommended to install Zig on your machine. You can find the installation instructions [here](https://ziglang.org/download/). Install the version referenced in [zig-version](zig-version). The [Zig Version Manager](https://github.com/tristanisham/zvm) is also a good alternative to installing one specific version of Zig. See the page [Zig Learn](https://ziglang.org/learn/) for more information about the language. ### Makefile commands * `make zig-build` to build the injector binary locally on your machine * Set a comma-separated list of allowed environment variable prefixes using `ALLOWED_ENV_VAR_PREFIXES`, e.g.: `OTEL_,CUSTOM_PREFIX_`, to allow additional env var prefixes in a custom build. Default value is `OTEL_`. * `make watch-zig-build` to continually rebuild the Zig sources on every change (requires [`fd`](https://github.com/sharkdp/fd) and [`entr`](https://github.com/eradman/entr) to be installed) * `make zig-unit-tests` to run the unit tests * `watch-zig-unit-tests` to continually run the Zig unit tests on every change (requires [`fd`](https://github.com/sharkdp/fd) and [`entr`](https://github.com/eradman/entr) to be installed) * `make tests` to run the unit tests and the injector integration tests The DEB and RPM system packages that bundle the injector with the auto-instrumentation agents for supported runtimes live in [open-telemetry/opentelemetry-packaging](https://github.com/open-telemetry/opentelemetry-packaging). Please open packaging bugs, feature requests, and pull requests against that repository. ### Github PR Workflow It is recommended to follow the ["GitHub Workflow"](https://guides.github.com/introduction/flow/). When using [GitHub's CLI](https://github.com/cli/cli), here's how it typically looks like: ```bash gh repo fork github.com/open-telemetry/opentelemetry-injector git checkout -b your-feature-branch # do your changes git commit -sam "Add feature X" gh pr create ``` ## Contributing Your contribution is welcome! For it to be accepted, we have a few standards that must be followed. ### New features Before starting the development of a new feature, please create an issue and discuss it with the project maintainers. Features should come with documentation and enough tests (unit and/or end-to-end). ### Bug fixes Every bug fix should be accompanied by a unit test, so that we can prevent regressions. ### Documentation, typos, ... They are mostly welcome! ### Adding a Changelog Entry The [CHANGELOG.md](./CHANGELOG.md) file in this repo is autogenerated from `.yaml` files in the `./.chloggen` directory. Your pull request should add a new `.yaml` file to this directory. The name of your file must be unique since the last release. During the collector release process, all `./.chloggen/*.yaml` files are transcribed into `CHANGELOG.md` and then deleted. If a changelog entry is not required, add the `"Skip Changelog"` label to disable this action. If your pull request title starts with `chore`, `test`, or `ci`, the action is skipped automatically. **Recommended Steps** 1. Create an entry file using `make chlog-new`. This generates a file based on your current branch (e.g. `./.chloggen/my-branch.yaml`) 2. Fill in all fields in the new file 3. Run `make chlog-validate` to ensure the new file is valid 4. Commit and push the file Alternately, copy `./.chloggen/TEMPLATE.yaml`, or just create your file from scratch. open-telemetry-opentelemetry-injector-ea9dca1/DESIGN.md000066400000000000000000000523101524057645600232530ustar00rootroot00000000000000## OpenTelemetry Injector Design The OpenTelemetry injector is a shared library (written in [Zig](https://ziglang.org/)) that is intended to be used via the environment variable [`LD_PRELOAD`](https://man7.org/linux/man-pages/man8/ld.so.8.html#ENVIRONMENT), the [`/etc/ld.so.preload`](https://man7.org/linux/man-pages/man8/ld.so.8.html#FILES) file, or similar mechanisms to inject environment variables into processes at startup. See [README.md](README.md) for a general purpose overview. This document focusses on the technical design and the design constraints leading to the approach taken by this project. In the remainder of this document `LD_PRELOAD` and `/etc/ld.so.preload` are used interchangeably. ## Design Constraints * The injector must work seamlessly with the two dominant CPU architectures found with Linux workloads, x86_64 and arm64. It should be possible to extend the support to other CPU architectures later on. (Note: When distributed as a container image, it is trivial to achieve this, by compiling the injector binary for all supported CPU architectures and building a multi-platform image.) * The injector must work seamlessly with both libc flavors, the [GNU C library](https://sourceware.org/glibc/) (or glibc for short) and [musl](https://musl.libc.org/). When adding the injector via `LD_PRELOAD` to systems where the libc flavor is not known ahead of time, this is crucial to ensure that the injector does not crash executables at startup. Specifically in a Kubernetes context, tools like Kubernetes operators do typically not know the libc flavor of container images used in pods, and have no reliable way of finding out. * The injector must be able to inject environment variables into any dynamically linked, ELF-based Linux executable. Other formats besides ELF (which are very rare on Linux) are out of scope. Other operating systems (Windows, macOS, BSD, etc.) are out of scope as well. * While it is out of scope for this project to inject environment variables into freestanding executables, libc-free executables, or statically linked executables etc. (only dynamically linked executables are supported because of the reliance on `LD_PRELOAD`), the injector must not break these types of executables. That is, using the injector as a system-wide `LD_PRELOAD` setting must not prohibit any executable on the system from starting correctly. * As an extension, to the previous point, it is also not acceptable to interfere with the environment of executables except for the desired modifications. For example, if the executable reads `JAVA_TOOL_OPTIONS` at startup, it must see the modified value that includes the `-javaagent` flag added by the injector. But if the executable reads an unrelated environment variable like `PATH`, it must see exactly what it would see without the injector being present. * In general, the injector must not alter the observed behavior of the executable, except for the desired modifications. * In short, it must always be safe to set up the injector as a system-wide `LD_PRELOAD` hook. * As a stretch goal, it would be desirable to not pollute the environment with unrelated environment variables, that is, do not set `NODE_OPTIONS` for a JVM process, or `JAVA_TOOL_OPTIONS` for a Node.js process, etc. The currently implemented approach does not achieve this. All known approaches that satisfy this have other, more severe limitations. Note: Statically linked executables will not be affected by `LD_PRELOAD` at all, but there are [binaries](#dynamically-linked-libc-free-binaries) that are dynamically linked and affected by `LD_PRELOAD`, but do _not_ link against any libc. Binaries of this type require special consideration. ## Design The approach taken by the OpenTelemetry injector is as follows: * It assumes it is added as a shared object to the process via `LD_PRELOAD` or `/etc/ld.so.preload`. When set up this way, whenever a Linux process starts up that is subject to dynamic linking (that is, it uses shared objects like the C standard library `libc`), the OpenTelemetry injector code is loaded at process startup. * The OpenTelemetry injector (`libotelinject.so`), via ELF's `.init_array`, executes the following steps at the startup of the executable, that is, before the actual `main()` function of the executable is called: * Find out which libc flavor we are dealing with - glibc or musl: * Read the ELF header section from `/proc/self/exe`, and the dynamic symbol table in particular, then find the `DT_NEEDED` entries in the dynamic symbol section. * Inspect the `DT_NEEDED` entries. If one of them contains the string `musl`, we know that this binary was linked against musl libc at build time. If one of them contains `libc.so.6`, we know that this binary was linked against glibc at build time. * Next, we try to find the location of the [`dlsym`](https://www.man7.org/linux/man-pages/man3/dlsym.3.html) symbol in the mapped memory of the process. This happens as follows: * Read `/proc/self/maps` and look for a memory segment that contains the libc shared object. The libc name found in the previous step facilitates this. Once a matching memory segment is found, check whether it contains the `dlsym` symbol, i.e. look up the memory address of that symbol. If this lookup is successful, we can use it to actually call the `dlsym` function (without declaring a direct dependency on it). * Finally, use `dlsym` to find the location of `setenv` and `getenv`. On glibc < 2.34 (e.g. RHEL 8, Debian Bullseye), `dlsym` is provided by `libdl.so` rather than `libc.so` itself. If the binary does not link `libdl.so`, `dlsym` will not be found in the libc memory range. In that case, the injector falls back to looking up `setenv` and `getenv` directly from the ELF symbol table of the libc memory range, which works because both symbols are always exported directly by `libc.so` on all glibc versions. Unlike `__environ` (a data symbol), `getenv` is a function and is not subject to copy relocation, so looking it up from the ELF symbol table is safe regardless of how the executable was linked. * Use `getenv` to read the existing environment variables for the process. * Use `setenv` to set or modify the required environment variables (`NODE_OPTIONS`, `JAVA_TOOL_OPTIONS`, `OTEL_RESOURCE_ATTRIBUTES` etc.) For .NET, the injector applies an additional check before setting the profiler and startup-hook related environment variables. It resolves the target application and inspects the adjacent `*.runtimeconfig.json` file when available, using `runtimeOptions.tfm` to decide whether the target is a compatible .NET application. It stands down when the application clearly targets an older framework, but missing, unreadable, malformed, or incomplete runtimeconfig files remain fail-open. Following that check, the injector also inspects the adjacent `*.deps.json` file when available and stands down only when the dependency graph already references `OpenTelemetry*` packages. This keeps the injector fail-open while still reducing the risk of double-instrumentation. If this sounds convoluted, and more complex than it should be, read on! The next section outlines which alternative approaches have been considered, and the shortcomings of each of them. ## Alternative Approaches ### Using `setenv` and `getenv` directly A very simple approach to implementing an injector would be to just declare a dependency on symbools like `setenv` and `getenv`. This would require that the injector is compiled and linked against a specific libc flavor, either glibc or musl. When used as an `LD_PRELOAD` hook on a system that uses a different libc flavor, this would crash all processes at startup. For example, if the injector would be linked against musl, the injector binary would explicitly request musl in its ELF header (e.g. `libc.musl-aarch64.so.1`). When added via `LD_PRELOAD` on a glibc based system, the kernel would look for this dependency, fail to find it, and refuse to start the executable. For the same reason, we can also not declare a direct dependency on `dlsym`. Another alternative would be to really only declare a dependency on these symbols (e.g. via `extern`) and not provide it at compile time, relying on the linker/loader to resolve the symbol at process startup. This way, the injector would work on glibc- as well as musl-based systems, as long as the symbols are present in memory at process startup. However, this will approach would crash [binaries](#dynamically-linked-libc-free-binaries) that are subject to dynamic linking but do not link against any libc. At startup, the linker/loader would not be able to resolve the symbols the injector declares as dependencies, and refuse to start the executable. ### Export `getenv` Instead of trying to depend on existing symbols from libc, the injector could _export_ its own `getenv` symbol to override libc's `getenv` function. When a process starts up, and the executable declares that it needs the `getenv` function, the dynamic linker/loader finds the `getenv` symbol provided by the injector first. In effect, whenever the process calls `getenv`, the injector's `getenv` implementation is called instead of the original libc version of that function. For environment variables that the injector does not care about, it returns the original value, and for the "interesting" environment variables, it modifies the value on the fly before returning it. In fact, this strategy would get us 99% of the way, and it has been used in previous incarnations of the OpenTelemetry injector. To make this work, the injector needs to be able to read the process environment on its own, since it cannot rely on libc functionality to get the environment. (And it obviously needs to know the environment to be able to return the original values for environment variables it does not want to modify and also for appending to the ones it needs to modify, if they are already set.) One way of doing that is to declare a dependency on the `__environ` symbol (e.g. `extern char **__environ`), which is a pointer to the in-memory storage of the environment. Again, this will crash [binaries](#dynamically-linked-libc-free-binaries) that are subject to dynamic linking but do not link against any libc. At startup, the linker/loader would not be able to resolve the `__environ` symbol, and refuse to start the executable. Another way of getting the environment would be to read the file `/proc/self/environ`, which contains the environment variables as a null-separated list of `KEY=VALUE` strings. This would solve the problem of the missing `__environ` symbol at startup. Unfortunately, there are still issues with the approach to export `getenv`: * Most importantly, a lot of runtimes do not use `getenv` consistently to read environment variables. Instead, they declare a dependency on the `__environ` symbol (or one of its aliases like `_environ` or `environ`) directly and read directly from the content of this symbol. This includes the JVM, Python, and .NET. Often, this way of reading environment variables without using `getenv` is only is used for in-runtime lookups of environment variables. That is, at startup, the native code the runtime is implemented in will actually use `getenv` -- for example, the JVM reads `JAVA_TOOL_OPTIONS` via `getenv`, the common language runtime for .NET reads `CORECLR_ENABLE_PROFILING`, `CORECLR_PROFILER` etc. via `getenv` and so on. But once the runtime is up and running, it will use the content of the `__environ` array (or a copy of that content) directly for any further lookups of environment variables that are made within Java or .NET code. This renders the `getenv` override ineffective for injecting `OTEL_RESOURCE_ATTRIBUTES` into the JVM, the CLR or Python. (The reason for this is that `OTEL_RESOURCE_ATTRIBUTES` is read within the runtime by the respective OpenTelemetry SDK, not by the runtime implementation itself. For the JVM there would be a workaround for `OTEL_RESOURCE_ATTRIBUTES` by adding a `-Dotel.resource.attributes` to `JAVA_TOOL_OPTIONS`, but there is no generalized workaround that also works for .NET and Python.) * When using the approach of exporting `getenv`, one has to also take care of modifications of the environment at runtime. Imagine the following sequence of events: * the process calls `getenv("SOME_ENV_VAR"`), * the injector's `getenv` override returns the value of `SOME_ENV_VAR` from its own memory, which has been populated (maybe from `__environ` or from `/proc/self/environ` or otherwise) at startup, * the process later modifies the environment variable (e.g. calls `setenv("SOME_ENV_VAR", "new-value", 1)`, * the process calls `getenv("SOME_ENV_VAR"`) again, * the injector's `getenv` override must make sure to return the new value. This is trivial when declaring a dependency on `__environ` (which has its own problems), but not when initializing the environment from `/proc/self/environ` at startup, in which case it would probably be required to also override `setenv`, `putenv`, `clearenv` etc. ### Export the `__environ` symbol The backing in-memory storage for `getenv`, `setenv` etc. is the symbol `__environ`, a pointer to a list of pointers, each of which is a string in the form of `KEY=VALUE`. What if, instead of exporting `getenv`, the injector would export the `__environ` symbol (and its aliases, that is, `_environ` and `environ`)? It could initialize that array the same way as libc would do it, by reading the file `/proc/self/environ` (which is a null-separated list of `KEY=VALUE` strings) at startup, and then populate its own `__environ` array from that. Then, if the executable declares a dependency on `__environ` (or `_environ` or `environ`), the injector's `__environ` array would be used instead of the libc version. This would solve all issues mentioned in the section [export `getenv`](#export-getenv) -- no `extern` dependency on any libc symbol, consistent behavior when `__environ` is modified after startup, no problems injecting into code that bypasses `getenv`. Theoretically, this should also work for the `__environ` symbol used in the libc's implementation of `getenv`, `setenv` etc., because of how the symbol resolution of the linker works. However, after some experiments, this approach turned out to be unfeasible. An injector with this approach can successfully inject environment variables into _some_ binaries. In particular, this worked with some shells. Since shells are used as entrypoints in many container images, it may seem as if this works reliably, because the injector modifies the environment of the shell entrypoint successfully, and then any process started from that shell inherits the modified environment. However, when using container images which use the binary (e.g. the JVM, Node.js, ...) as their entrypoint directly, without using a shell entrypoint, this approach fails to work for some binaries. The reason is that libc will override the content of `__environ` with the value of `envp` in its own initialization procedure, or in the `execve` system call, that is, after the injector has set up the `__environ` content, but before the application's `main()` function is called. There is currently no known way an `LD_PRELOAD`-based injector can hook into this process. ### Look up `setenv` and `__environ` at runtime, and still override `getenv` With the current approach outlined in the [design](#design) section, we can solve all requirements without breaking any executables. But since the approach makes its modifications via `setenv`, it will pollute the environment of all processes with all environment variables it sets, even if they are not relevant for the specific binary. That is, all process will see `NODE_OPTIONS`, `JAVA_TOOL_OPTIONS` and all the .NET related environment variables, even if it is not a Node.js, JVM or .NET process. Could we not export and override libc's `getenv` and handle the injection of `NODE_OPTIONS`, `JAVA_TOOL_OPTIONS` etc. (which are read via `getenv`) that way, and only use `setenv` for injecting `OTEL_RESOURCE_ATTRIBUTES` (which is not read via `getenv` by some runtimes, as explained above)? That would give us all the benefits of the `getenv` override approach (less environment variable pollution), without any of the drawbacks described in the section [export `getenv`](#export-getenv). There is an issue that makes this particular strategy prohibitive: there can be shared libraries that look up environment variables very early in the startup process, even before the injector had a chance to run its initialization code. A prominent example is OpenSSL, which is used by many runtimes and applications. The OpenSSL code, when run on arm64 CPUS, reads `OPENSSL_armcap` (a capabilities bitmask) before the injector's init code runs, that is, before it even had the chance to find the `__environ` pointer and read the process environment. This would lead to the injector reporting `OPENSSL_armcap=null` to OpenSSL, even if it is actually set. Obviously, this is also not acceptable, hence exporting `getenv` is not a viable approach. In the end, avoiding the risk of breaking assumptions about the environment is deemed much more critical than not adding irrelevant environment variables to the environment of all processes, which is effectively more an aesthetics concern. If an executable does not read `NODE_OPTIONS` (because it is not Node.js), or `JAVA_TOOL_OPTIONS` (because it is not a JVM), it effectively makes no difference that these variables are added. ## Dynamically Linked libc-free Binaries Several of the sections above make reference to binaries that are dynamically linked, but do not link against any libc, and how that is problematic for an `LD_PRELOAD`-based injector, especially when it declares a dependency on any symbol that is usually provided by libc, like `setenv`, `getenv` or `__environ`. But does something like this actually exist out in the wild? Yes, it does. One example is the `aws-vpc-cni` binary (and probably other related `aws-` binaries that are built in the same way), from the suite of CNI network plug-ins, which runs in Kubernetes pods in the `kube-system` namespace in EKS clusters. Here is how this binary is [built](https://github.com/aws/amazon-vpc-cni-k8s/blob/4ee9789484258d1ae8f6bf36859ea325097d5d7b/Makefile#L149-L152): It is written in Go and built with `-buildmode=pie` and `-ldflags '-s -w'`. There is also a [trivial test application](injector-integration-tests/apps/no-getenv-symbol) that is built in the same way, contained in this repository. When using an `LD_PRELOAD`-based injector that declares a dependency on `__environ` (or any other libc symbol), a binary built in this way will crash at startup with an error message like this: ``` /app/aws-vpc-cni: symbol lookup error: /path/to/libotelinject.so: undefined symbol: __environ ``` This is because the binary is dynamically linked, hence it is affected by `LD_PRELOAD`, but it does not link against any libc, hence the `__environ` symbol cannot be resolved. This was a known issue in a previous version of the injector, the current implementation has solved this problem by not declaring any direct dependencies on external symbols. ## Why Zig? Desirable characteristics for the programming language for the injector: * No dependency on libc: In particular when using the injector in a container environment, you cannot know ahead of time whether the processes the injector attaches to are based on glibc or musl. Binding the injector itself to a specific libc flavor or version would be problematic, as it would crash processes that bind a different libc. * Provide a rich enough set of standard functionality, without depending on a libc. A lot of compiled languages can be built without binding libc, but they then also usually lack basic functions, like string comparisons etc. and you end up re-implementing these basics. * Manual memory management: The injector needs to introduce as little overhead as possible to the system and the processes it attaches to, in terms of memory, CPU usage, and startup time. Manual memory management ensures the injector code is very deliberate about the memory it allocates. Not relying on garbage collection also works in favor of avoiding accidental CPU usage overhead. Zig has combination of characteristics that make it a good match for the items listed above: * It does not bind a libc flavor, but still provides a rich standard library. * It does not have automatic memory management, but relies on manual memory management instead. Let's compare this to some other programming languages that might be used for the injector: * The preference for manual memory management rules out Go. * Rust is also brought up fairly often, but it binds libc by default. `#![no_std]` is available, but that also removes access to basic functionality (printing, working with strings). * Traditionally, similar technology has been written in C++ or plain C. This is possible, but again, without binding to libc, you would have to rewrite a lot of basic functionality. Also, Zig avoids a lot of the footguns that C code can introduce, that is, code written in Zig is probably usually safer than C. * Zig also supposedly compiles fairly [fast](https://mitchellh.com/writing/zig-builds-getting-faster), making it pleasant to work with (that said, the injector code base is quite small, so compiler perfomance is not very crucial). open-telemetry-opentelemetry-injector-ea9dca1/Dockerfile000066400000000000000000000011161524057645600237500ustar00rootroot00000000000000ARG DOCKER_REPO=docker.io FROM ${DOCKER_REPO}/alpine:3.24.1@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b AS build-injector RUN apk add --no-cache make ARG ZIG_ARCHITECTURE RUN mkdir -p /opt/zig WORKDIR /opt/zig COPY zig-version . RUN . /opt/zig/zig-version && \ wget -q -O /tmp/zig.tar.gz https://ziglang.org/download/${ZIG_VERSION%-*}/zig-${ZIG_ARCHITECTURE}-linux-${ZIG_VERSION}.tar.xz && \ tar --strip-components=1 -xf /tmp/zig.tar.gz ENV PATH="$PATH:/opt/zig" WORKDIR /libotelinject COPY Makefile . COPY build.zig . COPY build.zig.zon . COPY src src open-telemetry-opentelemetry-injector-ea9dca1/LICENSE000066400000000000000000000261351524057645600227730ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. open-telemetry-opentelemetry-injector-ea9dca1/Makefile000066400000000000000000000146701524057645600234270ustar00rootroot00000000000000ARCH?=amd64 VERSION?=0.0.0-dev ALLOWED_ENV_VAR_PREFIXES?=OTEL_ INSTALL_DIR:=/usr/lib/opentelemetry/otelinject DIST_DIR_BINARY:=dist BINARY_NAME_PREFIX:=libotelinject BINARY_NAME_NO_ARCH:=$(BINARY_NAME_PREFIX).so BINARY_NAME:=$(BINARY_NAME_PREFIX)_$(ARCH).so # Docker repository used. DOCKER_REPO?=docker.io DIST_SRCS:=\ $(wildcard src/*.*) \ build.zig \ build.zig.zon \ Dockerfile \ zig-version DIST_TARGET:=$(DIST_DIR_BINARY)/$(BINARY_NAME) .PHONY: all all: so/$(BINARY_NAME_NO_ARCH) so: @mkdir -p so obj: @mkdir -p obj .PHONY: clean clean: rm -rf so $(DIST_DIR_BINARY) zig-out .zig-cache so/$(BINARY_NAME_NO_ARCH): so zig build -Dcpu-arch=${ARCH} -Dallowed-env-var-prefixes='$(ALLOWED_ENV_VAR_PREFIXES)' --prominent-compile-errors --summary none $(DIST_TARGET): $(DIST_SRCS) @echo building the injector binary for architecture $(ARCH) @set -e @mkdir -p $(DIST_DIR_BINARY) if [[ "$(ARCH)" = arm64 ]]; then \ ZIG_ARCHITECTURE=aarch64; \ elif [[ "$(ARCH)" = amd64 ]]; then \ ZIG_ARCHITECTURE=x86_64; \ fi; \ docker buildx build --platform linux/$(ARCH) --build-arg DOCKER_REPO=$(DOCKER_REPO) --build-arg ZIG_ARCHITECTURE=$$ZIG_ARCHITECTURE -o type=image,name=libotelinject-builder:$(ARCH),push=false . docker rm -f libotelinject-builder 2>/dev/null || true docker run -d --platform linux/$(ARCH) --name libotelinject-builder libotelinject-builder:$(ARCH) sleep inf docker exec libotelinject-builder make ARCH=$(ARCH) ALLOWED_ENV_VAR_PREFIXES='$(ALLOWED_ENV_VAR_PREFIXES)' SHELL=/bin/sh all docker cp libotelinject-builder:/libotelinject/so/$(BINARY_NAME_NO_ARCH) $(DIST_DIR_BINARY)/$(BINARY_NAME) docker rm -f libotelinject-builder .PHONY: dist dist: $(DIST_TARGET) # Run this to install and enable the auto-instrumentation files. Mostly intended for development. .PHONY: install install: all uninstall mkdir -p $(INSTALL_DIR) cp javaagent.jar $(INSTALL_DIR) cp so/$(BINARY_NAME_NO_ARCH) $(INSTALL_DIR) echo $(INSTALL_DIR)/$(BINARY_NAME_NO_ARCH) > /etc/ld.so.preload .PHONY: uninstall uninstall: rm -f /etc/ld.so.preload rm -f $(INSTALL_DIR)/javaagent.jar rm -f $(INSTALL_DIR)/$(BINARY_NAME_NO_ARCH) # Run this from with this directory to build and run the development container. # Once you have a command line inside the container, you can run make zig-build, make zig-unit-tests, # make watch-zig-build, make watch-zig-unit-tests etc. # Alternatively, you can also use the same commands directly on your development machine, without using the development # container. # By explicitly setting ARCH=arm64 or ARCH=amd64 you can run and test on different CPU platforms. # Mostly intended for development. .PHONY: docker-dev-run docker-dev-run: ARCH=$(ARCH) ./start-injector-dev-container.sh .PHONY: check-zig-installed check-zig-installed: @set +x @if ! zig version > /dev/null; then \ echo "error: zig is not installed. Run 'brew install zig' or similar."; \ exit 1; \ fi .PHONY: zig-build zig-build: check-zig-installed @mkdir -p so @(zig build -Dcpu-arch=${ARCH} -Dallowed-env-var-prefixes='$(ALLOWED_ENV_VAR_PREFIXES)' --prominent-compile-errors --summary none && echo $(shell date) build successful) || (echo $(shell date) build failed && exit 1) .PHONY: watch-zig-build watch-zig-build: check-zig-installed @fd -e zig | entr make zig-build .PHONY: zig-unit-tests zig-unit-tests: check-zig-installed @(zig build test -Dcpu-arch=${ARCH} -Dallowed-env-var-prefixes='$(ALLOWED_ENV_VAR_PREFIXES)' --prominent-compile-errors --summary none && echo $(shell date) tests successful) || (echo $(shell date) tests failed && exit 1) .PHONY: watch-zig-unit-tests watch-zig-unit-tests: check-zig-installed @fd -e zig | entr make zig-unit-tests .PHONY: tests tests: zig-unit-tests injector-integration-tests-for-one-architecture .PHONY: injector-integration-tests-for-one-architecture injector-integration-tests-for-one-architecture: ARCHITECTURES=$(ARCH) injector-integration-tests/scripts/test-all.sh .PHONY: injector-integration-tests-for-all-architectures injector-integration-tests-for-all-architectures: injector-integration-tests/scripts/test-all.sh .PHONY: lint lint: zig-fmt-check zig-validate-test-imports shellcheck-lint .PHONY: zig-fmt-check zig-fmt-check: check-zig-installed @zig fmt --check src # Run this to auto-format Zig code. .PHONY: zig-fmt zig-fmt: check-zig-installed zig fmt src .PHONY: zig-validate-test-imports zig-validate-test-imports: @set -e; \ files=$$(cd src && grep -l 'test "' ./*.zig 2>/dev/null || true); \ [ -n "$$files" ] || { echo "None of the .zig files contain any tests, this is likely an error in the zig-validate-test-imports make recipe."; exit 1; }; \ echo $$names; \ names=$$(printf '%s\n' $$files | sed -e 's|.*/||'); \ missing=0; \ for n in $$names; do \ if ! grep -q "@import(\"$$n\")" src/test.zig; then \ echo "- The file \"src/test.zig\" is missing an import for \"$$n\", tests in \"$$n\" will ignored."; \ missing=1; \ fi; \ done; \ if [ $$missing -gt 0 ]; then \ echo ;\ echo "The file \"src/test.zig\" is missing imports to files with Zig unit tests, see above. Please add all files with test cases to \"src/test.zig\"."; \ exit 1; \ fi .PHONY: check-shellcheck-installed check-shellcheck-installed: @set +x @if ! shellcheck --version > /dev/null; then \ echo "error: shellcheck is not installed. See https://github.com/koalaman/shellcheck?tab=readme-ov-file#installing for installation instructions."; \ exit 1; \ fi .PHONY: shellcheck-lint shellcheck-lint: check-shellcheck-installed @echo "linting shell scripts with shellcheck" find . -name \*.sh | xargs shellcheck -x SHELL = /bin/bash .SHELLFLAGS = -o pipefail -c # SRC_ROOT is the top of the source tree. SRC_ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) TOOLS_BIN_DIR := $(SRC_ROOT)/.tools CHLOGGEN_CONFIG := .chloggen/config.yaml CHLOGGEN := $(TOOLS_BIN_DIR)/chloggen $(TOOLS_BIN_DIR): mkdir -p $@ $(CHLOGGEN): $(TOOLS_BIN_DIR) GOBIN=$(TOOLS_BIN_DIR) go install go.opentelemetry.io/build-tools/chloggen@v0.23.1 FILENAME?=$(shell git branch --show-current) .PHONY: chlog-new chlog-new: $(CHLOGGEN) $(CHLOGGEN) new --config $(CHLOGGEN_CONFIG) --filename $(FILENAME) .PHONY: chlog-validate chlog-validate: $(CHLOGGEN) $(CHLOGGEN) validate --config $(CHLOGGEN_CONFIG) .PHONY: chlog-preview chlog-preview: $(CHLOGGEN) $(CHLOGGEN) update --config $(CHLOGGEN_CONFIG) --dry .PHONY: chlog-update chlog-update: $(CHLOGGEN) $(CHLOGGEN) update --config $(CHLOGGEN_CONFIG) --version $(VERSION) list: @grep '^[^#[:space:]].*:' Makefile open-telemetry-opentelemetry-injector-ea9dca1/README.md000066400000000000000000000650251524057645600232460ustar00rootroot00000000000000## OpenTelemetry Injector > [!IMPORTANT] > **The DEB and RPM packages for the OpenTelemetry Injector have moved.** > They are now built and released from > [open-telemetry/opentelemetry-packaging](https://github.com/open-telemetry/opentelemetry-packaging). > Download the latest `.deb` / `.rpm` from that repository's > [releases page](https://github.com/open-telemetry/opentelemetry-packaging/releases). > > This repository publishes only the raw `libotelinject.so` shared library. > Please open packaging bugs and feature requests against `opentelemetry-packaging`. The OpenTelemetry injector is a shared library (written in [Zig](https://ziglang.org/)) that is intended to be used via the environment variable [`LD_PRELOAD`](https://man7.org/linux/man-pages/man8/ld.so.8.html#ENVIRONMENT), the [`/etc/ld.so.preload`](https://man7.org/linux/man-pages/man8/ld.so.8.html#FILES) file, or similar mechanisms to inject environment variables into processes at startup. It serves two main purposes: * Inject an OpenTelemetry auto-instrumentation agent into the process to capture and report distributed traces and metrics to the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) for supported runtimes. * Set resource attributes automatically (for example Kubernetes related resource attributes and service related resource attributes in environments where this is applicable). The injector can be used to enable automatic zero-touch instrumentation of processes. For this to work, the injector binary needs to be bundled together with the OpenTelemetry auto-instrumentation agents for the target runtimes. Official RPM and DEB packages that bundle the injector together with auto-instrumentation agents for supported runtimes are built and published from the [open-telemetry/opentelemetry-packaging](https://github.com/open-telemetry/opentelemetry-packaging) repository. The OpenTelemetry injector Debian/RPM packages install the OpenTelemetry auto-instrumentation agents, the `libotelinject.so` shared object library, and a default configuration file to automatically instrument applications and services to capture and report distributed traces and metrics to the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/). This repository publishes only the raw `libotelinject.so` shared library. The `opentelemetry-injector` deb/rpm package installs and supports configuration of the following auto-instrumentation agents: - [Java](https://opentelemetry.io/docs/zero-code/java/) - [Node.js](https://opentelemetry.io/docs/zero-code/js/) - [.NET](https://opentelemetry.io/docs/zero-code/dotnet/) - [Ruby](https://github.com/open-telemetry/opentelemetry-ruby-instrumentation) - Python (disabled by default, see [Enabling Auto-Instrumentation for Python](#enabling-auto-instrumentation-for-python)) ## Activation and Configuration This method requires `root` privileges. 1. Add the path of the provided `/usr/lib/opentelemetry/libotelinject.so` shared object library to the [`/etc/ld.so.preload`](https://man7.org/linux/man-pages/man8/ld.so.8.html#FILES) file to activate auto- instrumentation for ***all*** supported processes on the system. For example: ``` echo /usr/lib/opentelemetry/libotelinject.so >> /etc/ld.so.preload ``` Alternatively, set the environment variable `LD_PRELOAD=/usr/lib/opentelemetry/libotelinject.so` for a specific process to activate auto-instrumentation for that process. For example: ``` LD_PRELOAD=/usr/lib/opentelemetry/libotelinject.so node myapp.js ``` 2. The default configuration file `/etc/opentelemetry/injector/injector.conf` includes the required settings, i.e. the paths to the respective auto-instrumentation agents per runtime: ``` dotnet_auto_instrumentation_agent_path_prefix=/usr/lib/opentelemetry/dotnet jvm_auto_instrumentation_agent_path=/usr/lib/opentelemetry/jvm/javaagent.jar nodejs_auto_instrumentation_agent_path=/usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js ``` You can override the location of the configuration file by setting `OTEL_INJECTOR_CONFIG_FILE`. You may want to modify this file for a couple of reasons: - You want to provide your own instrumentation files. - You want to selectively disable auto-instrumentation, either for all runtimes or for a subset of runtimes. See [Disabling auto-instrumentation for all runtimes or specific runtimes](#disabling-auto-instrumentation-for-all-runtimes-or-specific-runtimes) for more information. - You want to selectively enable (or disable) auto-instrumentation for a subset of programs (services) on your system. For example, you may want to only enable instrumentation of services that match a specific executable path pattern, or to programs that do not contain certain arguments on the command line. See [details on configuring the program inclusion and exclusion criteria](#details-on-configuring-the-program-inclusion-and-exclusion-criteria) for more information. The values set in the configuration file can be overridden with environment variables. - `DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX`: the path to the directory containing the .NET auto-instrumentation agent files - `DOTNET_AUTO_INSTRUMENTATION_MINIMUM_DOTNET_MAJOR_VERSION`: the minimum .NET major version an application (as determined by inspecting its `*.runtimeconfig.json` file) needs to target to be eligible for .NET auto-instrumentation; can also be set via the configuration file key `dotnet_auto_instrumentation_minimum_dotnet_major_version`. The default is `8`, matching the .NET versions supported by the upstream OpenTelemetry .NET auto-instrumentation. Distributions of the .NET auto-instrumentation that support older .NET versions can lower this threshold accordingly. - `JVM_AUTO_INSTRUMENTATION_AGENT_PATH`: the path to the Java auto-instrumentation agent JAR file - `NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH`: the path to the Node.js auto-instrumentation agent registration file - `PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX`: the path to the directory containing the Python auto-instrumentation agent files (Python is disabled by default, see [Enabling Auto-Instrumentation for Python](#enabling-auto-instrumentation-for-python)) - `RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX`: the path to the directory containing the Ruby auto-instrumentation gem bundle - `OTEL_INJECTOR_INCLUDE_PATHS`: a comma-separated list of glob patterns to match executable paths - `OTEL_INJECTOR_EXCLUDE_PATHS`: a comma-separated list of glob patterns to exclude executable paths - `OTEL_INJECTOR_INCLUDE_WITH_ARGUMENTS`: a comma-separated list of glob patterns to match process arguments - `OTEL_INJECTOR_EXCLUDE_WITH_ARGUMENTS`: a comma-separated list of glob patterns to exclude process arguments - `OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED`: either `*` to disable all auto-instrumentation for all runtimes, or a comma-separated list of runtimes for which to disable auto-instrumentation, see [Disabling auto-instrumentation for all runtimes or specific runtimes](#disabling-auto-instrumentation-for-all-runtimes-or-specific-runtimes) 3. (Optional) The default env agent configuration file `/etc/opentelemetry/injector/default_env.conf` is empty (use `all_auto_instrumentation_agents_env_path` option to specify a different path). Environment variables added to this file will be passed to all agents' environments. **NOTE**: by default, environment variables which do not start with `OTEL_` are ignored. If you need to allow additional prefixes in a custom build, compile the injector with `zig build -Dallowed-env-var-prefixes=OTEL_,CUSTOM_PREFIX_`. This setting is build-time only. The `auto_instrumentation_env.conf` file format is the same as other configurations: ``` OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317 OTEL_PROPAGATORS=tracecontext,baggage ``` 4. Reboot the system or restart the applications/services for any changes to take effect. The `libotelinject.so` shared object library will then be preloaded for all subsequent processes and inject the environment variables from the `/etc/opentelemetry/injector/injector.conf` configuration files for the process types you've configured (Java, .Net, Node.js or Ruby). When providing your own instrumentation files (for example via environment variables like `DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX`) the following directory structure is expected: - `JVM_AUTO_INSTRUMENTATION_AGENT_PATH`: This path must point to the Java auto-instrumentation agent JAR file `opentelemetry-javaagent.jar`. - `NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH`: The path to an installation of the npm module `@opentelemetry/auto-instrumentations-node`. - `RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX`: this path must be a directory containing `glibc` and `musl` subdirectories. Depending on the libc flavor that the injector detects at startup, the matching subdirectory (a gem home with the `opentelemetry-auto-instrumentation.rb` entry point) is used. - `DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX`: this path must be a directory that contains the following subdirectories and files: - For `x86_64` systems using `glibc`: - `glibc/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so` - `glibc/AdditionalDeps` - `glibc/store` - `glibc/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll` - For `x86_64` systems using `musl`: - `musl/linux-musl-x64/OpenTelemetry.AutoInstrumentation.Native.so` - `musl/AdditionalDeps` - `musl/store` - `musl/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll` - For `arm64` systems using `glibc`: - `glibc/linux-arm64/OpenTelemetry.AutoInstrumentation.Native.so` - `glibc/AdditionalDeps` - `glibc/store` - `glibc/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll` - For `arm` systems using `musl`: - `musl/linux-musl-arm64/OpenTelemetry.AutoInstrumentation.Native.so` - `musl/AdditionalDeps` - `musl/store` - `musl/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll` Note that the defaults provided by the RPM and Debian packages from [open-telemetry/opentelemetry-packaging](https://github.com/open-telemetry/opentelemetry-packaging) take care of all of that, and it is not necessary to edit `/etc/opentelemetry/injector/injector.conf` or set any of the above environment variables. Check the following for details about the auto-instrumentation agents and further configuration options: - [Java](https://opentelemetry.io/docs/zero-code/java/agent/configuration/) - [Node.js](https://opentelemetry.io/docs/zero-code/js/configuration/) - [.NET](https://opentelemetry.io/docs/zero-code/dotnet/configuration/) - [Ruby](https://github.com/open-telemetry/opentelemetry-ruby-instrumentation) ### Environment Modifications Here is an overview of the modifications that the injector will apply: * It sets (or appends to) `NODE_OPTIONS` to activate the Node.js instrumentation agent. * It sets (or prepends to) `RUBYOPT` to require the Ruby auto-instrumentation gem, and sets `OTEL_RUBY_ADDITIONAL_GEM_PATH` so the gem can locate its bundled dependencies. If `OTEL_RUBY_ADDITIONAL_GEM_PATH` is already set in the process's environment, the injector respects that value and also skips modifying `RUBYOPT` (the two variables are a coupled pair — injecting only the `-r` flag would make the gem look for its dependencies in the user-provided path and fail with `LoadError`). * It adds a `-javaagent` flag to `JAVA_TOOL_OPTIONS` to activate the Java OTel SDK. * It conditionally sets the required environment variables for activating the OpenTelemetry SDK for .NET: * `CORECLR_ENABLE_PROFILING` * `CORECLR_PROFILER` * `CORECLR_PROFILER_PATH` * `DOTNET_ADDITIONAL_DEPS` * `DOTNET_SHARED_STORE` * `DOTNET_STARTUP_HOOKS` * `OTEL_DOTNET_AUTO_HOME` * Note that the injector will not append to existing environment variables but overwrite them unconditionally if they are already set. In contrast to other runtimes, .NET does not support adding multiple agents. * The injector first inspects the adjacent `*.runtimeconfig.json` file of the target application when it is available. * The injector stands down if the specified runtime version does not target a supported .NET version. By default, the minimum supported .NET major version is 8, matching the .NET versions supported by the upstream OpenTelemetry .NET auto-instrumentation; it can be changed via the configuration file key `dotnet_auto_instrumentation_minimum_dotnet_major_version` or the environment variable `DOTNET_AUTO_INSTRUMENTATION_MINIMUM_DOTNET_MAJOR_VERSION`. * If the `.runtimeconfig.json` file is missing, unreadable, malformed, or missing the expected fields, the injector proceeds with additional .NET checks. * To reduce the risk of double-instrumentation, the injector then inspects the adjacent `*.deps.json` file of the target application when it is available. * The injector stands down if that `.deps.json` file already references `OpenTelemetry*` packages. * If the `.deps.json` file is missing, unreadable, malformed, or missing the expected fields, the injector proceeds with .NET injection. * It inspects specific existing environment variables and populates `OTEL_RESOURCE_ATTRIBUTES` with additional resource attributes. These environment variables need to be set externally (for example by a Kubernetes operator with a mutating webhook on the pod spec template of the workload). If `OTEL_RESOURCE_ATTRIBUTES` is already set, the additional key-value pairs are appended to the existing value of `OTEL_RESOURCE_ATTRIBUTES`. Existing key-value pairs are not overwritten, that is if e.g. `OTEL_RESOURCE_ATTRIBUTES` already has a key-value pair for `k8s.pod.name`, the existing key-value pair takes priority. The following environment variables and resource attributes are supported: * `OTEL_INJECTOR_RESOURCE_ATTRIBUTES` is expected to contain key-value pairs (e.g. `my.resource.attribute=value,my.other.resource.attribute=another-value`) and will be added as-is. * `OTEL_INJECTOR_SERVICE_NAME` will be translated to `service.name` * `OTEL_INJECTOR_SERVICE_VERSION` will be translated to `service.version` * `OTEL_INJECTOR_SERVICE_NAMESPACE` will be translated to `service.namespace` * `OTEL_INJECTOR_K8S_NAMESPACE_NAME` will be translated to `k8s.namespace.name` * `OTEL_INJECTOR_K8S_POD_NAME` will be translated to `k8s.pod.name` * `OTEL_INJECTOR_K8S_POD_UID` will be translated to `k8s.pod.uid` * `OTEL_INJECTOR_K8S_CONTAINER_NAME` will be translated to `k8s.container.name` #### Mapping Kubernetes Resource Attributes While you can set all resource attributes with `OTEL_INJECTOR_RESOURCE_ATTRIBUTES`, the additional environment variables controlling individual resource attributes (like `OTEL_INJECTOR_SERVICE_NAME` or `OTEL_INJECTOR_K8S_NAMESPACE_NAME`) are useful in Kubernetes for deriving resource attributes via [field selectors](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#use-pod-fields-as-values-for-environment-variables), e.g. by adding a snippet like this to the pod spec template: ``` - name: OTEL_INJECTOR_SERVICE_NAME valueFrom: fieldRef: fieldPath: metadata.labels['app.kubernetes.io/name'] ``` The following provides an overview of the intended mappings: | Environment Variable | Intended Mapping | | ---------------------------------- | ---------------- | | `OTEL_INJECTOR_K8S_NAMESPACE_NAME` | `valueFrom.fieldRef.fieldPath: metadata.namespace` | | `OTEL_INJECTOR_K8S_POD_NAME` | `valueFrom.fieldRef.fieldPath: metadata.name` | | `OTEL_INJECTOR_K8S_POD_UID` | `valueFrom.fieldRef.fieldPath: metadata.uid` | | `OTEL_INJECTOR_K8S_CONTAINER_NAME` | The container's name (no field selector) | | `OTEL_INJECTOR_SERVICE_NAME` | `valueFrom.fieldRef.fieldPath: metadata.labels['app.kubernetes.io/name']` | | `OTEL_INJECTOR_SERVICE_VERSION` | `valueFrom.fieldRef.fieldPath: metadata.labels['app.kubernetes.io/version']` | | `OTEL_INJECTOR_SERVICE_NAMESPACE` | `valueFrom.fieldRef.fieldPath: metadata.labels['app.kubernetes.io/part-of']` | The environment variable `OTEL_INJECTOR_RESOURCE_ATTRIBUTES` can be set to key-value pairs derived from the annotations `resource.opentelemetry.io/*`, to support mapping annotations like `resource.opentelemetry.io/service.namespace`, `resource.opentelemetry.io/service.name` to their respective resource attributes. See also: * https://opentelemetry.io/docs/specs/semconv/resource/k8s/ * https://opentelemetry.io/docs/specs/semconv/non-normative/k8s-attributes/ * https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#use-pod-fields-as-values-for-environment-variables ### Enabling Auto-Instrumentation for Python Python auto-instrumentation is currently disabled by default and no default Python auto-instrumentation agent is included in the system packages. Python instrumentation needs to be enabled explicitly by providing a Python auto-instrumentation agent and a custom injector.conf file with python_auto_instrumentation_agent_path_prefix set or by setting PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX. The configured path is expected to contain two directories, `glibc` and `musl`. Depending on the libc flavor that the injector detects at startup, either the `glibc` or the `musl` directory will be prepended to `PYTHONPATH`, and thus loaded first by the Python interpreter. Be aware that it is not always safe to inject the additional Python packages due to possible dependency conflicts. ### Configure the Injector's Logging By default, the injector only logs errors. Set the environment variable `OTEL_INJECTOR_LOG_LEVEL` to change the log level. Valid values are: - `debug` - `info` - `warn` - `error` - this is the default value - `none` - suppress all log output from the injector; this is useful for scenarios where you pipe `stderr` into another process and parse it. The injector's log message will be written to `stderr` of the process that is being instrumented. ### Details on configuring the program inclusion and exclusion criteria If you want to selectively enable (or disable) auto-instrumentation for a subset of programs (services) on your system, the configuration file provides a couple of settings which can be used alone or in combination to produce the desired outcome. By default, all processes are instrumented. Each non-empty include setting adds a condition that a process must satisfy to be instrumented — all such conditions must be met (AND across settings). Within a single setting, entries are ORed: any one matching pattern is sufficient to satisfy that setting. Exclusions take precedence over inclusions. - `include_paths` - A comma-separated list of glob patterns to match executable paths. - `exclude_paths` - A comma-separated list of glob patterns to exclude executable paths. - `include_with_arguments` - A comma-separated list of glob patterns to match process arguments. - `exclude_with_arguments` - A comma-separated list of glob patterns to exclude process arguments. For example, in the following configuration, all program executables in the `/app/system/` directory will not be instrumented, even though the `/app` directory is included for instrumentation: ``` dotnet_auto_instrumentation_agent_path_prefix=/usr/lib/opentelemetry/dotnet jvm_auto_instrumentation_agent_path=/usr/lib/opentelemetry/jvm/javaagent.jar nodejs_auto_instrumentation_agent_path=/usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js include_paths=/app/*,/utilities/* exclude_paths=/app/system/* ``` To give you an idea of what types of inclusion and exclusion criteria can be defined, let's look at the following example: ``` dotnet_auto_instrumentation_agent_path_prefix=/usr/lib/opentelemetry/dotnet jvm_auto_instrumentation_agent_path=/usr/lib/opentelemetry/jvm/javaagent.jar nodejs_auto_instrumentation_agent_path=/usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js include_paths=/app/*,/utilities/*,*.exe exclude_with_arguments=-javaagent:*,*@opentelemetry-js*,-Xmx?m include_with_arguments=*MyProject*.jar,*app.js ``` In the example above, both `include_paths` and `include_with_arguments` are configured, so a process must satisfy **both** to be instrumented. We'll instrument programs that: - run from the `/app` or `/utilities` directories, or have an `.exe` extension (`include_paths`) - **and** have a command line argument matching `*MyProject*.jar` or ending in `app.js` (`include_with_arguments`) - however, even if both include conditions are met, the injector **will avoid** programs that have: - a command line argument starting with `-javaagent:` - a command line argument containing `@opentelemetry-js` - a maximum memory argument matching `-Xmx?m` (single digit megabytes) The example above illustrates how we avoid telemetry from unwanted applications or injecting auto-instrumentation to programs that are already instrumented. If you have a standard way of deploying all of your applications, you can create a default `injector.conf` file that will ensure you get only the telemetry you want. Multiple lines of the same setting are combined as a union (OR). This means that if you define multiple lines of `include_paths`, for example, the resulting patterns list will be a union of all of the lines. This allows for easier manipulation of the configuration file with automated tools. Essentially, you can list each pattern on a separate line. Note that different settings (e.g. `include_paths` and `include_with_arguments`) are ANDed together — a process must satisfy each configured include setting. For example, the following two configuration files have an identical outcome: ``` include_paths=/app/*,/utilities/*,*.exe ``` is the same as: ``` include_paths=/app/* include_paths=/utilities/* include_paths=*.exe ``` ### Disabling auto-instrumentation for all runtimes or specific runtimes To disable auto-instrumentation for all runtimes, set `auto_instrumentation_disabled=*` in the configuration file, or set `OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=*` as an environment variable. To selectively disable auto-instrumentation for one or more specific runtimes, set `auto_instrumentation_disabled` or `OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED` to a comma-separated list of runtime names. Valid runtime names are: - `dotnet` - `jvm` - `nodejs` - `python` - `ruby` The injector will log a warning for unknown runtime names in the comma-separated list. For example, the following configuration file would leave JVM and Node.js auto-instrumentation active, while disabling .NET and Python auto-instrumentation: ``` jvm_auto_instrumentation_agent_path=/usr/lib/opentelemetry/jvm/javaagent.jar nodejs_auto_instrumentation_agent_path=/usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js auto_instrumentation_disabled=dotnet,python ``` You can also disable a specific runtime by setting its agent-path environment variable (or configuration-file key) to the empty string. For example, `RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=` disables Ruby auto-instrumentation for a single process without touching the shared configuration. The same applies to `PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX`, `DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX`, `JVM_AUTO_INSTRUMENTATION_AGENT_PATH`, and `NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH`. Note that the injector might still add additional resource attributes to applications, by adding or extending `OTEL_RESOURCE_ATTRIBUTES`, even when auto-instrumentation is disabled. See [Disabling the injector completely for specific workloads](#disabling-the-injector-completely-for-specific-workloads) for an option that also disables this part. ### Disabling the injector completely for specific workloads The injector can be installed globally on the host in [`/etc/ld.so.preload`](https://man7.org/linux/man-pages/man8/ld.so.8.html#FILES) or for all workloads in a Kubernetes cluster/namespace. Therefore, we provide an option for you to disable the injector for a specific program launch or workload by setting the environment variable `OTEL_INJECTOR_DISABLED=true`. When `OTEL_INJECTOR_DISABLED` is set to `true`, no environment variables will be modified by the injector. ## Design This is a short summary of how the injector works internally: 1. Find out which libc the process binds, if any. This is usually either glibc or musl. (Some OpenTelemetry SDKs need to be injected differently, e.g. using different binaries depending on the libc flavor.) 2. If the process does not bind a libc, or it cannot be identified, the injector aborts injection and hands back control to the host process. 3. Find the location of the `dlsym` function in the loaded libc (in memory), reading ELF metadata. 4. Use the libc's `dlsym` handle to look up more symbols in memory, in particular `getenv` and `setenv`. 5. Again, if looking up any of the symbols fails, the injector aborts injection and hands back control to the host process. 6. Use the `getenv` function to read the current environment of the process (before adding or modifying any environment variables). 7. Use the `setenv` function to add or modify environment variables to add and activate OpenTelemetry SDKs/auto-instrumentation agents for supported runtimes (e.g. `NODE_OPTIONS`, `JAVA_TOOL_OPTIONS`, `CORECLR_PROFILER`). 8. Use the pointer to the `setenv` symbol to add or modify `OTEL_RESOURCE_ATTRIBUTES`. There is a much more detailed explanation of this approach, and on alternative approaches and the intricate design constraints in [DESIGN.md](DESIGN.md). ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). ### Maintainers - [Antoine Toulme](https://github.com/atoulme), [Splunk](https://www.splunk.com/) - [Bastian Krol](https://github.com/basti1302), [Dash0](https://www.dash0.com/) - [Jack Berg](https://github.com/jack-berg), [Grafana Labs](https://grafana.com/) - [Jacob Aronoff](https://github.com/jaronoff97), [Tero](https://www.usetero.com/) - [Michele Mancioppi](https://github.com/mmanciop), [Dash0](https://www.dash0.com/) - [Nikola Grcevski](https://github.com/grcevski), [Grafana Labs](https://grafana.com/) For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer). ### Project History The code project was initially donated by [Splunk](https://www.splunk.com/) and later replaced with another code donation by [Dash0](https://www.dash0.com/). open-telemetry-opentelemetry-injector-ea9dca1/RELEASING.md000066400000000000000000000037221524057645600236160ustar00rootroot00000000000000# Releasing the injector An approver or maintainer can run the GitHub action to [prepare the release from the GitHub Actions page of the repository](https://github.com/open-telemetry/opentelemetry-injector/actions/workflows/prepare-release.yml). The action asks for an `override` input with the choices `auto` (default), `major`, `minor`, or `patch`. When left as `auto`, the release-tooling program at [tooling/nextversion](tooling/nextversion) derives the bump from the `change_type` of the chloggen entries in [.chloggen](.chloggen) (`breaking` -> major, `deprecation`/`new_component`/`enhancement` -> minor, `bug_fix` -> patch; while the current release is on `0.y.z`, a `breaking` change is bumped as minor per the semver 0.x convention). Any of `major`, `minor`, `patch` overrides the auto-derived bump. The action then bumps the appropriate component of the most recent stable `vX.Y.Z` tag, so there is no need to type out the full version. The action will trigger the creation of a pull request for review by project approvers ([example](https://github.com/open-telemetry/opentelemetry-injector/pull/112)). Approvers approve the changelog and merge the PR. (The person approving and merging the changelog PR can be the same person who triggered the `prepare-release` action.) Merging the PR will trigger the workflow `.github/workflows/create-tag-for-release.yml` which will create a tag with the version number. Creating the tag will trigger the `build` GitHub action workflow, which will then create the GitHub release in the last job (`publish-stable`). (This can take a couple of minutes.) Releases from this repository publish only the raw `libotelinject_.so` shared library. The DEB and RPM system packages are released independently from [open-telemetry/opentelemetry-packaging](https://github.com/open-telemetry/opentelemetry-packaging) on its own cadence. ## Announce the release Make sure to drop the good news of the release to the CNCF slack #otel-injector channel! open-telemetry-opentelemetry-injector-ea9dca1/build.zig000066400000000000000000000132511524057645600235730ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be executed by an external // runner. pub fn build(b: *std.Build) !void { const optimize = std.builtin.OptimizeMode.ReleaseSafe; const target_cpu = b.option(SupportedCpuArch, "cpu-arch", "The system architecture to compile the injector for; valid options are 'amd64' and 'arm64' (default)") orelse .arm64; const allowed_env_var_prefixes = b.option( []const u8, "allowed-env-var-prefixes", "Comma-separated env var prefixes allowed in the file pointed by all_auto_instrumentation_agents_env_path (default: OTEL_)", ) orelse "OTEL_"; const target = b.resolveTargetQuery(.{ .cpu_arch = target_cpu.arch(), // Skip cpu model detection because the automatic detection for transpiling fails in build .cpu_model = .{ .explicit = target_cpu.model() }, .os_tag = .linux, }); // Creates a "module", which represents a collection of source files alongside // some compilation options, such as optimization mode and linked system libraries. // Every executable or library we compile will be based on one or more modules. const lib_mod = b.createModule(.{ // `root_source_file` is the Zig "entry point" of the module. If a module // only contains e.g. external object files, you can make this `null`. // In this case the main source file is merely a path, however, in more // complicated build scripts, this could be a generated file. .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, .link_libc = false, .pic = true, .strip = false, // Avoid a dependency from `__tls_get_addr` when compiling for x86 .single_threaded = true, }); const build_options = b.addOptions(); build_options.addOption( []const u8, "allowed_env_var_prefixes", allowed_env_var_prefixes, ); lib_mod.addOptions("build_options", build_options); // Create a dynamically linked library based on the module created above. // This creates a `std.Build.Step.Compile`, which is the build step responsible // for actually invoking the compiler. const lib = b.addLibrary(.{ .linkage = .dynamic, .name = "injector", .root_module = lib_mod, }); lib.setVersionScript(b.path("src/injector.exports.map")); b.getInstallStep().dependOn(&b.addInstallArtifact(lib, .{ .dest_dir = .{ .override = .{ .custom = "." } } }).step); var copy_injector_to_bin = b.step("copy_file", "Copy injector file"); copy_injector_to_bin.makeFn = copyInjectorFile; // make the copy step depend in the install step, which then makes it transitively depend on the compile step copy_injector_to_bin.dependOn(b.getInstallStep()); // Make copying the injector shared library binary to its final location the default step. This wil also implictly // trigger building the library as a dependent build step. b.default_step = copy_injector_to_bin; // TESTING const test_filters = b.option([][]const u8, "test-filter", "Match tests to execute"); const testTarget = b.standardTargetOptions(.{}); const test_mod = b.createModule(.{ .root_source_file = b.path("src/test.zig"), .target = testTarget, .optimize = optimize, // For some reason, setting link_libc = false does not seem to be effective on Darwin, and the tests run with // builtin.link_libc=true anyway. This in turn makes makes std.posix.getenv use std.c.environ under the hood // instead of std.os.environ. // Also, on Linux (that is, on CI or with the development Docker container (devel.Dockerfile)) the tests crash // with // /opt/hostedtoolcache/zig/0.14.0/x64/lib/std/c.zig:1:1: error: dependency on libc must be explicitly // specified in the build command // when accessing std.c.environ and link_libc is set to false here. This needs more investigation. For now, we // simply always set .link_libc = true for the tests, even if the production code is built with // .link_libc = false. .link_libc = true, .pic = true, .strip = false, }); test_mod.addOptions("build_options", build_options); const unit_tests = b.addTest(.{ .root_module = test_mod, .filters = test_filters orelse &.{}, }); const run_unit_tests = b.addRunArtifact(unit_tests); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&run_unit_tests.step); } fn copyInjectorFile(step: *std.Build.Step, _: std.Build.Step.MakeOptions) anyerror!void { const source_path = step.owner.pathFromRoot("./zig-out/libinjector.so"); const dest_path = step.owner.pathFromRoot("so/libotelinject.so"); const dest_dir = step.owner.pathFromRoot("so"); std.fs.cwd().makePath(dest_dir) catch |err| { if (err != error.PathAlreadyExists) return err; }; try std.fs.copyFileAbsolute(source_path, dest_path, .{}); } const SupportedCpuArch = enum { amd64, arm64, const Self = @This(); fn arch(self: Self) std.Target.Cpu.Arch { return switch (self) { .amd64 => std.Target.Cpu.Arch.x86_64, .arm64 => std.Target.Cpu.Arch.aarch64, }; } fn model(self: Self) *const std.Target.Cpu.Model { return switch (self) { .amd64 => std.Target.Cpu.Model.generic(std.Target.Cpu.Arch.x86_64), .arm64 => std.Target.Cpu.Model.generic(std.Target.Cpu.Arch.aarch64), }; } }; open-telemetry-opentelemetry-injector-ea9dca1/build.zig.zon000066400000000000000000000104001524057645600243710ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 .{ // This is the default name used by packages depending on this one. For // example, when a user runs `zig fetch --save `, this field is used // as the key in the `dependencies` table. Although the user can choose a // different name, most users will stick with this provided value. // // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. .name = .injector, // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows // unambiguous detection of one package being an updated version of // another. // // When forking a Zig project, this id should be regenerated (delete the // field and run `zig build`) if the upstream project is still maintained. // Otherwise, the fork is *hostile*, attempting to take control over the // original project's identity. Thus it is recommended to leave the comment // on the following line intact, so that it shows up in code reviews that // modify the field. .fingerprint = 0xafb77859b87ae2a1, // Changing this has security and trust implications. // Tracks the earliest Zig version that the package considers to be a // supported use case. // Keep this in sync with ./zig-version. .minimum_zig_version = "0.15.2", // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. // Once all dependencies are fetched, `zig build` no longer requires // internet connectivity. .dependencies = .{ // See `zig fetch --save ` for a command-line interface for adding dependencies. //.example = .{ // // When updating this field to a new URL, be sure to delete the corresponding // // `hash`, otherwise you are communicating that you expect to find the old hash at // // the new URL. If the contents of a URL change this will result in a hash mismatch // // which will prevent zig from using it. // .url = "https://example.com/foo.tar.gz", // // // This is computed from the file contents of the directory of files that is // // obtained after fetching `url` and applying the inclusion rules given by // // `paths`. // // // // This field is the source of truth; packages do not come from a `url`; they // // come from a `hash`. `url` is just one of many possible mirrors for how to // // obtain a package matching this `hash`. // // // // Uses the [multihash](https://multiformats.io/multihash/) format. // .hash = "...", // // // When this is provided, the package is found in a directory relative to the // // build root. In this case the package's hash is irrelevant and therefore not // // computed. This field and `url` are mutually exclusive. // .path = "foo", // // // When this is set to `true`, a package is declared to be lazily // // fetched. This makes the dependency only get fetched if it is // // actually used. // .lazy = false, //}, }, // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk // when using the zig package manager. As a rule of thumb, one should list // files required for compilation plus any license(s). // Paths are relative to the build root. Use the empty string (`""`) to refer to // the build root itself. // A directory listed here means that all files within, recursively, are included. .paths = .{ "build.zig", "build.zig.zon", "src", }, } open-telemetry-opentelemetry-injector-ea9dca1/devel.Dockerfile000066400000000000000000000027451524057645600250570ustar00rootroot00000000000000# This Dockerfile is intended to serve as a development environment for the injector, i.e. mounting the injector # directory as a volume to the container and then running the image with `-it` to have a fast feeback cycle when working # on injector changes. # It also enables running unit tests on different architectures (x86_64 vs aarm64). # Use `make docker-run` build and run the container. ARG base_image=debian:13@sha256:34cd9e9fd437c0a095ec39cb2e73422c9f30821b0d0848ed74fd0d43bae4d958 FROM ${base_image} ARG zig_architecture RUN apt-get update && \ apt-get install -y --no-install-recommends \ autoconf \ binutils \ build-essential \ ca-certificates \ default-jre \ entr \ fd-find \ gdb \ less \ locales \ nodejs \ tmux \ rsyslog \ vim \ wget \ && \ apt-get clean && \ localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 ENV LANG=en_US.utf8 RUN ln -s $(which fdfind) /usr/local/bin/fd RUN echo 'alias ll="ls -lah"' >> ~/.bashrc # install Zig tooling. RUN mkdir -p /opt/zig WORKDIR /opt/zig COPY zig-version . RUN . /opt/zig/zig-version && \ wget -q -O /tmp/zig.tar.gz https://ziglang.org/download/${ZIG_VERSION%-*}/zig-${zig_architecture}-linux-${ZIG_VERSION}.tar.xz && \ tar --strip-components=1 -xf /tmp/zig.tar.gz ENV PATH="$PATH:/opt/zig" # to see syslogs, run the following in the container # apt install rsyslog # service rsyslog start WORKDIR /injector open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/000077500000000000000000000000001524057645600272555ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/README.md000066400000000000000000000051271524057645600305410ustar00rootroot00000000000000Injector Integration Tests ========================== This directory contains integration tests for the injector binary. The tests in this folder do not use a multi-platform image; instead, an injector binary is build (in a container) per CPU architecture, and then used for testing. Note that the Zig source code in `src` also contains Zig unit tests. The integration tests for the DEB and RPM system packages live in [open-telemetry/opentelemetry-packaging](https://github.com/open-telemetry/opentelemetry-packaging). The available test cases for the injector integration tests are listed in the files `injector-integration-tests/tests/*.tests`. Usage ----- * `scripts/test-all.sh` to run all tests. * `ARCHITECTURES=arm64,amd64 scripts/test-all.sh` to run tests for a subset of CPU architectures. Can be combined with `LIBC_FLAVORS` and other flags. * `LIBC_FLAVORS=glibc,musl scripts/test-all.sh` to run tests for a subset of libc flavors. Can be combined with `ARCHITECTURES` and other flags. * `TEST_SETS=default,nodejs,jvm,sdk-does-not-exist,sdk-cannot-be-accessed` to only run a subset of test sets. The test set names are the different `injector-integration-tests/tests/*.tests` files. Can be combined with `ARCHITECTURES`, `LIBC_FLAVORS` etc. * `TEST_CASES="injects NODE_OPTIONS if it is not present" scripts/test-all.sh` to only run test cases whose names _exactly match_ one of the provided strings. The test cases are listed in the different test sets, i.e. the `injector-integration-tests/tests/*.tests` files. Can be combined with `ARCHITECTURES`, `LIBC_FLAVORS` etc., cannot be combined with `TEST_CASES_CONTAINING`. * `TEST_CASES_CONTAINING=OTEL_RESOURCE_ATTRIBUTES,OTEL_RESOURCE_ATTRIBUTES scripts/test-all.sh` to only run tests cases whose names _contain_ one of the provided strings as a substring. The test cases are listed in the different `scripts/*.tests` files. Can be combined with `ARCHITECTURES`, `LIBC_FLAVORS` etc., cannot be combined with `TEST_CASES`. * Set `VERBOSE=true` to set `OTEL_INJECTOR_LOG_LEVEL=debug` when running test cases and to always include the output from running the test case. Otherwise, the default log level (`error`) is used, and output is only printed to stdout when a test case fails. * Set `INTERACTIVE=true` to get a shell into the container under test, instead of running a test. Best used when working on a test for one specific app/test set, you would usually want to combine this with something like `ARCHITECTURES=arm64 LIBC_FLAVORS=glibc TEST_SETS=dotnet INTERACTIVE=true scripts/test-all.sh` to narrow down the scope to one container under test. open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/000077500000000000000000000000001524057645600302205ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/binary-validation/000077500000000000000000000000001524057645600336345ustar00rootroot00000000000000Dockerfile000066400000000000000000000020661524057645600355530ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/binary-validation# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Dockerfile for binary validation tests. # Uses a minimal Debian image with binutils installed for readelf. ARG base_image_run="debian:bookworm-slim" FROM ${base_image_run} ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi # Install binutils (provides readelf) for binary validation tests. # Detect package manager and install accordingly (apt for Debian, apk for Alpine). RUN apt-get update && apt-get install -y --no-install-recommends binutils RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry WORKDIR /usr/src/otel/injector/ COPY injector-integration-tests/apps/binary-validation app COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY dist/${injector_binary} /injector/libotelinject.so CMD ["scripts/run-tests-within-container.sh"] main.sh000077500000000000000000000047401524057645600350450ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/binary-validation#!/usr/bin/env sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -eu command=${1:-} if [ -z "$command" ]; then echo "Error: command parameter is required" exit 1 fi injector_binary=/injector/libotelinject.so check_no_weak_dynamic_symbols() { # Use readelf to get dynamic symbols and check for WEAK binding weak_symbols=$(readelf --dyn-syms "$injector_binary" 2>/dev/null | grep -E "WEAK" || true) if [ -n "$weak_symbols" ]; then echo "The injector binary contains weak dynamic symbols, which is not allowed." echo "Weak symbols found:" echo "$weak_symbols" exit 1 else echo "test no weak dynamic symbols successful" exit 0 fi } check_no_global_undefined_symbols() { # Use readelf to get dynamic symbols and check for GLOBAL binding with UNDEFINED (UND) section global_undefined_symbols=$(readelf --dyn-syms "$injector_binary" 2>/dev/null | grep -E "GLOBAL[[:space:]]+UND" || true) if [ -n "$global_undefined_symbols" ]; then echo "The injector binary contains global undefined dynamic symbols, which is not allowed." echo "Global undefined symbols found:" echo "$global_undefined_symbols" exit 1 else echo "test no global undefined dynamic symbols successful" exit 0 fi } check_dynsym_golden_signature() { # Expected output: only the mandatory null symbol entry # Note: readelf outputs a leading newline before the symbol table header expected_output=" Symbol table '.dynsym' contains 1 entry: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND " actual_output=$(readelf --dyn-syms "$injector_binary" 2>/dev/null) if [ "$actual_output" = "$expected_output" ]; then echo "test dynsym golden signature successful" exit 0 else echo "The injector binary dynamic symbol table does not match the expected signature." echo "Expected (only the compulsory null symbol):" echo "$expected_output" echo "" echo "Actual:" echo "$actual_output" exit 1 fi } case "$command" in check_no_weak_dynamic_symbols) check_no_weak_dynamic_symbols ;; check_no_global_undefined_symbols) check_no_global_undefined_symbols ;; check_dynsym_golden_signature) check_dynsym_golden_signature ;; *) echo "Error: unknown command '$command'" echo "Valid commands: check_no_weak_dynamic_symbols, check_no_global_undefined_symbols, check_dynsym_golden_signature" exit 1 ;; esac open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/copy-reloc/000077500000000000000000000000001524057645600322745ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/copy-reloc/Dockerfile000066400000000000000000000030531524057645600342670ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # node:16-bullseye-slim ships glibc 2.31 and a node binary that contains a COPY # relocation on environ (verified via `readelf -r /usr/local/bin/node`). This # exercises the glibc < 2.34 fallback path where the injector resolves getenv # directly from the ELF symbol table rather than via dlsym. ARG base_image_run="node:16-bullseye-slim@sha256:503446c15c6236291222f8192513c2eb56a02a8949cbadf4fe78cce19815c734" FROM ${base_image_run} ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi ARG create_sdk_dummy_files_script RUN if [ -z "$create_sdk_dummy_files_script" ]; then \ echo "Error: build argument create_sdk_dummy_files_script is required but not set."; \ exit 1; \ fi RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry WORKDIR /usr/src/otel/injector/ COPY injector-integration-tests/apps/copy-reloc/no-op-agent no-op-agent COPY injector-integration-tests/apps/copy-reloc app COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY injector-integration-tests/common/injector.custom-location.conf /custom/config/path/injector.conf RUN scripts/create-config-file-directory.sh RUN ${create_sdk_dummy_files_script} COPY dist/${injector_binary} /injector/libotelinject.so USER node CMD ["scripts/run-tests-within-container.sh"] open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/copy-reloc/index.js000066400000000000000000000010651524057645600337430ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const process = require('node:process'); function echoEnvVar(envVarName) { const envVarValue = process.env[envVarName]; if (!envVarValue) { process.stdout.write(`${envVarName}: -`); } else { process.stdout.write(`${envVarName}: ${envVarValue}`); } } function main() { const envVarName = process.argv[2]; if (!envVarName) { console.error('error: an environment variable name argument is required'); process.exit(1); } echoEnvVar(envVarName); } main(); no-op-agent/000077500000000000000000000000001524057645600343415ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/copy-relocindex.js000066400000000000000000000002171524057645600360060ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/copy-reloc/no-op-agent// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 global.otel_injector_nodejs_no_op_agent_has_been_loaded = true; open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/dotnet/000077500000000000000000000000001524057645600315155ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/dotnet/.gitignore000066400000000000000000000000111524057645600334750ustar00rootroot00000000000000obj/ bin/open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/dotnet/Dockerfile000066400000000000000000000033111524057645600335050ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 ARG base_image_build=mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim ARG base_image_run=mcr.microsoft.com/dotnet/runtime:9.0-bookworm-slim FROM ${base_image_build} AS build ARG TARGETARCH WORKDIR /no-op-startup-hook-source COPY injector-integration-tests/apps/dotnet/no-op-startup-hook . RUN dotnet restore RUN dotnet publish --no-restore -o /no-op-startup-hook WORKDIR /source # copy csproj and restore as distinct layers COPY injector-integration-tests/apps/dotnet/app/*.csproj . RUN dotnet restore # copy and publish app and libraries COPY injector-integration-tests/apps/dotnet/app/* . RUN dotnet publish --no-restore -o /app # final stage/image FROM ${base_image_run} RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry WORKDIR /usr/src/otel/injector/ COPY --from=build /no-op-startup-hook no-op-startup-hook COPY --from=build /app app ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi ARG create_sdk_dummy_files_script RUN if [ -z "$create_sdk_dummy_files_script" ]; then \ echo "Error: build argument create_sdk_dummy_files_script is required but not set."; \ exit 1; \ fi COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY injector-integration-tests/common/injector.custom-location.conf /custom/config/path/injector.conf RUN scripts/create-config-file-directory.sh RUN ${create_sdk_dummy_files_script} COPY dist/${injector_binary} /injector/libotelinject.so CMD ["scripts/run-tests-within-container.sh"] open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/dotnet/app/000077500000000000000000000000001524057645600322755ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/dotnet/app/App.cs000066400000000000000000000030741524057645600333500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 namespace OpenTelemetry; using System; class App { static int Main(string[] args) { if (args.Length == 0) { Console.WriteLine("error: not enough arguments, the command for the app under test needs to be specifed"); return 1; } string command = args[0]; if (command == null || command == "") { Console.WriteLine("error: not enough arguments, the command for the app under test needs to be specifed"); return 1; } switch (command) { case "coreclr-profiler-path": EchoEnvVar("CORECLR_PROFILER_PATH"); break; case "verify-startup-hook-has-been-injected": EchoEnvVar("otel_injector_dotnet_no_op_startup_hook_has_been_loaded"); break; case "otel-resource-attributes": EchoEnvVar("OTEL_RESOURCE_ATTRIBUTES"); break; case "custom-env-var": EchoEnvVar(args[1]); break; default: Console.WriteLine("error: unknown test app command: " + command); return 1; } return 0; } private static void EchoEnvVar(String envVarName) { string value = Environment.GetEnvironmentVariable(envVarName); if (value != null) { Console.WriteLine("{0}: {1}", envVarName, value); } else { Console.WriteLine("{0}: -", envVarName); } } } dotnetapp.csproj000066400000000000000000000003541524057645600354400ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/dotnet/app Exe net9.0 enable enable no-op-startup-hook/000077500000000000000000000000001524057645600351245ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/dotnetNoOpStartupHook.cs000066400000000000000000000005621524057645600405350ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/dotnet/no-op-startup-hookusing System; // A simple .NET startup hook that only sets an environment variable, so that an application under test can verify // whether this hook has been loaded or not. internal class StartupHook { public static void Initialize() { System.Environment.SetEnvironmentVariable("otel_injector_dotnet_no_op_startup_hook_has_been_loaded", "true"); } }dotnetapp.csproj000066400000000000000000000006241524057645600403460ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/dotnet/no-op-startup-hook OpenTelemetry.AutoInstrumentation.StartupHook OpenTelemetry.AutoInstrumentation.StartupHook AnyCPU Library net9.0 open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/empty-gnu-hash-bucket/000077500000000000000000000000001524057645600343415ustar00rootroot00000000000000Dockerfile000066400000000000000000000054441524057645600362630ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/empty-gnu-hash-bucket# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Regression test for issue #399: # "panic: integer overflow in ElfDynLib.lookupAddress on empty GNU hash bucket" # # debian:bullseye-slim ships glibc 2.31. On glibc < 2.34, /proc/self/maps shows # "libc-2.31.so" not "libc.so.6", so the injector's first pass finds nothing and # the second pass scans ALL mapped .so files. # # A crafted library (trigger.so) has a .gnu.hash section where the bloom filter # passes (false positive) for "setenv" but the corresponding bucket value is 0 # (empty bucket). Without the fix, this causes: # chain_index = 0 - symoffset(1) → u32 underflow → panic: integer overflow → SIGABRT # # premapper.so maps trigger.so at a fixed low address (0x1000000) before the # injector's constructor runs, so the second-pass scan hits it before libc. # Because premapper.so is listed second in LD_PRELOAD, its constructor runs first # (the dynamic linker initialises later entries first when there are no inter- # library dependencies), ensuring trigger.so is already mapped when the injector's # initEnviron is called. ARG base_image_run="debian:bullseye-slim@sha256:0083feb8da4f624e3a0245e7752af2517d4b81d8b8db50c725644672a132a31b" FROM ${base_image_run} ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi RUN apt-get update \ && apt-get install -y --no-install-recommends gcc libc6-dev python3 \ && rm -rf /var/lib/apt/lists/* RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry # Generate the crafted trigger.so with the malformed GNU hash section. COPY injector-integration-tests/apps/empty-gnu-hash-bucket/gen_trigger_so.py /tmp/gen_trigger_so.py RUN ARCH=$(uname -m); \ if [ "$ARCH" = "aarch64" ]; then MACHINE=183; \ elif [ "$ARCH" = "x86_64" ]; then MACHINE=62; \ else echo "Unsupported arch: $ARCH" && exit 1; fi; \ python3 /tmp/gen_trigger_so.py --machine "$MACHINE" /trigger.so # Build the premapper shared library. # It maps /trigger.so at 0x1000000 in its constructor, before the injector runs. COPY injector-integration-tests/apps/empty-gnu-hash-bucket/premapper.c /tmp/premapper.c RUN gcc -shared -fPIC -o /premapper.so /tmp/premapper.c WORKDIR /usr/src/otel/injector # Build the test binary (plain C, no -ldl, so the injector's second pass is triggered). COPY injector-integration-tests/apps/empty-gnu-hash-bucket/printenv.c . RUN mkdir -p app && gcc -o app/printenv printenv.c && rm printenv.c COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY dist/${injector_binary} /injector/libotelinject.so CMD ["scripts/run-tests-within-container.sh"] gen_trigger_so.py000066400000000000000000000103411524057645600376300ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/empty-gnu-hash-bucket#!/usr/bin/env python3 """ Generate a minimal ELF shared library that triggers the integer overflow bug in ElfDynLib.lookupAddress (issue #399). The .gnu.hash section is crafted so that: - The bloom filter passes (false positive) for "setenv" (hash=0x1b85483a) - The corresponding bucket value is 0 (empty) - chain_index = bucket_value(0) - symoffset(1) = 0 - 1 as u32 → wraps to 0xffffffff → panic in ReleaseSafe Usage: python3 gen_trigger_so.py [--machine <183|62>] 183 = EM_AARCH64 (default), 62 = EM_X86_64 """ import struct import sys import argparse parser = argparse.ArgumentParser() parser.add_argument("--machine", type=int, default=183, help="ELF e_machine: 183=EM_AARCH64, 62=EM_X86_64") parser.add_argument("output", help="Output .so path") args = parser.parse_args() MACHINE = args.machine OUTPATH = args.output # --- GNU hash parameters --- NBUCKETS = 1 SYMOFFSET = 1 # underflow: bucket_val(0) - symoffset(1) wraps as u32 BLOOM_SIZE = 1 BLOOM_SHIFT = 6 SETENV_HASH = 0x1b85483a # gnu_hash("setenv") bit0 = SETENV_HASH & 63 bit1 = (SETENV_HASH >> BLOOM_SHIFT) & 63 bloom_val = (1 << bit0) | (1 << bit1) # --- Section data --- dynstr = b"\x00" # minimal string table # STN_UNDEF symbol entry (24 bytes): st_name, st_info, st_other, st_shndx, st_value, st_size dynsym = struct.pack(" #include #include #include static void __attribute__((constructor(101))) map_trigger(void) { int fd = open("/trigger.so", O_RDONLY); if (fd < 0) return; struct stat st; if (fstat(fd, &st) < 0) { close(fd); return; } /* 0x1000000 = 16 MB — well below the ASLR mmap range on 64-bit kernels */ mmap((void *)0x1000000, (size_t)st.st_size, PROT_READ | PROT_EXEC, MAP_FIXED | MAP_PRIVATE, fd, 0); close(fd); } printenv.c000066400000000000000000000015641524057645600363010ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/empty-gnu-hash-bucket// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // A minimal C program that prints the value of a named environment variable. // Deliberately uses only libc (stdio, stdlib) and is compiled without -ldl, so // libdl.so does not appear in /proc/self/maps at runtime. This reproduces the // scenario seen on RHEL 8 and any other pre-glibc-2.34 system where dlsym lives // in libdl.so (not in libc.so), and the binary under test does not link libdl. #include #include int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "error: an environment variable name argument is required\n"); return 1; } const char *name = argv[1]; const char *value = getenv(name); if (value == NULL) { printf("%s: -", name); } else { printf("%s: %s", name, value); } return 0; } open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/000077500000000000000000000000001524057645600310145ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/Dockerfile000066400000000000000000000027451524057645600330160ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 ARG base_image_build="maven:3.9-eclipse-temurin-21" ARG base_image_run="eclipse-temurin:21-jre" FROM --platform=$BUILDPLATFORM ${base_image_build} AS build WORKDIR /usr/src/otel/injector/ COPY injector-integration-tests/apps/jvm /usr/src/otel/injector/ RUN mvn package -B -q # final stage/image FROM ${base_image_run} RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry WORKDIR /usr/src/otel/injector/ COPY --from=build /usr/src/otel/injector/no-op-agent/target/no-op-agent.jar no-op-agent/no-op-agent.jar COPY --from=build /usr/src/otel/injector/app/target/Main.jar app/Main.jar ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi ARG create_sdk_dummy_files_script RUN if [ -z "$create_sdk_dummy_files_script" ]; then \ echo "Error: build argument create_sdk_dummy_files_script is required but not set."; \ exit 1; \ fi COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY injector-integration-tests/common/injector.custom-location.conf /custom/config/path/injector.conf RUN scripts/create-config-file-directory.sh RUN ${create_sdk_dummy_files_script} COPY dist/${injector_binary} /injector/libotelinject.so CMD ["scripts/run-tests-within-container.sh"] open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/app/000077500000000000000000000000001524057645600315745ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/app/pom.xml000066400000000000000000000017741524057645600331220ustar00rootroot00000000000000 4.0.0 io.opentelemetry.injector.test jvm-integration-tests 1.0-SNAPSHOT app jar Main org.apache.maven.plugins maven-jar-plugin 3.5.1 test.Main open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/app/src/000077500000000000000000000000001524057645600323635ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/app/src/main/000077500000000000000000000000001524057645600333075ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/app/src/main/java/000077500000000000000000000000001524057645600342305ustar00rootroot00000000000000test/000077500000000000000000000000001524057645600351305ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/app/src/main/javaMain.java000066400000000000000000000041521524057645600366610ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/app/src/main/java/test// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package test; import java.util.Map; public class Main { public static void main(String[] args) { if (args.length == 0) { System.err.println("error: not enough arguments, the command for the app under test needs to be specifed"); System.exit(1); } final String command = args[0]; if (command == null || command.equals("")) { System.err.println("error: not enough arguments, the command for the app under test needs to be specifed"); System.exit(1); } switch (command) { case "verify-javaagent-has-been-injected": echoProperty("otel.injector.jvm.no_op_agent.has_been_loaded"); break; case "verify-javaagent-has-been-injected-and-existing-property-is-still-in-place": echoProperties(new String[]{"otel.injector.jvm.no_op_agent.has_been_loaded", "some-property"}); break; case "custom-env-var": echoEnvVar(args[1]); break; default: System.out.println("error: unknown test app command: " + command); System.exit(1); } } public static void echoProperty(String propertyName) { String value = System.getProperty(propertyName); if (value != null) { System.out.println(propertyName + ": " + value); } else { System.out.println(propertyName + ": -"); } } public static void echoEnvVar(String envName) { String value = System.getenv(envName); if (value != null) { System.out.println(envName + ": " + value); } else { System.out.println(envName + ": -"); } } public static void echoProperties(String[] propertyNames) { for (int i = 0; i < propertyNames.length - 1; i++) { System.out.print(propertyNames[i] + ": " + System.getProperty(propertyNames[i]) + "; "); } echoProperty(propertyNames[propertyNames.length - 1]); } } open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/no-op-agent/000077500000000000000000000000001524057645600331405ustar00rootroot00000000000000pom.xml000066400000000000000000000020741524057645600344010ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/no-op-agent 4.0.0 io.opentelemetry.injector.test jvm-integration-tests 1.0-SNAPSHOT no-op-agent jar no-op-agent org.apache.maven.plugins maven-jar-plugin 3.5.1 io.opentelemetry.javaagent.NoOpAgent open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/no-op-agent/src/000077500000000000000000000000001524057645600337275ustar00rootroot00000000000000main/000077500000000000000000000000001524057645600345745ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/no-op-agent/srcjava/000077500000000000000000000000001524057645600355155ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/no-op-agent/src/mainio/000077500000000000000000000000001524057645600361245ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/no-op-agent/src/main/javaopentelemetry/000077500000000000000000000000001524057645600410205ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/no-op-agent/src/main/java/iojavaagent/000077500000000000000000000000001524057645600427605ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/no-op-agent/src/main/java/io/opentelemetryNoOpAgent.java000066400000000000000000000011011524057645600454460ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/no-op-agent/src/main/java/io/opentelemetry/javaagentpackage io.opentelemetry.javaagent; import java.lang.instrument.*; // A simple Java agent that only sets a system property, so that an application under test can verify whether this agent // has been loaded or not. public class NoOpAgent { public static void premain(String args, Instrumentation inst) { // Intentionally left (almost) empty. We only set a system property that the application under test can use to // verify that the no-op agent has been loaded. System.setProperty("otel.injector.jvm.no_op_agent.has_been_loaded", "true"); } }open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/jvm/pom.xml000066400000000000000000000014651524057645600323370ustar00rootroot00000000000000 4.0.0 io.opentelemetry.injector.test jvm-integration-tests 1.0-SNAPSHOT pom no-op-agent app 11 11 UTF-8 open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-getenv-symbol/000077500000000000000000000000001524057645600334255ustar00rootroot00000000000000.gitignore000066400000000000000000000000131524057645600353300ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-getenv-symbolnogetenv.* Dockerfile000066400000000000000000000022671524057645600353470ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-getenv-symbol# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 ARG base_image_run="golang:1.26.3-trixie@sha256:fb4b74a39c7318d53539ebda43ccd3ecba6e447a78591889c0efc0a7235ea8b3" FROM ${base_image_run} ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry WORKDIR /usr/src/otel/injector COPY injector-integration-tests/apps/no-getenv-symbol/go.mod . COPY injector-integration-tests/apps/no-getenv-symbol/go.sum . RUN go mod download COPY injector-integration-tests/apps/no-getenv-symbol/main.go . ARG TARGETOS ARG TARGETARCH RUN GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -v -buildmode=pie -ldflags '-s -w' -o app/nogetenv . COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY injector-integration-tests/common/injector.exclude-nogetenv.conf injector.exclude-nogetenv.conf COPY dist/${injector_binary} /injector/libotelinject.so CMD ["scripts/run-tests-within-container.sh"] README.md000066400000000000000000000016451524057645600346330ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-getenv-symbolThis Go application serves as a regression test for an issue where an earlier incarnation of the injector (before https://github.com/open-telemetry/opentelemetry-injector/pull/166) would crash executables that do not have the `__environ` symbol, but are still subject to dynamic linking at startup. This was an issue because the injector previously declared a dependency on `__environ`, with the assumption that _any_ dynamically linked executable would also bind to a libc that exports that symbol. This would happen for example for Go applications built with `-buildmode=pie`. Applications with the usual `-ldflags '-extldflags "-static"'` would not be affected. Nowadays, this is no longer an issue, since the current injector (after PR #166) does not depend on any external symbols being present at link time. (Instead, it looks up the `dlsym` symbol dynamically at startup, and simply stands down if that symbol is not found.) go.mod000066400000000000000000000001531524057645600344530ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-getenv-symbolmodule github.com/dash0hq/dash0-operator/images/instrumentation/injector/test/no_environ_symbol go 1.26.3 go.sum000066400000000000000000000000001524057645600344670ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-getenv-symbolmain.go000066400000000000000000000006611524057645600346240ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-getenv-symbol// SPDX-FileCopyrightText: Copyright 2025 Dash0 Inc. // SPDX-License-Identifier: Apache-2.0 package main import ( "fmt" "os" ) func main() { envVarName := "NO_ENVIRON_TEST_VAR" value, isSet := os.LookupEnv(envVarName) if !isSet { fmt.Printf("The environment variable \"%s\" is not set.\n", envVarName) os.Exit(0) } fmt.Printf("The environment variable \"%s\" had the value: \"%s\".\n", envVarName, value) os.Exit(0) } open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-libdl/000077500000000000000000000000001524057645600317205ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-libdl/Dockerfile000066400000000000000000000025541524057645600337200ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # debian:bullseye-slim ships glibc 2.31. On glibc < 2.34, dlsym is exported by # libdl.so, not by libc.so itself (the two were merged in glibc 2.34). The test # app is a plain C binary compiled without -ldl, so libdl.so never appears in # /proc/self/maps. This combination reproduces the libc-detection failure seen # on RHEL 8 and similar pre-glibc-2.34 systems when a binary does not link libdl. ARG base_image_run="debian:bullseye-slim@sha256:0083feb8da4f624e3a0245e7752af2517d4b81d8b8db50c725644672a132a31b" FROM ${base_image_run} ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi RUN apt-get update \ && apt-get install -y --no-install-recommends gcc libc6-dev \ && rm -rf /var/lib/apt/lists/* RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry WORKDIR /usr/src/otel/injector COPY injector-integration-tests/apps/no-libdl/app.c . RUN mkdir -p app && gcc -o app/printenv app.c && rm app.c COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY dist/${injector_binary} /injector/libotelinject.so CMD ["scripts/run-tests-within-container.sh"] open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/no-libdl/app.c000066400000000000000000000015641524057645600326520ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // A minimal C program that prints the value of a named environment variable. // Deliberately uses only libc (stdio, stdlib) and is compiled without -ldl, so // libdl.so does not appear in /proc/self/maps at runtime. This reproduces the // scenario seen on RHEL 8 and any other pre-glibc-2.34 system where dlsym lives // in libdl.so (not in libc.so), and the binary under test does not link libdl. #include #include int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "error: an environment variable name argument is required\n"); return 1; } const char *name = argv[1]; const char *value = getenv(name); if (value == NULL) { printf("%s: -", name); } else { printf("%s: %s", name, value); } return 0; } open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/nodejs/000077500000000000000000000000001524057645600315025ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/nodejs/Dockerfile000066400000000000000000000024001524057645600334700ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 ARG base_image_run="node:24.13.0-bookworm-slim@sha256:bf22df20270b654c4e9da59d8d4a3516cce6ba2852e159b27288d645b7a7eedc" FROM ${base_image_run} ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi ARG create_sdk_dummy_files_script RUN if [ -z "$create_sdk_dummy_files_script" ]; then \ echo "Error: build argument create_sdk_dummy_files_script is required but not set."; \ exit 1; \ fi RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry WORKDIR /usr/src/otel/injector/ COPY injector-integration-tests/apps/nodejs/no-op-agent no-op-agent COPY injector-integration-tests/apps/nodejs app COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY injector-integration-tests/common/injector.custom-location.conf /custom/config/path/injector.conf RUN scripts/create-config-file-directory.sh RUN ${create_sdk_dummy_files_script} COPY dist/${injector_binary} /injector/libotelinject.so USER node CMD ["scripts/run-tests-within-container.sh"] open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/nodejs/index.js000066400000000000000000000035431524057645600331540ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const process = require('node:process'); function echoEnvVar(envVarName) { const envVarValue = process.env[envVarName]; if (!envVarValue) { process.stdout.write(`${envVarName}: -`); } else { process.stdout.write(`${envVarName}: ${envVarValue}`); } } function echoGlobalProperty(name) { const value = global[name]; if (!value) { process.stdout.write(`${name}: -`); } else { process.stdout.write(`${name}: ${value}`); } } function main() { const command = process.argv[2]; if (!command) { console.error('error: not enough arguments, the command for the app under test needs to be specifed'); process.exit(1); } switch (command) { case 'non-existing': echoEnvVar('DOES_NOT_EXIST'); break; case 'existing': echoEnvVar('TEST_VAR'); break; case 'node-options': echoEnvVar('NODE_OPTIONS'); break; case 'node-options-twice': echoEnvVar('NODE_OPTIONS'); process.stdout.write('; '); echoEnvVar('NODE_OPTIONS'); break; case 'verify-auto-instrumentation-agent-has-been-injected': echoGlobalProperty('otel_injector_nodejs_no_op_agent_has_been_loaded'); break; case 'otel-resource-attributes': echoEnvVar('OTEL_RESOURCE_ATTRIBUTES'); break; case 'java-tool-options': echoEnvVar('JAVA_TOOL_OPTIONS'); break; case 'dotnet-startup-hooks': echoEnvVar('DOTNET_STARTUP_HOOKS'); break; case 'rubyopt': echoEnvVar('RUBYOPT'); break; case 'otel-ruby-additional-gem-path': echoEnvVar('OTEL_RUBY_ADDITIONAL_GEM_PATH'); break; case 'custom-env-var': echoEnvVar(process.argv[3]); break; default: console.error(`unknown test app command: ${command}`); process.exit(1); } } main(); open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/nodejs/no-op-agent/000077500000000000000000000000001524057645600336265ustar00rootroot00000000000000index.js000066400000000000000000000002171524057645600352140ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/nodejs/no-op-agent// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 global.otel_injector_nodejs_no_op_agent_has_been_loaded = true; open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/python/000077500000000000000000000000001524057645600315415ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/python/Dockerfile000066400000000000000000000022001524057645600335250ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 ARG base_image_run="python:3.14-slim-bookworm" FROM ${base_image_run} ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi ARG create_sdk_dummy_files_script RUN if [ -z "$create_sdk_dummy_files_script" ]; then \ echo "Error: build argument create_sdk_dummy_files_script is required but not set."; \ exit 1; \ fi RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry WORKDIR /usr/src/otel/injector/ COPY injector-integration-tests/apps/python/no-op-agent no-op-agent COPY injector-integration-tests/apps/python app COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY injector-integration-tests/common/injector.custom-location.conf /custom/config/path/injector.conf RUN ${create_sdk_dummy_files_script} COPY dist/${injector_binary} /injector/libotelinject.so CMD ["scripts/run-tests-within-container.sh"]open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/python/app.py000066400000000000000000000026761524057645600327060ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 import builtins import os import sys def echo_env_var(env_var_name): env_var_value = os.environ.get(env_var_name) if not env_var_value: sys.stdout.write(f"{env_var_name}: -") else: sys.stdout.write(f"{env_var_name}: {env_var_value}") def echo_builtin_property(): value = builtins.otel_injector_python_no_op_agent_has_been_loaded if not value: sys.stdout.write(f"otel_injector_python_no_op_agent_has_been_loaded: -") else: sys.stdout.write(f"otel_injector_python_no_op_agent_has_been_loaded: {value}") def main(): if len(sys.argv) < 2: sys.stderr.write('error: not enough arguments, the command for the app under test needs to be specifed\n') sys.exit(1) command = sys.argv[1] if command == 'pythonpath': echo_env_var('PYTHONPATH') elif command == 'verify-auto-instrumentation-agent-has-been-injected': echo_builtin_property() elif command == 'otel-resource-attributes': echo_env_var('OTEL_RESOURCE_ATTRIBUTES') elif command == 'custom-env-var': if len(sys.argv) < 3: sys.stderr.write('error: custom-env-var command requires an additional argument\n') sys.exit(1) echo_env_var(sys.argv[2]) else: sys.stderr.write(f'unknown test app command: {command}\n') sys.exit(1) if __name__ == '__main__': main() open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/python/no-op-agent/000077500000000000000000000000001524057645600336655ustar00rootroot00000000000000usercustomize.py000066400000000000000000000004401524057645600370770ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/python/no-op-agentimport builtins # A simple Python usercustomize script which only sets a system property on the global builtins object, so that an # application under test can verify whether this agent has been loaded or not. setattr(builtins, 'otel_injector_python_no_op_agent_has_been_loaded', 'true')open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/ruby/000077500000000000000000000000001524057645600312015ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/ruby/Dockerfile000066400000000000000000000021611524057645600331730ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 ARG base_image_run="ruby:3.3-slim" FROM ${base_image_run} ARG injector_binary RUN if [ -z "$injector_binary" ]; then \ echo "Error: build argument injector_binary is required but not set."; \ exit 1; \ fi ARG create_sdk_dummy_files_script RUN if [ -z "$create_sdk_dummy_files_script" ]; then \ echo "Error: build argument create_sdk_dummy_files_script is required but not set."; \ exit 1; \ fi RUN mkdir -p /etc/opentelemetry/injector/conf.d && chmod -R 777 /etc/opentelemetry WORKDIR /usr/src/otel/injector/ COPY injector-integration-tests/apps/ruby/no-op-agent no-op-agent COPY injector-integration-tests/apps/ruby app COPY injector-integration-tests/scripts/*.sh scripts/ COPY injector-integration-tests/tests/*.tests tests/ COPY injector-integration-tests/common/ common/ COPY injector-integration-tests/common/injector.custom-location.conf /custom/config/path/injector.conf RUN ${create_sdk_dummy_files_script} COPY dist/${injector_binary} /injector/libotelinject.so CMD ["scripts/run-tests-within-container.sh"] open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/ruby/app.rb000066400000000000000000000021331524057645600323050ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 def echo_env_var(name) value = ENV[name] if value.nil? || value.empty? print "#{name}: -" else print "#{name}: #{value}" end end def echo_no_op_agent_flag value = $otel_injector_ruby_no_op_agent_has_been_loaded if value print "otel_injector_ruby_no_op_agent_has_been_loaded: #{value}" else print "otel_injector_ruby_no_op_agent_has_been_loaded: -" end end command = ARGV[0] if command.nil? warn "error: not enough arguments, the command for the app under test needs to be specified" exit 1 end case command when "rubyopt" echo_env_var("RUBYOPT") when "additional-gem-path" echo_env_var("OTEL_RUBY_ADDITIONAL_GEM_PATH") when "verify-auto-instrumentation-agent-has-been-injected" echo_no_op_agent_flag when "otel-resource-attributes" echo_env_var("OTEL_RESOURCE_ATTRIBUTES") when "custom-env-var" if ARGV[1].nil? warn "error: custom-env-var command requires an additional argument" exit 1 end echo_env_var(ARGV[1]) else warn "unknown test app command: #{command}" exit 1 end open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/ruby/no-op-agent/000077500000000000000000000000001524057645600333255ustar00rootroot00000000000000opentelemetry-auto-instrumentation.rb000066400000000000000000000005101524057645600426720ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/apps/ruby/no-op-agent# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # No-op stand-in for the opentelemetry-auto-instrumentation gem entry point. It does nothing except # record that it was loaded, so tests can verify the injector made Ruby require it via RUBYOPT. $otel_injector_ruby_no_op_agent_has_been_loaded = true open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common/000077500000000000000000000000001524057645600305455ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common/conf.d/000077500000000000000000000000001524057645600317145ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common/conf.d/dotnet.conf000066400000000000000000000001151524057645600340550ustar00rootroot00000000000000dotnet_auto_instrumentation_agent_path_prefix = /usr/lib/opentelemetry/dotnetopen-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common/conf.d/jvm.conf000066400000000000000000000001161524057645600333550ustar00rootroot00000000000000jvm_auto_instrumentation_agent_path = /usr/lib/opentelemetry/jvm/javaagent.jaropen-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common/conf.d/nodejs.conf000066400000000000000000000002231524057645600340420ustar00rootroot00000000000000nodejs_auto_instrumentation_agent_path = /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.jsopen-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common/conf.d/python.conf000066400000000000000000000001151524057645600341010ustar00rootroot00000000000000python_auto_instrumentation_agent_path_prefix = /usr/lib/opentelemetry/pythonopen-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common/conf.d/ruby.conf000066400000000000000000000001111524057645600335350ustar00rootroot00000000000000ruby_auto_instrumentation_agent_path_prefix = /usr/lib/opentelemetry/rubyopen-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common/default_env.conf000066400000000000000000000001631524057645600337100ustar00rootroot00000000000000OTEL_SDK_DISABLED=true OTEL_LOG_LEVEL=info CUSTOM_ENV_VAR=custom-value-123 ANOTHER_CUSTOM_ENV_VAR=custom-value-456 open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common/injector.conf000066400000000000000000000011561524057645600332340ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 dotnet_auto_instrumentation_agent_path_prefix = /path/from/config-file/dotnet jvm_auto_instrumentation_agent_path = /path/from/config-file/jvm/javaagent.jar nodejs_auto_instrumentation_agent_path = /path/from/config-file/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js python_auto_instrumentation_agent_path_prefix = /path/from/config-file/python ruby_auto_instrumentation_agent_path_prefix = /path/from/config-file/ruby all_auto_instrumentation_agents_env_path = /etc/opentelemetry/injector/default_env.conf injector.custom-location.conf000066400000000000000000000012761524057645600362770ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 dotnet_auto_instrumentation_agent_path_prefix = /path/from/config-file-custom-location/dotnet jvm_auto_instrumentation_agent_path = /path/from/config-file-custom-location/jvm/javaagent.jar nodejs_auto_instrumentation_agent_path = /path/from/config-file-custom-location/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js python_auto_instrumentation_agent_path_prefix = /path/from/config-file-custom-location/python ruby_auto_instrumentation_agent_path_prefix = /path/from/config-file-custom-location/ruby all_auto_instrumentation_agents_env_path = /etc/opentelemetry/injector/default_env.conf injector.exclude-nogetenv.conf000066400000000000000000000001451524057645600364250ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/common# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 exclude_paths=*/nogetenv open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts/000077500000000000000000000000001524057645600307445ustar00rootroot00000000000000create-config-file-directory.sh000077500000000000000000000016661524057645600366620ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts#!/usr/bin/env sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # (Re-)build the default configuration directory layout that the OpenTelemetry injector integration test framework expect inside # the test container. Invoked from each test app Dockerfile at image build time, and also at the start of each test case. # # The conf.d drop-in files are staged under common/conf.d/ (the Dockerfile is responsible for the COPY); this script # populates the runtime location /etc/opentelemetry/injector/conf.d/ from that stage and gives the test user write # access. The same staging directory is used by reset-config-file-directory.sh to restore conf.d between test cases. set -e mkdir -p /etc/opentelemetry/injector/conf.d cp common/injector.conf /etc/opentelemetry/injector/injector.conf if [ -d common/conf.d ]; then find common/conf.d -maxdepth 1 -name '*.conf' -exec cp {} /etc/opentelemetry/injector/conf.d/ \; fi create-inaccessible-sdk-dummy-files.sh000077500000000000000000000037761524057645600401360ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts#!/usr/bin/env sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 echo "deliberately creating inaccessible OTel auto-instrumentation dummy files" # .NET mkdir -p /usr/lib/opentelemetry/dotnet/glibc/linux-x64 mkdir -p /usr/lib/opentelemetry/dotnet/glibc/linux-arm64 mkdir -p /usr/lib/opentelemetry/dotnet/musl/linux-musl-x64 mkdir -p /usr/lib/opentelemetry/dotnet/musl/linux-musl-arm64 touch /usr/lib/opentelemetry/dotnet/glibc/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so touch /usr/lib/opentelemetry/dotnet/glibc/linux-arm64/OpenTelemetry.AutoInstrumentation.Native.so touch /usr/lib/opentelemetry/dotnet/musl/linux-musl-x64/OpenTelemetry.AutoInstrumentation.Native.so touch /usr/lib/opentelemetry/dotnet/musl/linux-musl-arm64/OpenTelemetry.AutoInstrumentation.Native.so mkdir -p /usr/lib/opentelemetry/dotnet/glibc/AdditionalDeps mkdir -p /usr/lib/opentelemetry/dotnet/musl/AdditionalDeps mkdir -p /usr/lib/opentelemetry/dotnet/glibc/store mkdir -p /usr/lib/opentelemetry/dotnet/musl/store mkdir -p /usr/lib/opentelemetry/dotnet/glibc/net mkdir -p /usr/lib/opentelemetry/dotnet/musl/net touch /usr/lib/opentelemetry/dotnet/glibc/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll touch /usr/lib/opentelemetry/dotnet/musl/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll # JVM mkdir -p /usr/lib/opentelemetry/jvm && touch /usr/lib/opentelemetry/jvm/javaagent.jar # Node.js mkdir -p /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src touch /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js # Ruby (both libc flavors need an entry file that the injector access()-checks before injecting) mkdir -p /usr/lib/opentelemetry/ruby/glibc mkdir -p /usr/lib/opentelemetry/ruby/musl touch /usr/lib/opentelemetry/ruby/glibc/opentelemetry-auto-instrumentation.rb touch /usr/lib/opentelemetry/ruby/musl/opentelemetry-auto-instrumentation.rb # make all files inaccessible chmod -R 600 /usr/lib/opentelemetry create-no-sdk-dummy-files.sh000077500000000000000000000002371524057645600361130ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts#!/usr/bin/env sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 echo "not creating any OTel auto-instrumentation dummy files" create-sdk-dummy-files-dotnet-optional.sh000077500000000000000000000004741524057645600406220ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts#!/usr/bin/env sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 mkdir -p /usr/lib/opentelemetry/dotnet/glibc/AdditionalDeps mkdir -p /usr/lib/opentelemetry/dotnet/musl/AdditionalDeps mkdir -p /usr/lib/opentelemetry/dotnet/glibc/store mkdir -p /usr/lib/opentelemetry/dotnet/musl/store create-sdk-dummy-files.sh000077500000000000000000000056571524057645600355140ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts#!/usr/bin/env sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Add dummy no-op OTel auto-instrumentation agents which actually do nothing but make the file check in the injector # pass, so we can test whether NODE_OPTIONS, JAVA_TOOL_OPTIONS, etc. have been modfied as expected. mkdir -p /usr/lib/opentelemetry/dotnet/glibc/linux-x64 mkdir -p /usr/lib/opentelemetry/dotnet/glibc/linux-arm64 mkdir -p /usr/lib/opentelemetry/dotnet/musl/linux-musl-x64 mkdir -p /usr/lib/opentelemetry/dotnet/musl/linux-musl-arm64 touch /usr/lib/opentelemetry/dotnet/glibc/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so touch /usr/lib/opentelemetry/dotnet/glibc/linux-arm64/OpenTelemetry.AutoInstrumentation.Native.so touch /usr/lib/opentelemetry/dotnet/musl/linux-musl-x64/OpenTelemetry.AutoInstrumentation.Native.so touch /usr/lib/opentelemetry/dotnet/musl/linux-musl-arm64/OpenTelemetry.AutoInstrumentation.Native.so mkdir -p /usr/lib/opentelemetry/dotnet/glibc/net mkdir -p /usr/lib/opentelemetry/dotnet/musl/net if [ -d no-op-startup-hook ]; then cp no-op-startup-hook/* /usr/lib/opentelemetry/dotnet/glibc/net/ cp no-op-startup-hook/* /usr/lib/opentelemetry/dotnet/musl/net/ fi # JVM # Copy the no-op agent jar file that is created in injector-integration-tests/apps/jvm/Dockerfile if [ -f no-op-agent/no-op-agent.jar ]; then mkdir -p /usr/lib/opentelemetry/jvm cp no-op-agent/no-op-agent.jar /usr/lib/opentelemetry/jvm/javaagent.jar fi # Node.js # Copy the Node.js no-op agent. if [ -f no-op-agent/index.js ]; then mkdir -p /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src cp no-op-agent/index.js /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js fi # Python # Copy the Python no-op agent. if [ -f no-op-agent/usercustomize.py ]; then mkdir -p /usr/lib/opentelemetry/python/glibc mkdir -p /usr/lib/opentelemetry/python/musl cp no-op-agent/usercustomize.py /usr/lib/opentelemetry/python/glibc cp no-op-agent/usercustomize.py /usr/lib/opentelemetry/python/musl fi # Ruby # Copy the Ruby no-op agent to the entry path the injector requires via RUBYOPT. if [ -f no-op-agent/opentelemetry-auto-instrumentation.rb ]; then mkdir -p /usr/lib/opentelemetry/ruby/glibc mkdir -p /usr/lib/opentelemetry/ruby/musl cp no-op-agent/opentelemetry-auto-instrumentation.rb /usr/lib/opentelemetry/ruby/glibc cp no-op-agent/opentelemetry-auto-instrumentation.rb /usr/lib/opentelemetry/ruby/musl fi # Provide instrumentation files also in three more locations, for testing configuration via # /etc/opentelemetry/injector/injector.conf, OTEL_INJECTOR_CONFIG_FILE, and via environment variables # NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH and friends. mkdir -p /path/from cp -R /usr/lib/opentelemetry /path/from/config-file cp -R /usr/lib/opentelemetry /path/from/environment-variable cp -R /usr/lib/opentelemetry /path/from/config-file-custom-location reset-config-file-directory.sh000077500000000000000000000025101524057645600365260ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts#!/usr/bin/env sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Resets the test container's mutable file system state to a clean baseline so each test case starts isolated from modifications # made by previous test cases. Invoked from the function run_test_case in run-tests-within-container.sh, before every test case. # Run with the workdir set to /usr/src/otel/injector/ (the same home directory used by run_test_case), so common/conf.d/ resolves # to the source files. set -e # Delete the entire configuration directory to remove all potentially modified config files. rm -rf /etc/opentelemetry/injector # Clean up the optional .NET directories that create-sdk-dummy-files-dotnet-optional.sh creates for test cases # labelled "...the additional deps directory exists..." or "...the shared store directory exists...". Skip when the # parent tree is intentionally inaccessible (sdk-cannot-be-accessed.tests chmod-s it to 600). if [ -d /usr/lib/opentelemetry ] && [ -x /usr/lib/opentelemetry ]; then rm -rf /usr/lib/opentelemetry/dotnet/glibc/AdditionalDeps rm -rf /usr/lib/opentelemetry/dotnet/musl/AdditionalDeps rm -rf /usr/lib/opentelemetry/dotnet/glibc/store rm -rf /usr/lib/opentelemetry/dotnet/musl/store fi # Restore the original configuration file layout. scripts/create-config-file-directory.sh run-tests-for-container.sh000077500000000000000000000150161524057645600357370ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts#!/usr/bin/env bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -eu cd "$(dirname "${BASH_SOURCE[0]}")"/../.. if [ -z "${ARCH:-}" ]; then ARCH=arm64 fi if [ "$ARCH" = arm64 ]; then docker_platform=linux/arm64 expected_cpu_architecture=aarch64 injector_binary=libotelinject_arm64.so elif [ "$ARCH" = amd64 ]; then docker_platform=linux/amd64 expected_cpu_architecture=x86_64 injector_binary=libotelinject_amd64.so else echo "The architecture $ARCH is not supported." exit 1 fi if [ -z "${LIBC:-}" ]; then LIBC=glibc fi if [ -z "${TEST_SET:-}" ]; then TEST_SET=default.tests fi # Note: Runtime-independent test sets like default.tests, sdk-does-not-exist.tests, and sdk-cannot-be-accessed.tests # also use Node.js as the runtime for the container under test. test_app="nodejs" if [[ "$TEST_SET" = "dotnet.tests" ]]; then test_app="dotnet" fi if [[ "$TEST_SET" = "jvm.tests" ]]; then test_app="jvm" fi if [[ "$TEST_SET" = "no-getenv-symbol.tests" ]]; then test_app="no-getenv-symbol" fi if [[ "$TEST_SET" = "python.tests" ]]; then test_app="python" fi if [[ "$TEST_SET" = "ruby.tests" ]]; then test_app="ruby" fi if [[ "$TEST_SET" = "no-libdl.tests" ]]; then test_app="no-libdl" fi if [[ "$TEST_SET" = "binary-validation.tests" ]]; then test_app="binary-validation" fi if [[ "$TEST_SET" = "copy-reloc.tests" ]]; then test_app="copy-reloc" fi if [[ "$TEST_SET" = "empty-gnu-hash-bucket.tests" ]]; then test_app="empty-gnu-hash-bucket" fi # We also use the Node.js test app for non-runtime specific tests (e.g. injector-integration-tests/tests/default.tests # etc.), so this is the default Dockerfile. dockerfile_name="injector-integration-tests/apps/nodejs/Dockerfile" image_name="otel-injector-test-$ARCH-$LIBC-$test_app" base_image_run=unknown base_image_build=unknown case "$test_app" in "dotnet") dockerfile_name="injector-integration-tests/apps/dotnet/Dockerfile" base_image_build=mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim base_image_run=mcr.microsoft.com/dotnet/runtime:9.0-bookworm-slim if [[ "$LIBC" = "musl" ]]; then base_image_build=mcr.microsoft.com/dotnet/sdk:9.0-alpine base_image_run=mcr.microsoft.com/dotnet/runtime:9.0-alpine fi ;; "jvm") dockerfile_name="injector-integration-tests/apps/jvm/Dockerfile" base_image_build=maven:3.9-eclipse-temurin-21 base_image_run=eclipse-temurin:21-jre if [[ "$LIBC" = "musl" ]]; then base_image_build=maven:3.9-eclipse-temurin-21-alpine base_image_run=eclipse-temurin:21-jre-alpine fi ;; "nodejs") base_image_run=node:22.15.0-bookworm-slim if [[ "$LIBC" = "musl" ]]; then base_image_run=node:22.15.0-alpine3.21 fi ;; "python") dockerfile_name="injector-integration-tests/apps/python/Dockerfile" base_image_run=python:3.14-slim-bookworm if [[ "$LIBC" = "musl" ]]; then base_image_run=python:3.14-alpine3.23 fi ;; "ruby") dockerfile_name="injector-integration-tests/apps/ruby/Dockerfile" base_image_run=ruby:3.3-slim if [[ "$LIBC" = "musl" ]]; then base_image_run=ruby:3.3-alpine fi ;; "no-getenv-symbol") dockerfile_name="injector-integration-tests/apps/no-getenv-symbol/Dockerfile" base_image_run=golang:1.26.3-trixie # We do not provide a different base image depending on the libc flavor, the point of this test scenario is to test # an app that depends on no libc whatsoever, so the test is the same for LIBC=musl and LIBC=glibc. ;; "no-libdl") dockerfile_name="injector-integration-tests/apps/no-libdl/Dockerfile" base_image_run=debian:bullseye-slim # We do not provide a different base image depending on the libc flavor: the tests themselves skip for LIBC=musl # because musl uses a different libc-detection path that is not affected by this bug. ;; "binary-validation") dockerfile_name="injector-integration-tests/apps/binary-validation/Dockerfile" base_image_run=debian:bookworm-slim ;; "copy-reloc") dockerfile_name="injector-integration-tests/apps/copy-reloc/Dockerfile" # node:16-bullseye-slim provides Debian Bullseye (glibc 2.31, i.e. < 2.34) plus a node binary whose # /usr/local/bin/node carries an R_*_COPY relocation on __environ. We do not provide a musl variant # because the tests themselves skip for LIBC=musl: the bug is in the glibc-specific fallback path. base_image_run=node:16-bullseye-slim ;; "empty-gnu-hash-bucket") dockerfile_name="injector-integration-tests/apps/empty-gnu-hash-bucket/Dockerfile" base_image_run=debian:bullseye-slim # We do not provide a different base image depending on the libc flavor: the tests themselves skip for # LIBC=musl because musl uses a different libc-detection path that is not affected by this bug. ;; *) echo "Unknown test app: $test_app" exit 1 ;; esac create_sdk_dummy_files_script="scripts/create-sdk-dummy-files.sh" if [[ "$TEST_SET" = "sdk-does-not-exist.tests" ]]; then create_sdk_dummy_files_script="scripts/create-no-sdk-dummy-files.sh" elif [[ "$TEST_SET" = "sdk-cannot-be-accessed.tests" ]]; then create_sdk_dummy_files_script="scripts/create-inaccessible-sdk-dummy-files.sh" fi docker rmi -f "$image_name" 2> /dev/null set -x docker build \ --platform "$docker_platform" \ --build-arg "base_image_build=${base_image_build}" \ --build-arg "base_image_run=${base_image_run}" \ --build-arg "injector_binary=${injector_binary}" \ --build-arg "create_sdk_dummy_files_script=${create_sdk_dummy_files_script}" \ . \ -f "$dockerfile_name" \ -t "$image_name" { set +x; } 2> /dev/null docker_run_extra_options="" docker_run_extra_arguments="" if [ "${INTERACTIVE:-}" = "true" ]; then docker_run_extra_options="--interactive --tty" if [ "$LIBC" = glibc ]; then docker_run_extra_arguments=/bin/bash elif [ "$LIBC" = musl ]; then docker_run_extra_arguments=/bin/sh else echo "The libc flavor $LIBC is not supported." exit 1 fi fi if [ "$LIBC" = "musl" ]; then dotnet_arch="linux-musl-${ARCH/amd64/x64}" else dotnet_arch="linux-${ARCH/amd64/x64}" fi set -x # shellcheck disable=SC2086 docker run $docker_run_extra_options \ --rm \ --platform "$docker_platform" \ --env EXPECTED_CPU_ARCHITECTURE="$expected_cpu_architecture" \ --env LIBC_FLAVOR="$LIBC" \ --env DOTNET_ARCH="$dotnet_arch" \ --env TEST_SET="$TEST_SET" \ --env TEST_CASES="${TEST_CASES:-}" \ --env TEST_CASES_CONTAINING="${TEST_CASES_CONTAINING:-}" \ --env VERBOSE="${VERBOSE:-}" \ "$image_name" \ $docker_run_extra_arguments { set +x; } 2> /dev/null run-tests-within-container.sh000077500000000000000000000173351524057645600364610ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts#!/usr/bin/env sh # shellcheck disable=SC2059 # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -euo noglob home_directory=$(pwd) RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' if [ -z "${EXPECTED_CPU_ARCHITECTURE:-}" ]; then echo "EXPECTED_CPU_ARCHITECTURE is not set for $0." exit 1 fi if [ -z "${LIBC_FLAVOR:-}" ]; then echo "LIBC_FLAVOR is not set for $0." exit 1 fi arch=$(uname -m) arch_exit_code=$? if [ $arch_exit_code != 0 ]; then printf "${RED}verifying CPU architecture failed:${NC}\n" echo "exit code: $arch_exit_code" echo "output: $arch" exit 1 elif [ "$arch" != "$EXPECTED_CPU_ARCHITECTURE" ]; then printf "${RED}verifying CPU architecture failed:${NC}\n" echo "expected: $EXPECTED_CPU_ARCHITECTURE" echo "actual: $arch" exit 1 else printf "verifying CPU architecture %s successful\n" "$EXPECTED_CPU_ARCHITECTURE" fi injector_binary=/injector/libotelinject.so if [ ! -f $injector_binary ]; then printf "${RED}error: %s does not exist, not running any tests.${NC}\n" "$injector_binary" exit 1 fi # Runs one test case. Usage: # # run_test_case $test_case_label $working_dir $test_app_command $expected_output $env_vars # # - test_case_label: a human readable phrase describing the test case # - working_dir: the working directory for the test case # - test_app_command: the test app executable to run, plus (optionally) additional command line arguments that will be passed on to # the test app # - expected_output: The app's output will be compared to this string, the test case is deemed successful if the exit # code is zero and the app's output matches this string # - env_vars (optional): Set additional environment variables like NODE_OPTIONS or OTEL_RESOURCE_ATTRIBUTES when running # the test # - expected_stderr (optional): If provided, the test will also assert that the process's stderr output matches this # value exactly. If not provided, stderr is not checked. # shellcheck disable=SC2317 run_test_case() { test_case_label=$1 working_dir=$2 test_app_command=$3 expected=$4 env_vars=${5:-} if [ $# -ge 6 ]; then check_stderr=true expected_stderr=$6 else check_stderr=false expected_stderr="" fi if [ -n "${TEST_CASES:-}" ]; then # Only run test case if there is an _exact match_ for the test case label in the comma-separated list $TEST_CASES. IFS=, # shellcheck disable=SC2086 set -- $TEST_CASES"" run_this_test_case="false" for selected_test_case in "$@"; do if [ "$test_case_label" = "$selected_test_case" ]; then run_this_test_case="true" fi done if [ "$run_this_test_case" != "true" ]; then echo "- skipping test case \"$test_case_label\"" return fi fi if [ -n "${TEST_CASES_CONTAINING:-}" ]; then # Only run test case if the test case label contains one of the strings from the comma-separated list # $TEST_CASES_CONTAINING as a substring. IFS=, # shellcheck disable=SC2086 set -- $TEST_CASES_CONTAINING"" run_this_test_case="false" for selected_test_case in "$@"; do set +e match=$(expr "$test_case_label" : ".*$selected_test_case.*") set -e if [ "$match" -gt 0 ]; then run_this_test_case="true" fi done if [ "$run_this_test_case" != "true" ]; then echo "- skipping test case \"$test_case_label\"" return fi fi # Ensure each test starts from a clean state. scripts/reset-config-file-directory.sh set +e match=$(expr "$test_case_label" : ".*default configuration file.*") set -e if [ "$match" -gt 0 ]; then echo "providing configuration file at default location /etc/opentelemetry/injector/injector.conf for test case \"$test_case_label\"" cp common/injector.conf /etc/opentelemetry/injector/injector.conf # Remove conf.d drop-in files so the main configuration file is tested in isolation. # Note: We use find instead of a glob pattern because the shell runs with noglob. find /etc/opentelemetry/injector/conf.d -name '*.conf' -delete 2>/dev/null || true fi set +e match=$(expr "$test_case_label" : ".*configuration file at a custom location.*") set -e if [ "$match" -gt 0 ]; then # Remove conf.d drop-in files so the custom configuration file is tested in isolation. find /etc/opentelemetry/injector/conf.d -name '*.conf' -delete 2>/dev/null || true fi set +e match=$(expr "$test_case_label" : ".*include paths filter.*") set -e if [ "$match" -gt 0 ]; then echo "providing include paths filter configuration file at /etc/opentelemetry/injector/injector.conf for test case \"$test_case_label\"" cp injector.exclude-nogetenv.conf /etc/opentelemetry/injector/injector.conf fi set +e match=$(expr "$test_case_label" : ".*env file.*") set -e if [ "$match" -gt 0 ]; then echo "providing env file at /etc/opentelemetry/injector/default_env.conf for test case \"$test_case_label\"" cp common/default_env.conf /etc/opentelemetry/injector/default_env.conf else echo "providing empty env file at /etc/opentelemetry/injector/default_env.conf for test case \"$test_case_label\"" touch /etc/opentelemetry/injector/default_env.conf fi set +e match1=$(expr "$test_case_label" : ".*the additional deps directory exists.*") match2=$(expr "$test_case_label" : ".*the shared store directory exists.*") set -e if [ "$match1" -gt 0 ] || [ "$match2" -gt 0 ]; then echo "creating .NET additional deps and shared store directories for test case \"$test_case_label\"" scripts/create-sdk-dummy-files-dotnet-optional.sh fi cd "$working_dir" if [ "$TEST_SET" = binary-validation.tests ]; then full_command="" else full_command="LD_PRELOAD=""$injector_binary"" OTEL_INJECTOR_K8S_NAMESPACE_NAME=my-namespace OTEL_INJECTOR_K8S_POD_NAME=my-pod OTEL_INJECTOR_K8S_POD_UID=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff OTEL_INJECTOR_K8S_CONTAINER_NAME=test-app" fi if [ "${VERBOSE:-}" = "true" ]; then # add OTEL_INJECTOR_LOG_LEVEL=debug to the list of env vars to see debug output from the injector. full_command="$full_command OTEL_INJECTOR_LOG_LEVEL=debug" fi if [ "$env_vars" != "" ]; then full_command=" $full_command $env_vars" fi full_command=" $full_command $test_app_command" set +e if [ "$check_stderr" = "true" ]; then stderr_file=$(mktemp) test_output=$(eval "$full_command" 2>"$stderr_file") test_exit_code=$? test_stderr=$(cat "$stderr_file") rm -f "$stderr_file" else test_output=$(eval "$full_command") test_exit_code=$? test_stderr="" fi cd "$home_directory" set -e if [ $test_exit_code != 0 ]; then printf "${RED}test \"%s\" crashed:${NC}\n" "$test_case_label" echo "test command: $full_command" echo "received exit code: $test_exit_code" echo "output: $test_output" echo "--- end of output" exit_code=1 elif [ "$test_output" != "$expected" ]; then printf "${RED}test \"%s\" failed:${NC}\n" "$test_case_label" echo "test command: $full_command" echo "expected: $expected" echo "actual: $test_output" echo "--- end of output" exit_code=1 elif [ "$check_stderr" = "true" ] && [ "$test_stderr" != "$expected_stderr" ]; then printf "${RED}test \"%s\" failed (stderr mismatch):${NC}\n" "$test_case_label" echo "test command: $full_command" echo "expected stderr: $expected_stderr" echo "actual stderr: $test_stderr" echo "--- end of output" exit_code=1 else printf "${GREEN}test \"%s\" successful${NC}\n" "$test_case_label" if [ "${VERBOSE:-}" = "true" ]; then echo "test command: $full_command" echo "output: $test_output" echo "--- end of output" fi fi } exit_code=0 # shellcheck source=injector-integration-tests/tests/default.tests . "tests/${TEST_SET:-default.tests}" exit $exit_code open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/scripts/test-all.sh000077500000000000000000000121051524057645600330270ustar00rootroot00000000000000#!/usr/bin/env bash # shellcheck disable=SC2059 # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -eu RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' cd "$(dirname "${BASH_SOURCE[0]}")"/../.. if ! docker info > /dev/null 2>&1; then echo "This script uses docker, but it looks like Docker is not running. Please start docker and try again." exit 1 fi architectures="" if [[ -n "${ARCHITECTURES:-}" ]]; then architectures=("${ARCHITECTURES//,/ }") echo Only testing a subset of architectures: "${architectures[@]}" fi libc_flavors="" if [[ -n "${LIBC_FLAVORS:-}" ]]; then libc_flavors=("${LIBC_FLAVORS//,/ }") echo Only testing a subset of libc flavors: "${libc_flavors[@]}" fi all_test_sets_string=$(find injector-integration-tests/tests/ -name \*.tests -print0 | xargs -0 -n 1 basename | sort | tr '\n' ' ') read -ra all_test_sets <<< "$all_test_sets_string" echo Found test sets: "${all_test_sets[@]}" test_sets="" if [[ -n "${TEST_SETS:-}" ]]; then IFS=',' read -ra test_sets <<< "$TEST_SETS" echo Only running a subset of test sets: "${test_sets[@]}" for test_set_id in "${test_sets[@]}"; do test_set_file_name="injector-integration-tests/tests/$test_set_id.tests" if [[ ! -f "$test_set_file_name" ]]; then echo "Error: test set \"$test_set_id\" does not exist. Available test sets are: ${all_test_sets_string//\.tests/}" exit 1 fi done fi if [[ -n "${TEST_CASES:-}" && -n "${TEST_CASES_CONTAINING:-}" ]]; then echo "Error: TEST_CASES and TEST_CASES_CONTAINING are mutually exclusive, please set at most one of them." exit 1 fi if [[ -n "${TEST_CASES:-}" ]]; then echo Only running a subset of test cases: "$TEST_CASES" else TEST_CASES="" fi if [[ -n "${TEST_CASES_CONTAINING:-}" ]]; then echo Only running test cases which contain: "$TEST_CASES_CONTAINING" else TEST_CASES_CONTAINING="" fi global_exit_code=0 test_exit_code_last_test_set=0 summary="" run_test_set_for_architecture_and_libc_flavor() { arch=$1 libc=$2 test_set=$3 echo echo "running test set \"$test_set\" on $arch and $libc" set +e ARCH="$arch" \ LIBC="$libc" \ TEST_SET="$test_set" \ TEST_CASES="$TEST_CASES" \ TEST_CASES_CONTAINING="$TEST_CASES_CONTAINING" \ injector-integration-tests/scripts/run-tests-for-container.sh test_exit_code_last_test_set=$? set -e echo echo ---------------------------------------- } run_tests_for_architecture_and_libc_flavor() { arch=$1 libc=$2 echo echo ---------------------------------------- echo "testing the injector library on $arch and $libc" echo ---------------------------------------- test_exit_code=0 for test_set in "${all_test_sets[@]}"; do local test_set_name="${test_set%.tests}" if [[ -n "${test_sets[0]}" ]]; then if [[ $(echo "${test_sets[@]}" | grep -o "$test_set_name" | wc -w) -eq 0 ]]; then echo "skipping test set $test_set" continue fi fi run_test_set_for_architecture_and_libc_flavor "$arch" "$libc" "$test_set" if [[ $test_exit_code_last_test_set -gt $test_exit_code ]]; then test_exit_code=$test_exit_code_last_test_set fi done echo echo ---------------------------------------- if [ $test_exit_code != 0 ]; then printf "${RED}tests for %s/%s failed (see above for details)${NC}\n" "$arch" "$libc" global_exit_code=1 summary="$summary\n$arch/$libc:\t${RED}failed${NC}" else printf "${GREEN}tests for %s/%s were successful${NC}\n" "$arch" "$libc" summary="$summary\n$arch/$libc:\t${GREEN}ok${NC}" fi echo ---------------------------------------- echo } declare -a all_architectures=( "arm64" "amd64" ) declare -a all_libc_flavors=( "glibc" "musl" ) # build injector binary for both architectures echo ---------------------------------------- echo building the injector binary locally from source echo ---------------------------------------- for arch in "${all_architectures[@]}"; do if [[ -n "${architectures[0]}" ]]; then if [[ $(echo "${architectures[@]}" | grep -o "$arch" | wc -w) -eq 0 ]]; then echo ---------------------------------------- echo "skipping build for CPU architecture $arch" echo ---------------------------------------- continue fi fi make ARCH="$arch" dist done echo for arch in "${all_architectures[@]}"; do if [[ -n "${architectures[0]}" ]]; then if [[ $(echo "${architectures[@]}" | grep -o "$arch" | wc -w) -eq 0 ]]; then echo ---------------------------------------- echo "skipping tests on CPU architecture $arch" echo ---------------------------------------- continue fi fi for libc_flavor in "${all_libc_flavors[@]}"; do if [[ -n "${libc_flavors[0]}" ]]; then if [[ $(echo "${libc_flavors[@]}" | grep -o "$libc_flavor" | wc -w) -eq 0 ]]; then echo ---------------------------------------- echo "skipping tests for libc flavor $libc_flavor" echo ---------------------------------------- continue fi fi run_tests_for_architecture_and_libc_flavor "$arch" "$libc_flavor" done done printf "$summary\n\n" exit $global_exit_code open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests/000077500000000000000000000000001524057645600304175ustar00rootroot00000000000000binary-validation.tests000066400000000000000000000025631524057645600350460ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests# shellcheck shell=sh # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Binary validation tests that check the injector binary for correctness. # These tests validate ELF binary properties rather than runtime behavior. # Note: These tests use a dedicated binary validation container with binutils for readelf. # Verify the injector binary has no weak dynamic symbols. # Weak symbols can cause issues with symbol resolution and should not be present. run_test_case \ "no weak dynamic symbols" \ "app" \ "./main.sh check_no_weak_dynamic_symbols" \ "test no weak dynamic symbols successful" # Verify the injector binary has no global undefined dynamic symbols. # Global undefined symbols indicate missing dependencies that should be resolved at link time. run_test_case \ "no global undefined dynamic symbols" \ "app" \ "./main.sh check_no_global_undefined_symbols" \ "test no global undefined dynamic symbols successful" # Verify the injector binary has exactly one dynamic symbol entry (the compulsory null symbol). # Every ELF file must have a null symbol as the first entry in .dynsym. This test ensures # no other dynamic symbols are exposed, which is required for a fully static injector binary. run_test_case \ "dynsym golden signature" \ "app" \ "./main.sh check_dynsym_golden_signature" \ "test dynsym golden signature successful" open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests/copy-reloc.tests000066400000000000000000000047761524057645600335750ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Regression tests for COPY relocations on glibc < 2.34. # # When an executable contains an R_*_COPY relocation for the environ symbol # (e.g. /usr/local/bin/node from node:16-bullseye-slim, but more generally # any binary that references `extern char **environ` and is not built as a # PIE), ld.so allocates the canonical storage in the executable's BSS and # rewires libc's own GOT entry to point at that copy. # # The injector resolves getenv at runtime (via dlsym or direct ELF lookup), # so it always calls the correct libc function regardless of where the # underlying environ storage lives. This ensures that pre-existing values # of NODE_OPTIONS, OTEL_RESOURCE_ATTRIBUTES, OTEL_INJECTOR_K8S_*, etc. are # visible to the injector and correctly merged or preserved. # # These tests use node:16-bullseye-slim, which ships glibc 2.31 and a node # binary whose readelf output shows `R_AARCH64_COPY __environ`. # These tests are glibc-specific: musl uses a different libc-detection path # and does not exercise the affected fallback. if [ "${LIBC_FLAVOR:-}" != "glibc" ]; then echo "Skipping copy-reloc tests: these tests are glibc-specific (LIBC_FLAVOR=${LIBC_FLAVOR:-unset})" return 0 2>/dev/null || true fi run_test_case \ "copy reloc: injects NODE_OPTIONS when it is not present" \ "app" \ "node index.js NODE_OPTIONS" \ "NODE_OPTIONS: --require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js" run_test_case \ "copy reloc: injects OTEL_RESOURCE_ATTRIBUTES from OTEL_INJECTOR_K8S_* when no pre-existing value" \ "app" \ "node index.js OTEL_RESOURCE_ATTRIBUTES" \ "OTEL_RESOURCE_ATTRIBUTES: k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" run_test_case \ "copy reloc: prepends to NODE_OPTIONS preserving pre-existing value" \ "app" \ "node index.js NODE_OPTIONS" \ "NODE_OPTIONS: --require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js --no-deprecation" \ "NODE_OPTIONS=--no-deprecation" run_test_case \ "copy reloc: merges OTEL_RESOURCE_ATTRIBUTES with pre-existing value" \ "app" \ "node index.js OTEL_RESOURCE_ATTRIBUTES" \ "OTEL_RESOURCE_ATTRIBUTES: foo=bar,k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" \ "OTEL_RESOURCE_ATTRIBUTES=foo=bar" open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests/default.tests000066400000000000000000000060711524057645600331330ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Note: The default test cases are run via the Node.js runtime. Node.js specific test case (so everything around # modifying NODE_OPTIONS) are in nodejs.tests. # Verify that unrelated environment variables are not accidentally modified. run_test_case \ "returns undefined for non-existing environment variable" \ "app" \ "node index.js non-existing" \ "DOES_NOT_EXIST: -" run_test_case \ "returns environment variable unchanged" \ "app" \ "node index.js existing" \ "TEST_VAR: value" \ "TEST_VAR=value " # Tests for modifying OTEL_RESOURCE_ATTRIBUTES. run_test_case \ "sets k8s.pod.uid and k8s.container.name via OTEL_RESOURCE_ATTRIBUTES" \ "app" \ "node index.js otel-resource-attributes" \ "OTEL_RESOURCE_ATTRIBUTES: k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" run_test_case \ "sets k8s.pod.uid and k8s.container.name via OTEL_RESOURCE_ATTRIBUTES with pre-existing value" \ "app" \ "node index.js otel-resource-attributes" \ "OTEL_RESOURCE_ATTRIBUTES: foo=bar,k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" \ "OTEL_RESOURCE_ATTRIBUTES=foo=bar" run_test_case \ "use mapped app.kubernetes.io labels for service.name and friends" \ "app" \ "node index.js otel-resource-attributes" \ "OTEL_RESOURCE_ATTRIBUTES: k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app,service.name=service-name,service.version=service-version,service.namespace=service-namespace" \ "OTEL_INJECTOR_SERVICE_NAME=service-name OTEL_INJECTOR_SERVICE_VERSION=service-version OTEL_INJECTOR_SERVICE_NAMESPACE=service-namespace" run_test_case \ "use mapped resource.opentelemetry.io labels as additional resource attributes" \ "app" \ "node index.js otel-resource-attributes" \ "OTEL_RESOURCE_ATTRIBUTES: aaa=bbb,ccc=ddd,k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" \ "OTEL_INJECTOR_RESOURCE_ATTRIBUTES=aaa=bbb,ccc=ddd" run_test_case \ "combine mapped app.kubernetes.io and resource.opentelemetry.io labels" \ "app" \ "node index.js otel-resource-attributes" \ "OTEL_RESOURCE_ATTRIBUTES: aaa=bbb,ccc=ddd,k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app,service.name=service-name,service.version=service-version,service.namespace=service-namespace" \ "OTEL_INJECTOR_SERVICE_NAME=service-name OTEL_INJECTOR_SERVICE_VERSION=service-version OTEL_INJECTOR_SERVICE_NAMESPACE=service-namespace OTEL_INJECTOR_RESOURCE_ATTRIBUTES=aaa=bbb,ccc=ddd" run_test_case \ "OTEL_INJECTOR_DISABLED prevents modification of OTEL environment variables" \ "app" \ "node index.js otel-resource-attributes" \ "OTEL_RESOURCE_ATTRIBUTES: -" \ "OTEL_INJECTOR_RESOURCE_ATTRIBUTES=aaa=bbb,ccc=ddd OTEL_INJECTOR_DISABLED=true"open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests/dotnet.tests000066400000000000000000000172061524057645600330060ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 run_test_case \ "sets CORECLR_PROFILER_PATH" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: /usr/lib/opentelemetry/dotnet/${LIBC_FLAVOR}/${DOTNET_ARCH}/OpenTelemetry.AutoInstrumentation.Native.so" # For test cases with the "default configuration file" substring in their label, run-tests-within-container.sh # will provide a configuration file in the default location (/etc/opentelemetry/injector/injector.conf). run_test_case \ "uses CORECLR_PROFILER_PATH path from the default configuration file" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: /path/from/config-file/dotnet/${LIBC_FLAVOR}/${DOTNET_ARCH}/OpenTelemetry.AutoInstrumentation.Native.so" run_test_case \ "the DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX environment variable overrides the path prefix from the default configuration file" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: /path/from/environment-variable/dotnet/${LIBC_FLAVOR}/${DOTNET_ARCH}/OpenTelemetry.AutoInstrumentation.Native.so" \ "DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/environment-variable/dotnet" run_test_case \ "uses DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX path from the configuration file at a custom location" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: /path/from/config-file-custom-location/dotnet/${LIBC_FLAVOR}/${DOTNET_ARCH}/OpenTelemetry.AutoInstrumentation.Native.so" \ "OTEL_INJECTOR_CONFIG_FILE=/custom/config/path/injector.conf" run_test_case \ "the DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX environment variable overrides the path prefix from the configuration file at a custom location" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: /path/from/environment-variable/dotnet/${LIBC_FLAVOR}/${DOTNET_ARCH}/OpenTelemetry.AutoInstrumentation.Native.so" \ "OTEL_INJECTOR_CONFIG_FILE=/custom/config/path/injector.conf DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/environment-variable/dotnet" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=dotnet disables setting CORECLR_PROFILER_PATH" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=dotnet" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=* disables setting CORECLR_PROFILER_PATH" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=*" run_test_case \ "setting DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX to the empty string disables setting CORECLR_PROFILER_PATH" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: -" \ "DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=" run_test_case \ "pre-existing CORECLR_PROFILER_PATH prevents overwriting .NET profiler path" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: /existing/profiler/path.so" \ "CORECLR_PROFILER_PATH=/existing/profiler/path.so" run_test_case \ "sets k8s.pod.uid and k8s.container.name via OTEL_RESOURCE_ATTRIBUTES" \ "app" \ "./dotnetapp otel-resource-attributes" \ "OTEL_RESOURCE_ATTRIBUTES: k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" run_test_case \ "adds the .NET profiler" \ "app" \ "./dotnetapp verify-startup-hook-has-been-injected" \ "otel_injector_dotnet_no_op_startup_hook_has_been_loaded: true" # For test cases with the "default configuration file" substring in their label, run-tests-within-container.sh # will provide a configuration file in the expected location (/etc/opentelemetry/injector/injector.conf). run_test_case \ "uses the .NET profiler path from the default configuration file" \ "app" \ "./dotnetapp verify-startup-hook-has-been-injected" \ "otel_injector_dotnet_no_op_startup_hook_has_been_loaded: true" run_test_case \ "the DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX environment variable overrides .NET profiler path from the default configuration file" \ "app" \ "./dotnetapp verify-startup-hook-has-been-injected" \ "otel_injector_dotnet_no_op_startup_hook_has_been_loaded: true" \ "DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/environment-variable/dotnet" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=dotnet will result in the .NET auto-instrumentation not being loaded" \ "app" \ "./dotnetapp verify-startup-hook-has-been-injected" \ "otel_injector_dotnet_no_op_startup_hook_has_been_loaded: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=dotnet" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=* will result in the .NET auto-instrumentation not being loaded" \ "app" \ "./dotnetapp verify-startup-hook-has-been-injected" \ "otel_injector_dotnet_no_op_startup_hook_has_been_loaded: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=*" run_test_case \ "setting DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX to the empty string will result in the .NET auto-instrumentation not being loaded" \ "app" \ "./dotnetapp verify-startup-hook-has-been-injected" \ "otel_injector_dotnet_no_op_startup_hook_has_been_loaded: -" \ "DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=" run_test_case \ "pre-existing DOTNET_SHARED_STORE prevents loading the injector startup hook" \ "app" \ "./dotnetapp verify-startup-hook-has-been-injected" \ "otel_injector_dotnet_no_op_startup_hook_has_been_loaded: -" \ "DOTNET_SHARED_STORE=/usr/src/otel/injector/no-op-startup-hook" run_test_case \ "OTEL_ custom env var is returned according to the env file provided" \ "app" \ "./dotnetapp custom-env-var OTEL_SDK_DISABLED" \ "OTEL_SDK_DISABLED: true" run_test_case \ "non OTEL_ custom env var is ignored" \ "app" \ "./dotnetapp custom-env-var CUSTOM_ENV_VAR" \ "CUSTOM_ENV_VAR: -" run_test_case \ "setting OTEL_INJECTOR_DISABLED to true string disables setting CORECLR_PROFILER_PATH" \ "app" \ "./dotnetapp coreclr-profiler-path" \ "CORECLR_PROFILER_PATH: -" \ "OTEL_INJECTOR_DISABLED=1" # DOTNET_ADDITIONAL_DEPS and DOTNET_SHARED_STORE are optional. By default these two directories are not created (see # scripts/create-sdk-dummy-files.sh), to reflect .NET SDK 1.15 no longer having these directories. However, when they do exist, # the associated environment variables are still added. This is due to the comment in # https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/tag/v1.15.0-beta.1: # > DOTNET_ADDITIONAL_DEPS/DOTNET_SHARED_STORE # > These environment variables are no longer required [...] # > Removing them is not yet recommended, as re-implementation of this workaround may be necessary for future configurations. run_test_case \ "does not set DOTNET_ADDITIONAL_DEPS when the additional deps directory does not exist" \ "app" \ "./dotnetapp custom-env-var DOTNET_ADDITIONAL_DEPS" \ "DOTNET_ADDITIONAL_DEPS: -" run_test_case \ "does not set DOTNET_SHARED_STORE when the shared store directory does not exist" \ "app" \ "./dotnetapp custom-env-var DOTNET_SHARED_STORE" \ "DOTNET_SHARED_STORE: -" run_test_case \ "sets DOTNET_ADDITIONAL_DEPS when the additional deps directory exists" \ "app" \ "./dotnetapp custom-env-var DOTNET_ADDITIONAL_DEPS" \ "DOTNET_ADDITIONAL_DEPS: /usr/lib/opentelemetry/dotnet/${LIBC_FLAVOR}/AdditionalDeps" run_test_case \ "sets DOTNET_SHARED_STORE when the shared store directory exists" \ "app" \ "./dotnetapp custom-env-var DOTNET_SHARED_STORE" \ "DOTNET_SHARED_STORE: /usr/lib/opentelemetry/dotnet/${LIBC_FLAVOR}/store" empty-gnu-hash-bucket.tests000066400000000000000000000034201524057645600355440ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Regression test for issue #399: # panic: integer overflow in ElfDynLib.lookupAddress on empty GNU hash bucket. # # On glibc < 2.34 (e.g. Debian Bullseye / glibc 2.31), /proc/self/maps shows # "libc-2.31.so" not "libc.so.6". The injector's first pass (which matches by # libc name) therefore finds nothing and falls through to the second pass, which # scans every mapped .so. # # trigger.so is a crafted ELF shared library with a .gnu.hash section where: # - The bloom filter passes (false positive) for "setenv" (hash 0x1b85483a) # - The corresponding bucket value is 0 (empty) # - chain_index = 0 - symoffset(1) wraps as u32 → panic in ReleaseSafe # # premapper.so maps trigger.so at 0x1000000 (below the ASLR range) in its # constructor, so the second-pass scan encounters it before libc. With # LD_PRELOAD="/injector/libotelinject.so:/premapper.so", premapper's constructor # runs first (the dynamic linker initialises later-listed entries first when # there are no interdependencies), ensuring trigger.so is already mapped when # the injector's initEnviron reads /proc/self/maps. # # This test is glibc-specific; musl uses a different libc-detection path. if [ "${LIBC_FLAVOR:-}" != "glibc" ]; then echo "Skipping empty-gnu-hash-bucket tests: these tests are glibc-specific (LIBC_FLAVOR=${LIBC_FLAVOR:-unset})" return 0 2>/dev/null || true fi run_test_case \ "empty GNU hash bucket: process survives lookup on empty bucket in second pass" \ "app" \ "./printenv OTEL_RESOURCE_ATTRIBUTES" \ "OTEL_RESOURCE_ATTRIBUTES: k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" \ "LD_PRELOAD=/injector/libotelinject.so:/premapper.so" open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests/jvm.tests000066400000000000000000000055271524057645600323100ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 run_test_case \ "adds the Java agent by adding a new JAVA_TOOL_OPTIONS environment variable" \ "app" \ "java -jar Main.jar verify-javaagent-has-been-injected" \ "otel.injector.jvm.no_op_agent.has_been_loaded: true" run_test_case \ "adds the Java agent to an existing JAVA_TOOL_OPTIONS environment variable" \ "app" \ "java -jar Main.jar verify-javaagent-has-been-injected-and-existing-property-is-still-in-place" \ "otel.injector.jvm.no_op_agent.has_been_loaded: true; some-property: some-value" \ "JAVA_TOOL_OPTIONS=-Dsome-property=some-value" # For test cases with the "default configuration file" substring in their label, run-tests-within-container.sh # will provide a configuration file in the expected location (/etc/opentelemetry/injector/injector.conf). run_test_case \ "uses the Java agent path from the default configuration file" \ "app" \ "java -jar Main.jar verify-javaagent-has-been-injected" \ "otel.injector.jvm.no_op_agent.has_been_loaded: true" run_test_case \ "the JVM_AUTO_INSTRUMENTATION_AGENT_PATH environment variable overrides the Java agent path from the default configuration file" \ "app" \ "java -jar Main.jar verify-javaagent-has-been-injected" \ "otel.injector.jvm.no_op_agent.has_been_loaded: true" \ "JVM_AUTO_INSTRUMENTATION_AGENT_PATH=/path/from/environment-variable/jvm/javaagent.jar" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=jvm disables the JVM auto-instrumentation" \ "app" \ "java -jar Main.jar verify-javaagent-has-been-injected" \ "otel.injector.jvm.no_op_agent.has_been_loaded: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=jvm" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=* disables the JVM auto-instrumentation" \ "app" \ "java -jar Main.jar verify-javaagent-has-been-injected" \ "otel.injector.jvm.no_op_agent.has_been_loaded: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=*" run_test_case \ "setting JVM_AUTO_INSTRUMENTATION_AGENT_PATH to the empty string disables the JVM auto-instrumentation" \ "app" \ "java -jar Main.jar verify-javaagent-has-been-injected" \ "otel.injector.jvm.no_op_agent.has_been_loaded: -" \ "JVM_AUTO_INSTRUMENTATION_AGENT_PATH=" run_test_case \ "OTEL_ custom env var is returned according to the env file provided" \ "app" \ "java -jar Main.jar custom-env-var OTEL_SDK_DISABLED" \ "OTEL_SDK_DISABLED: true" run_test_case \ "non OTEL_ custom env var is ignored" \ "app" \ "java -jar Main.jar custom-env-var CUSTOM_ENV_VAR" \ "CUSTOM_ENV_VAR: -" run_test_case \ "setting OTEL_INJECTOR_DISABLED to true string disables the JVM auto-instrumentation" \ "app" \ "java -jar Main.jar verify-javaagent-has-been-injected" \ "otel.injector.jvm.no_op_agent.has_been_loaded: -" \ "OTEL_INJECTOR_DISABLED=true"no-getenv-symbol.tests000066400000000000000000000016651524057645600346410ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests# Regression tests for missing getenv symbol: run_test_case \ "no getenv symbol: read unset environment variable" \ "app" \ "./nogetenv" \ "The environment variable \"NO_ENVIRON_TEST_VAR\" is not set." run_test_case \ "no getenv symbol: read an environment variable that is set" \ "app" \ "./nogetenv" \ "The environment variable \"NO_ENVIRON_TEST_VAR\" had the value: \"some-value\"." \ "NO_ENVIRON_TEST_VAR=some-value" # Verify that a process filtered out by include_paths produces no injector output on stderr. # The nogetenv binary is a Go PIE binary with no libc — exactly the kind of process that # previously triggered "failed to identify libc" warnings before allow/deny filtering was # moved ahead of libc detection. run_test_case \ "include paths filter: filtered process produces no injector stderr output" \ "app" \ "./nogetenv" \ "The environment variable \"NO_ENVIRON_TEST_VAR\" is not set." \ "" \ "" open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests/no-libdl.tests000066400000000000000000000022611524057645600332040ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Regression tests for libc detection on glibc < 2.34 systems without libdl. # # On glibc < 2.34 (e.g. RHEL 8 / glibc 2.28, Debian Bullseye / glibc 2.31), # dlsym is exported by libdl.so, not by libc.so itself. The injector's second # pass over /proc/self/maps searches every mapped shared object for dlsym; if # the process did not link libdl.so, that search exhausts all candidates and # returns CannotFindLibcMemoryRange. # # These tests use a plain C binary compiled without -ldl, which is the # generalisation of the concrete failure observed for /usr/bin/grep on RHEL 8. # This test is only meaningful on glibc; musl uses a different detection path. if [ "${LIBC_FLAVOR:-}" != "glibc" ]; then echo "Skipping no-libdl tests: these tests are glibc-specific (LIBC_FLAVOR=${LIBC_FLAVOR:-unset})" return 0 2>/dev/null || true fi run_test_case \ "glibc without libdl: injects OTEL_RESOURCE_ATTRIBUTES" \ "app" \ "./printenv OTEL_RESOURCE_ATTRIBUTES" \ "OTEL_RESOURCE_ATTRIBUTES: k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests/nodejs.tests000066400000000000000000000113301524057645600327630ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 run_test_case \ "injects NODE_OPTIONS if it is not present" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: --require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js" run_test_case \ "ask for NODE_OPTIONS (unset) twice" \ "app" \ "node index.js node-options-twice" \ "NODE_OPTIONS: --require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js; NODE_OPTIONS: --require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js" run_test_case \ "prepends to NODE_OPTIONS if it is present" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: --require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js --no-deprecation" \ "NODE_OPTIONS=--no-deprecation" run_test_case \ "ask for NODE_OPTIONS (set) twice" \ "app" \ "node index.js node-options-twice" \ "NODE_OPTIONS: --require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js --no-deprecation; NODE_OPTIONS: --require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js --no-deprecation" \ "NODE_OPTIONS=--no-deprecation" run_test_case \ "injects the Node.js auto-instrumentation agent" \ "app" \ "node index.js verify-auto-instrumentation-agent-has-been-injected" \ "otel_injector_nodejs_no_op_agent_has_been_loaded: true" # For test cases with the "default configuration file" substring in their label, run-tests-within-container.sh # will provide a configuration file in the default location (/etc/opentelemetry/injector/injector.conf). run_test_case \ "uses NODE_OPTIONS path from the default configuration file" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: --require /path/from/config-file/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js" run_test_case \ "the NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH environment variable overrides the NODE_OPTIONS path from the default configuration file" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: --require /path/from/environment-variable/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js" \ "NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH=/path/from/environment-variable/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js" run_test_case \ "uses NODE_OPTIONS path from the configuration file at a custom location" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: --require /path/from/config-file-custom-location/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js" \ "OTEL_INJECTOR_CONFIG_FILE=/custom/config/path/injector.conf" run_test_case \ "the NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH environment variable overrides the NODE_OPTIONS path from the configuration file at a custom location" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: --require /path/from/environment-variable/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js" \ "OTEL_INJECTOR_CONFIG_FILE=/custom/config/path/injector.conf NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH=/path/from/environment-variable/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=nodejs disables the Node.js auto-instrumentation" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=nodejs" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=* disables the Node.js auto-instrumentation" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=*" run_test_case \ "setting NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH to the empty string disables the Node.js auto-instrumentation" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: -" \ "NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH=" run_test_case \ "OTEL_ custom env var is returned according to the env file provided" \ "app" \ "node index.js custom-env-var OTEL_SDK_DISABLED" \ "OTEL_SDK_DISABLED: true" run_test_case \ "non OTEL_ custom env var is ignored" \ "app" \ "node index.js custom-env-var CUSTOM_ENV_VAR" \ "CUSTOM_ENV_VAR: -" run_test_case \ "setting OTEL_INJECTOR_DISABLED to true string disables the Node.js auto-instrumentation" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: -" \ "OTEL_INJECTOR_DISABLED=TRUE OTEL_INJECTOR_LOG_LEVEL=info" open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests/python.tests000066400000000000000000000071471524057645600330350ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 run_test_case \ "injects PYTHONPATH if it is not present" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: /usr/lib/opentelemetry/python/${LIBC_FLAVOR}" run_test_case \ "prepends to PYTHONPATH if it is present" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: /usr/lib/opentelemetry/python/${LIBC_FLAVOR}:/custom/python/path" \ "PYTHONPATH=/custom/python/path" run_test_case \ "injects the Python auto-instrumentation agent" \ "app" \ "python app.py verify-auto-instrumentation-agent-has-been-injected" \ "otel_injector_python_no_op_agent_has_been_loaded: true" # For test cases with the "default configuration file" substring in their label, run-tests-within-container.sh # will provide a configuration file in the default location (/etc/opentelemetry/injector/injector.conf). run_test_case \ "uses PYTHONPATH path from the default configuration file" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: /path/from/config-file/python/${LIBC_FLAVOR}" run_test_case \ "the PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX environment variable overrides the PYTHONPATH path from the default configuration file" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: /path/from/environment-variable/python/${LIBC_FLAVOR}" \ "PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/environment-variable/python" run_test_case \ "uses PYTHONPATH path from the configuration file at a custom location" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: /path/from/config-file-custom-location/python/${LIBC_FLAVOR}" \ "OTEL_INJECTOR_CONFIG_FILE=/custom/config/path/injector.conf" run_test_case \ "the PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX environment variable overrides the PYTHONPATH path from the configuration file at a custom location" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: /path/from/environment-variable/python/${LIBC_FLAVOR}" \ "OTEL_INJECTOR_CONFIG_FILE=/custom/config/path/injector.conf PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/environment-variable/python" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=python disables the Python auto-instrumentation" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=python" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=* disables the Python auto-instrumentation" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=*" run_test_case \ "setting PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX to the empty string disables the Python auto-instrumentation" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: -" \ "PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=" run_test_case \ "sets k8s.pod.uid and k8s.container.name via OTEL_RESOURCE_ATTRIBUTES" \ "app" \ "python app.py otel-resource-attributes" \ "OTEL_RESOURCE_ATTRIBUTES: k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" run_test_case \ "OTEL_ custom env var is returned according to the env file provided" \ "app" \ "python app.py custom-env-var OTEL_SDK_DISABLED" \ "OTEL_SDK_DISABLED: true" run_test_case \ "non OTEL_ custom env var is ignored" \ "app" \ "python app.py custom-env-var CUSTOM_ENV_VAR" \ "CUSTOM_ENV_VAR: -" run_test_case \ "setting OTEL_INJECTOR_DISABLED to true string disables the Python auto-instrumentation" \ "app" \ "python app.py pythonpath" \ "PYTHONPATH: -" \ "OTEL_INJECTOR_DISABLED=T" open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests/ruby.tests000066400000000000000000000122441524057645600324670ustar00rootroot00000000000000# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 run_test_case \ "injects RUBYOPT if it is not present" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -r /usr/lib/opentelemetry/ruby/${LIBC_FLAVOR}/opentelemetry-auto-instrumentation.rb" run_test_case \ "prepends to RUBYOPT if it is present" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -r /usr/lib/opentelemetry/ruby/${LIBC_FLAVOR}/opentelemetry-auto-instrumentation.rb -W0" \ "RUBYOPT=-W0" run_test_case \ "injects OTEL_RUBY_ADDITIONAL_GEM_PATH" \ "app" \ "ruby app.rb additional-gem-path" \ "OTEL_RUBY_ADDITIONAL_GEM_PATH: /usr/lib/opentelemetry/ruby/${LIBC_FLAVOR}" run_test_case \ "injects the Ruby auto-instrumentation agent" \ "app" \ "ruby app.rb verify-auto-instrumentation-agent-has-been-injected" \ "otel_injector_ruby_no_op_agent_has_been_loaded: true" # For test cases with the "default configuration file" substring in their label, run-tests-within-container.sh # will provide a configuration file in the default location (/etc/opentelemetry/injector/injector.conf). run_test_case \ "uses RUBYOPT path from the default configuration file" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -r /path/from/config-file/ruby/${LIBC_FLAVOR}/opentelemetry-auto-instrumentation.rb" run_test_case \ "the RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX environment variable overrides the RUBYOPT path from the default configuration file" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -r /path/from/environment-variable/ruby/${LIBC_FLAVOR}/opentelemetry-auto-instrumentation.rb" \ "RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/environment-variable/ruby" run_test_case \ "uses RUBYOPT path from the configuration file at a custom location" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -r /path/from/config-file-custom-location/ruby/${LIBC_FLAVOR}/opentelemetry-auto-instrumentation.rb" \ "OTEL_INJECTOR_CONFIG_FILE=/custom/config/path/injector.conf" run_test_case \ "the RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX environment variable overrides the RUBYOPT path from the configuration file at a custom location" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -r /path/from/environment-variable/ruby/${LIBC_FLAVOR}/opentelemetry-auto-instrumentation.rb" \ "OTEL_INJECTOR_CONFIG_FILE=/custom/config/path/injector.conf RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/environment-variable/ruby" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=ruby disables the Ruby auto-instrumentation" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=ruby" run_test_case \ "setting OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=* disables the Ruby auto-instrumentation" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -" \ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=*" run_test_case \ "setting RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX to the empty string disables the Ruby auto-instrumentation" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -" \ "RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=" # Whitespace in the path prefix would let Ruby's RUBYOPT tokenizer parse additional switches, either crashing every # Ruby process on the host with "invalid switch in RUBYOPT" or letting an attacker inject `-I` to hijack # $LOAD_PATH. The injector rejects such prefixes and stands down. run_test_case \ "whitespace in RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX disables the Ruby auto-instrumentation" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -" \ "RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX='/opt/OpenTelemetry Injector/ruby'" # When the user has pre-set OTEL_RUBY_ADDITIONAL_GEM_PATH, the injector stands down for both env vars (RUBYOPT is # skipped and OTEL_RUBY_ADDITIONAL_GEM_PATH is left untouched) to avoid a hybrid boot where our entry file loads # but its bundled dependencies would be searched in the user's path (LoadError). run_test_case \ "user-set OTEL_RUBY_ADDITIONAL_GEM_PATH is preserved and RUBYOPT injection stands down" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -" \ "OTEL_RUBY_ADDITIONAL_GEM_PATH=/user/provided/gem/path" run_test_case \ "user-set OTEL_RUBY_ADDITIONAL_GEM_PATH is preserved when Ruby injection stands down" \ "app" \ "ruby app.rb additional-gem-path" \ "OTEL_RUBY_ADDITIONAL_GEM_PATH: /user/provided/gem/path" \ "OTEL_RUBY_ADDITIONAL_GEM_PATH=/user/provided/gem/path" run_test_case \ "sets k8s.pod.uid and k8s.container.name via OTEL_RESOURCE_ATTRIBUTES" \ "app" \ "ruby app.rb otel-resource-attributes" \ "OTEL_RESOURCE_ATTRIBUTES: k8s.namespace.name=my-namespace,k8s.pod.name=my-pod,k8s.pod.uid=275ecb36-5aa8-4c2a-9c47-d8bb681b9aff,k8s.container.name=test-app" run_test_case \ "OTEL_ custom env var is returned according to the env file provided" \ "app" \ "ruby app.rb custom-env-var OTEL_SDK_DISABLED" \ "OTEL_SDK_DISABLED: true" run_test_case \ "non OTEL_ custom env var is ignored" \ "app" \ "ruby app.rb custom-env-var CUSTOM_ENV_VAR" \ "CUSTOM_ENV_VAR: -" run_test_case \ "setting OTEL_INJECTOR_DISABLED to true string disables the Ruby auto-instrumentation" \ "app" \ "ruby app.rb rubyopt" \ "RUBYOPT: -" \ "OTEL_INJECTOR_DISABLED=T" sdk-cannot-be-accessed.tests000066400000000000000000000023641524057645600356260ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # DOTNET_STARTUP_HOOKS run_test_case \ "does not add DOTNET_STARTUP_HOOKS if it the .NET auto-instrumentation is present but cannot be accessed" \ "app" \ "node index.js dotnet-startup-hooks" \ "DOTNET_STARTUP_HOOKS: -" # JAVA_TOOL_OPTIONS run_test_case \ "does not add JAVA_TOOL_OPTIONS if it the Java auto-instrumentation is present but cannot be accessed" \ "app" \ "node index.js java-tool-options" \ "JAVA_TOOL_OPTIONS: -" # NODE_OPTIONS run_test_case \ "does not add NODE_OPTIONS if it the Node.js auto-instrumentation is present but cannot be accessed" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: -" # RUBYOPT / OTEL_RUBY_ADDITIONAL_GEM_PATH -- both env vars share a gate on the entry file, so a # permission-denied access() must leave both unmodified. run_test_case \ "does not add RUBYOPT if the Ruby auto-instrumentation is present but cannot be accessed" \ "app" \ "node index.js rubyopt" \ "RUBYOPT: -" run_test_case \ "does not add OTEL_RUBY_ADDITIONAL_GEM_PATH if the Ruby auto-instrumentation is present but cannot be accessed" \ "app" \ "node index.js otel-ruby-additional-gem-path" \ "OTEL_RUBY_ADDITIONAL_GEM_PATH: -" sdk-does-not-exist.tests000066400000000000000000000023721524057645600350710ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/injector-integration-tests/tests# Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # DOTNET_STARTUP_HOOKS run_test_case \ "does not add DOTNET_STARTUP_HOOKS if it the .NET auto-instrumentation is not present" \ "app" \ "node index.js dotnet-startup-hooks" \ "DOTNET_STARTUP_HOOKS: -" # NODE_OPTIONS run_test_case \ "does not add NODE_OPTIONS if it the Node.js auto-instrumentation agent is not present" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: -" run_test_case \ "does not modify existing NODE_OPTIONS if it the Node.js auto-instrumentation agent is not present" \ "app" \ "node index.js node-options" \ "NODE_OPTIONS: --no-deprecation" \ "NODE_OPTIONS=--no-deprecation" # JAVA_TOOL_OPTIONS (note: we are using a Node.js app to verify the content of JAVA_TOOL_OPTIONS here, just because # is is more convenient) run_test_case \ "does not add JAVA_TOOL_OPTIONS if it the Java auto-instrumentation agent is not present" \ "app" \ "node index.js java-tool-options" \ "JAVA_TOOL_OPTIONS: -" run_test_case \ "does not modify existing JAVA_TOOL_OPTIONS if it the Java auto-instrumentation is not present" \ "app" \ "node index.js java-tool-options" \ "JAVA_TOOL_OPTIONS: existing-value" \ "JAVA_TOOL_OPTIONS=existing-value" open-telemetry-opentelemetry-injector-ea9dca1/renovate.json000066400000000000000000000017151524057645600245010ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:best-practices", "helpers:pinGitHubActionDigestsToSemver", "schedule:weekly" ], "ignorePaths": [ "injector-integration-tests/**/Dockerfile*" ], "packageRules": [ { "groupName": "all", "matchUpdateTypes": ["minor", "major", "patch"], "schedule": ["before 8am on Monday"] }, { "groupName": "Dockerfile", "matchManagers": ["dockerfile"], "matchUpdateTypes": ["minor", "patch", "digest"], "schedule": ["before 8am on Monday"] }, { "matchManagers": ["dockerfile"], "matchUpdateTypes": ["major"], "dependencyDashboardApproval": true }, { "groupName": "GitHub Actions", "matchManagers": ["github-actions"], "matchUpdateTypes": ["minor", "major", "patch", "digest"], "schedule": ["before 8am on Monday"] } ], "labels": [ "dependencies" ] } open-telemetry-opentelemetry-injector-ea9dca1/src/000077500000000000000000000000001524057645600225465ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/src/args_parser.zig000066400000000000000000000142731524057645600256000ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); /// Parses /proc//cmdline and extracts the executable and arguments. /// Returns the slice of the arguments including the executable as the first argument. /// The cmdline file contains null-separated arguments. /// Caller owns the returned memory and must free it. pub fn cmdLineForPID(allocator: std.mem.Allocator) ![]const []const u8 { const cmdline_path = "/proc/self/cmdline"; return getCmdLineForPID(allocator, cmdline_path); } fn getCmdLineForPID(allocator: std.mem.Allocator, path: []const u8) ![]const []const u8 { const file = std.fs.openFileAbsolute(path, .{}) catch |err| { return err; }; defer file.close(); // Read the entire file (typically small, < 4KB for most processes) const max_size = 64 * 1024; // 64KB should be more than enough const content = try file.readToEndAlloc(allocator, max_size); defer allocator.free(content); return getCmdLineFromContent(allocator, content); } fn getCmdLineFromContent(allocator: std.mem.Allocator, content: []const u8) ![]const []const u8 { if (content.len == 0) { return error.EmptyCmdline; } // Split by null bytes to get arguments var arg_list: std.ArrayList([]const u8) = .empty; errdefer arg_list.deinit(allocator); var iter = std.mem.splitScalar(u8, content, 0); while (iter.next()) |arg| { if (arg.len > 0) { // Skip empty strings const arg_copy = try allocator.dupe(u8, arg); try arg_list.append(allocator, arg_copy); } } const all_args = try arg_list.toOwnedSlice(allocator); if (all_args.len == 0) { return error.NoCmdlineArgs; } // First argument is the executable, rest are arguments return all_args; } const testing = std.testing; test "getCmdLineFromContent: basic command with arguments" { const allocator = testing.allocator; const cmdline_data = "/usr/bin/test\x00-arg1\x00value1\x00-arg2\x00value2\x00"; const result = try getCmdLineFromContent(allocator, cmdline_data); defer { for (result) |arg| allocator.free(arg); allocator.free(result); } try testing.expectEqual(5, result.len); try testing.expectEqualStrings("/usr/bin/test", result[0]); try testing.expectEqualStrings("-arg1", result[1]); try testing.expectEqualStrings("value1", result[2]); try testing.expectEqualStrings("-arg2", result[3]); try testing.expectEqualStrings("value2", result[4]); } test "getCmdLineFromContent: command without arguments" { const allocator = testing.allocator; const cmdline_data = "/usr/bin/test\x00"; const result = try getCmdLineFromContent(allocator, cmdline_data); defer { for (result) |arg| allocator.free(arg); allocator.free(result); } try testing.expectEqual(1, result.len); try testing.expectEqualStrings("/usr/bin/test", result[0]); } test "getCmdLineFromContent: command with single argument" { const allocator = testing.allocator; const cmdline_data = "/usr/bin/grep\x00pattern\x00"; const result = try getCmdLineFromContent(allocator, cmdline_data); defer { for (result) |arg| allocator.free(arg); allocator.free(result); } try testing.expectEqual(2, result.len); try testing.expectEqualStrings("/usr/bin/grep", result[0]); try testing.expectEqualStrings("pattern", result[1]); } test "getCmdLineFromContent: empty content returns error" { const allocator = testing.allocator; const cmdline_data = ""; const result = getCmdLineFromContent(allocator, cmdline_data); try testing.expectError(error.EmptyCmdline, result); } test "getCmdLineFromContent: null bytes returns error" { const allocator = testing.allocator; const cmdline_data = "\x00\x00\x00"; const result = getCmdLineFromContent(allocator, cmdline_data); try testing.expectError(error.NoCmdlineArgs, result); } test "getCmdLineFromContent: trailing nulls are ignored" { const allocator = testing.allocator; const cmdline_data = "/usr/bin/test\x00arg1\x00\x00\x00"; const result = try getCmdLineFromContent(allocator, cmdline_data); defer { for (result) |arg| allocator.free(arg); allocator.free(result); } try testing.expectEqual(2, result.len); try testing.expectEqualStrings("/usr/bin/test", result[0]); try testing.expectEqualStrings("arg1", result[1]); } test "getCmdLineFromContent: java command with jar" { const allocator = testing.allocator; const cmdline_data = "/usr/bin/java\x00-jar\x00/app/myapp.jar\x00--server.port=8080\x00"; const result = try getCmdLineFromContent(allocator, cmdline_data); defer { for (result) |arg| allocator.free(arg); allocator.free(result); } try testing.expectEqual(4, result.len); try testing.expectEqualStrings("/usr/bin/java", result[0]); try testing.expectEqualStrings("-jar", result[1]); try testing.expectEqualStrings("/app/myapp.jar", result[2]); try testing.expectEqualStrings("--server.port=8080", result[3]); } test "getCmdLineFromContent: node command with script" { const allocator = testing.allocator; const cmdline_data = "/usr/bin/node\x00/app/index.js\x00--port\x003000\x00"; const result = try getCmdLineFromContent(allocator, cmdline_data); defer { for (result) |arg| allocator.free(arg); allocator.free(result); } try testing.expectEqual(4, result.len); try testing.expectEqualStrings("/usr/bin/node", result[0]); try testing.expectEqualStrings("/app/index.js", result[1]); try testing.expectEqualStrings("--port", result[2]); try testing.expectEqualStrings("3000", result[3]); } test "getCmdLineFromContent: dotnet command with dll" { const allocator = testing.allocator; const cmdline_data = "/usr/bin/dotnet\x00/app/MyApp.dll\x00"; const result = try getCmdLineFromContent(allocator, cmdline_data); defer { for (result) |arg| allocator.free(arg); allocator.free(result); } try testing.expectEqual(2, result.len); try testing.expectEqualStrings("/usr/bin/dotnet", result[0]); try testing.expectEqualStrings("/app/MyApp.dll", result[1]); } open-telemetry-opentelemetry-injector-ea9dca1/src/auxv.zig000066400000000000000000000052051524057645600242460ustar00rootroot00000000000000const std = @import("std"); const print = @import("print.zig"); const test_util = @import("test_util.zig"); const testing = std.testing; pub const proc_self_auxv_path: []const u8 = "/proc/self/auxv"; // Need to implement and export this symbol to prevent an unwanted dependency on the `getauxval` symbol from libc, which // will not be fulfilled when linking to a process that does not include libc itself. It is safe to export this, since // we do not export any _global_ symbols, only local symbols, and in particular, getauxval is only exported locally. // Executables requiring getauxval will bind to libc's getauxval, not the symbol exported here. pub export fn getauxval(auxv_type: u32) callconv(.c) usize { return readAuxValFromFile(auxv_type, proc_self_auxv_path); } fn readAuxValFromFile(auxv_type: u32, auxv_path: []const u8) usize { var auxv_file = std.fs.openFileAbsolute(auxv_path, .{}) catch |err| { print.printError("Failed to open {s}: {}", .{ auxv_path, err }); return 0; }; defer auxv_file.close(); while (true) { var auxv_symbol: std.elf.Elf64_auxv_t = undefined; const bytes_read = auxv_file.read(std.mem.asBytes(&auxv_symbol)) catch |err| { print.printError("Failed to read from {s}: {}", .{ auxv_path, err }); return 0; }; if (bytes_read == 0) { break; } if (bytes_read < @sizeOf(std.elf.Elf64_auxv_t)) { print.printError("Short read from {s}", .{auxv_path}); return 0; } if (auxv_symbol.a_type == auxv_type) { return auxv_symbol.a_un.a_val; } else if (auxv_symbol.a_type == std.elf.AT_NULL) { break; } } return 0; } test "readAuxValFromFile: should read value" { const auxv_files = [_][]const u8{ "unit-test-assets/proc-self-auxv/auxv-glibc-arm64", "unit-test-assets/proc-self-auxv/auxv-glibc-x86_64", "unit-test-assets/proc-self-auxv/auxv-musl-arm64", "unit-test-assets/proc-self-auxv/auxv-musl-x86_64", }; for (auxv_files) |auxv_file| { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_auxv_file = try std.fs.path.resolve(allocator, &.{ cwd_path, auxv_file, }); defer allocator.free(absolute_path_to_auxv_file); const auxv_result = readAuxValFromFile(std.elf.AT_BASE, absolute_path_to_auxv_file); try test_util.expectWithMessage(auxv_result > 0, "readAuxValFromFile(AT_BASE) should return > 0"); } } open-telemetry-opentelemetry-injector-ea9dca1/src/config.zig000066400000000000000000003515211524057645600245350ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const build_options = @import("build_options"); const print = @import("print.zig"); const test_util = @import("test_util.zig"); const patterns_util = @import("patterns_util.zig"); const proc_self_environ_parser = @import("proc_self_environ_parser.zig"); const testing = std.testing; const default_config_file_path = "/etc/opentelemetry/injector/injector.conf"; const config_file_path_env_var = "OTEL_INJECTOR_CONFIG_FILE"; const max_line_length = 8192; const empty_string = @constCast(""); const allowed_env_var_prefixes = build_options.allowed_env_var_prefixes; // Agent paths are empty by default; they are enabled by installing conf.d drop-in files from the // respective language packages (e.g., opentelemetry-java-autoinstrumentation installs java.conf). const default_all_auto_instrumentation_agents_env_path = "/etc/opentelemetry/injector/default_env.conf"; const default_config_dir_path = "/etc/opentelemetry/injector/conf.d"; const config_dir_path_env_var = "OTEL_INJECTOR_CONFIG_DIR"; const dotnet_path_prefix_key = "dotnet_auto_instrumentation_agent_path_prefix"; const dotnet_minimum_dotnet_major_version_key = "dotnet_auto_instrumentation_minimum_dotnet_major_version"; const jvm_path_key = "jvm_auto_instrumentation_agent_path"; const nodejs_path_key = "nodejs_auto_instrumentation_agent_path"; const python_path_prefix_key = "python_auto_instrumentation_agent_path_prefix"; const ruby_path_prefix_key = "ruby_auto_instrumentation_agent_path_prefix"; const all_agents_env_path_key = "all_auto_instrumentation_agents_env_path"; const auto_instrumentation_disabled_key = "auto_instrumentation_disabled"; const dotnet_agent_path_prefix_env_var = "DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX"; const dotnet_minimum_dotnet_major_version_env_var = "DOTNET_AUTO_INSTRUMENTATION_MINIMUM_DOTNET_MAJOR_VERSION"; const jvm_agent_path_env_var = "JVM_AUTO_INSTRUMENTATION_AGENT_PATH"; const nodejs_agent_path_env_var = "NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH"; const python_agent_path_prefix_env_var = "PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX"; const ruby_agent_path_prefix_env_var = "RUBY_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX"; /// Configuration options for choosing what to instrument or exclude from instrumentation const include_paths_key = "include_paths"; const exclude_paths_key = "exclude_paths"; const include_paths_env_var = "OTEL_INJECTOR_INCLUDE_PATHS"; const exclude_paths_env_var = "OTEL_INJECTOR_EXCLUDE_PATHS"; const include_args_key = "include_with_arguments"; const exclude_args_key = "exclude_with_arguments"; const include_args_env_var = "OTEL_INJECTOR_INCLUDE_WITH_ARGUMENTS"; const exclude_args_env_var = "OTEL_INJECTOR_EXCLUDE_WITH_ARGUMENTS"; /// Configuration option to disable parts of the injector const auto_instrumentation_disabled_env_var = "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED"; pub const InjectorConfiguration = struct { dotnet_auto_instrumentation_agent_path_prefix: []u8, jvm_auto_instrumentation_agent_path: []u8, nodejs_auto_instrumentation_agent_path: []u8, python_auto_instrumentation_agent_path_prefix: []u8, ruby_auto_instrumentation_agent_path_prefix: []u8, all_auto_instrumentation_agents_env_path: []u8, all_auto_instrumentation_agents_env_vars: std.StringHashMap([]u8), include_paths: [][]const u8, exclude_paths: [][]const u8, include_args: [][]const u8, exclude_args: [][]const u8, dotnet_auto_instrumentation_minimum_dotnet_major_version: u32, dotnet_instrumentation_disabled: bool, jvm_instrumentation_disabled: bool, nodejs_instrumentation_disabled: bool, python_instrumentation_disabled: bool, ruby_instrumentation_disabled: bool, pub fn deinit(self: *InjectorConfiguration, allocator: std.mem.Allocator) void { allocator.free(self.dotnet_auto_instrumentation_agent_path_prefix); allocator.free(self.jvm_auto_instrumentation_agent_path); allocator.free(self.nodejs_auto_instrumentation_agent_path); allocator.free(self.python_auto_instrumentation_agent_path_prefix); allocator.free(self.ruby_auto_instrumentation_agent_path_prefix); allocator.free(self.all_auto_instrumentation_agents_env_path); var it = self.all_auto_instrumentation_agents_env_vars.iterator(); while (it.next()) |entry| { allocator.free(entry.key_ptr.*); allocator.free(entry.value_ptr.*); } self.all_auto_instrumentation_agents_env_vars.deinit(); deinitStringArray(allocator, self.include_paths); deinitStringArray(allocator, self.exclude_paths); deinitStringArray(allocator, self.include_args); deinitStringArray(allocator, self.exclude_args); } }; const ConfigApplier = fn (gpa: std.mem.Allocator, key: []const u8, value: []u8, file_path: []const u8, configuration: *InjectorConfiguration) void; const default_dotnet_auto_instrumentation_agent_path_prefix = ""; // The upstream OpenTelemetry .NET auto-instrumentation supports .NET 8 or later. Distributions of the .NET // auto-instrumentation that support older .NET versions can lower this threshold via the configuration file key // dotnet_auto_instrumentation_minimum_dotnet_major_version or the corresponding environment variable. pub const default_dotnet_auto_instrumentation_minimum_dotnet_major_version: u32 = 8; const default_jvm_auto_instrumentation_agent_path = ""; const default_nodejs_auto_instrumentation_agent_path = ""; // Python auto-instrumentation is opt-in for now, hence the default value for the Python path is the empty string -- // an empty path effectively disables auto-instrumentation for the runtime in question. const default_python_auto_instrumentation_agent_path = ""; // Ruby auto-instrumentation defaults to the empty string (disabled). It is enabled by installing the // conf.d drop-in file from the Ruby auto-instrumentation package (ruby.conf), which sets the path prefix. const default_ruby_auto_instrumentation_agent_path = ""; var cached_configuration_optional: ?InjectorConfiguration = null; /// Checks whether the configuration has already been read and reads it if necessary. The configuration will only be /// read once per process and the result will be cached for subsequent calls. /// /// The configuration will be read from the path denoted by the environment variable OTEL_INJECTOR_CONFIG_FILE, or from /// the default location /etc/opentelemetry/injector/injector.conf if this environment variable is unset or empty. /// If the file does not exist or cannot be opened, readConfiguration continues with default values. /// /// After reading the configuration file, the configuration will be merged with values read from environment variables /// (DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX, JVM_AUTO_INSTRUMENTATION_AGENT_PATH, etc.). Environment variables /// have higher precedence and can override settings from the configuration file. pub fn readConfiguration(allocator: std.mem.Allocator, getenv_fn: proc_self_environ_parser.GetenvFn) InjectorConfiguration { if (cached_configuration_optional) |cached_configuration| { return cached_configuration; } var config_file_path: []const u8 = default_config_file_path; const env_config_path = getenv_fn(allocator, config_file_path_env_var); defer if (env_config_path) |p| allocator.free(p); if (env_config_path) |value| { const trimmed = std.mem.trim(u8, value, " \t\r\n"); if (trimmed.len > 0) { config_file_path = trimmed; } } return readConfigurationFromPath(allocator, config_file_path, getenv_fn) catch |err| { print.printError("Cannot allocate memory while parsing configuration: {t}", .{err}); return createEmptyConfiguration(allocator); }; } fn createEmptyConfiguration(allocator: std.mem.Allocator) InjectorConfiguration { return InjectorConfiguration{ .dotnet_auto_instrumentation_agent_path_prefix = "", .jvm_auto_instrumentation_agent_path = "", .nodejs_auto_instrumentation_agent_path = "", .python_auto_instrumentation_agent_path_prefix = "", .ruby_auto_instrumentation_agent_path_prefix = "", .all_auto_instrumentation_agents_env_path = "", .all_auto_instrumentation_agents_env_vars = std.StringHashMap([]u8).init(allocator), .include_paths = &.{}, .exclude_paths = &.{}, .include_args = &.{}, .exclude_args = &.{}, .dotnet_auto_instrumentation_minimum_dotnet_major_version = default_dotnet_auto_instrumentation_minimum_dotnet_major_version, .dotnet_instrumentation_disabled = false, .jvm_instrumentation_disabled = false, .nodejs_instrumentation_disabled = false, .python_instrumentation_disabled = false, .ruby_instrumentation_disabled = false, }; } fn readConfigurationFromPath(allocator: std.mem.Allocator, cfg_file_path: []const u8, getenv_fn: proc_self_environ_parser.GetenvFn) std.mem.Allocator.Error!InjectorConfiguration { // We create a good amount of intermediate values - keys, values, comma-separated parts of strings, default config // values that might or might not be later overwritten, etc. It would tricky and error-prone to release each of // them individually at exactly the right time. Instead, we use an arena for all allocations (including the actual // config values). Once we have compiled the final configuration values, we copy them over to memory allocated via // the allocator passed in via the allocator parameter, then we free all intermediate values in bulk by deinit-ing // the arena. var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var preliminary_configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFile(arena_allocator, cfg_file_path, &preliminary_configuration); readConfigurationDirectory(arena_allocator, &preliminary_configuration); readConfigurationFromEnvironment(arena_allocator, &preliminary_configuration, getenv_fn); readAllAgentsEnvFile( arena_allocator, preliminary_configuration.all_auto_instrumentation_agents_env_path, &preliminary_configuration, ); const final_configuration = try copyToPermanentlyAllocatedHeap(allocator, preliminary_configuration); cached_configuration_optional = final_configuration; return final_configuration; } test "readConfiguration: should cache configuration and return same instance on subsequent calls" { const allocator = testing.allocator; defer { if (cached_configuration_optional) |*config| { config.deinit(allocator); } cached_configuration_optional = null; } const original_environ = try test_util.clearStdCEnviron(); defer test_util.resetStdCEnviron(original_environ); const config1 = readConfiguration(allocator, proc_self_environ_parser.posixGetenv); const config2 = readConfiguration(allocator, proc_self_environ_parser.posixGetenv); // Compare the pointer values directly to ensure the caching worked try testing.expectEqual(@intFromPtr(config1.dotnet_auto_instrumentation_agent_path_prefix.ptr), @intFromPtr(config2.dotnet_auto_instrumentation_agent_path_prefix.ptr)); try testing.expectEqual(@intFromPtr(config1.jvm_auto_instrumentation_agent_path.ptr), @intFromPtr(config2.jvm_auto_instrumentation_agent_path.ptr)); } test "readConfiguration: respects OTEL_INJECTOR_CONFIG_FILE environment variable" { const allocator = testing.allocator; defer { if (cached_configuration_optional) |*config| { config.deinit(allocator); } cached_configuration_optional = null; } const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/all_values.conf" }); defer allocator.free(absolute_path_to_config_file); const env_string = try std.fmt.allocPrint(allocator, "{s}={s}", .{ config_file_path_env_var, absolute_path_to_config_file }); defer allocator.free(env_string); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{env_string}); defer test_util.resetStdCEnviron(original_environ); const configuration = readConfiguration(allocator, proc_self_environ_parser.posixGetenv); // No separate deinit — cleanup is handled by the defer block above via cached_configuration_optional try testing.expectEqualStrings( "/custom/path/to/dotnet/instrumentation", configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( "/custom/path/to/jvm/javaagent.jar", configuration.jvm_auto_instrumentation_agent_path, ); } test "readConfigurationFromPath: loads from the specified path" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/all_values.conf" }); defer allocator.free(absolute_path_to_config_file); const original_environ = try test_util.clearStdCEnviron(); defer test_util.resetStdCEnviron(original_environ); var configuration = try readConfigurationFromPath(allocator, absolute_path_to_config_file, proc_self_environ_parser.posixGetenv); defer configuration.deinit(allocator); try testing.expectEqualStrings( "/custom/path/to/dotnet/instrumentation", configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqual(@as(u32, 6), configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version); try testing.expectEqualStrings( "/custom/path/to/jvm/javaagent.jar", configuration.jvm_auto_instrumentation_agent_path, ); } test "readConfigurationFromPath: file does not exist, no environment variables" { const allocator = testing.allocator; const original_environ = try test_util.clearStdCEnviron(); defer test_util.resetStdCEnviron(original_environ); var configuration = try readConfigurationFromPath(allocator, @constCast("/does/not/exist"), proc_self_environ_parser.posixGetenv); defer configuration.deinit(allocator); try testing.expectEqualStrings( default_dotnet_auto_instrumentation_agent_path_prefix, configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_jvm_auto_instrumentation_agent_path, configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_nodejs_auto_instrumentation_agent_path, configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_python_auto_instrumentation_agent_path, configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_all_auto_instrumentation_agents_env_path, configuration.all_auto_instrumentation_agents_env_path, ); try testing.expectEqual(0, configuration.all_auto_instrumentation_agents_env_vars.count()); try testing.expectEqual(0, configuration.include_paths.len); try testing.expectEqual(0, configuration.exclude_paths.len); try testing.expectEqual(0, configuration.include_args.len); try testing.expectEqual(0, configuration.exclude_args.len); try testing.expectEqual( default_dotnet_auto_instrumentation_minimum_dotnet_major_version, configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version, ); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.nodejs_instrumentation_disabled, "!configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationFromPath: file does not exist, environment variables are set" { const allocator = testing.allocator; const original_environ = try test_util.setStdCEnviron(&[9][]const u8{ "DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/env/var/dotnet", "DOTNET_AUTO_INSTRUMENTATION_MINIMUM_DOTNET_MAJOR_VERSION=6", "JVM_AUTO_INSTRUMENTATION_AGENT_PATH=/path/from/env/var/jvm", "NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH=/path/from/env/var/nodejs", "PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/env/var/python", "OTEL_INJECTOR_INCLUDE_PATHS=/path/from/env/var/include1,/path/from/env/var/include2", "OTEL_INJECTOR_EXCLUDE_PATHS=/path/from/env/var/exclude1,/path/from/env/var/exclude2", "OTEL_INJECTOR_INCLUDE_WITH_ARGUMENTS=--from-env-var-include1,--from-env-var-include2", "OTEL_INJECTOR_EXCLUDE_WITH_ARGUMENTS=--from-env-var-exclude1,--from-env-var-exclude2", }); defer test_util.resetStdCEnviron(original_environ); var configuration = try readConfigurationFromPath(allocator, @constCast("/does/not/exist"), proc_self_environ_parser.posixGetenv); defer configuration.deinit(allocator); try testing.expectEqualStrings( "/path/from/env/var/dotnet", configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqual(@as(u32, 6), configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version); try testing.expectEqualStrings( "/path/from/env/var/jvm", configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/path/from/env/var/nodejs", configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/path/from/env/var/python", configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_all_auto_instrumentation_agents_env_path, configuration.all_auto_instrumentation_agents_env_path, ); try testing.expectEqual(0, configuration.all_auto_instrumentation_agents_env_vars.count()); try testing.expectEqual(2, configuration.include_paths.len); try testing.expectEqualStrings("/path/from/env/var/include1", configuration.include_paths[0]); try testing.expectEqualStrings("/path/from/env/var/include2", configuration.include_paths[1]); try testing.expectEqual(2, configuration.exclude_paths.len); try testing.expectEqualStrings("/path/from/env/var/exclude1", configuration.exclude_paths[0]); try testing.expectEqualStrings("/path/from/env/var/exclude2", configuration.exclude_paths[1]); try testing.expectEqual(2, configuration.include_args.len); try testing.expectEqualStrings("--from-env-var-include1", configuration.include_args[0]); try testing.expectEqualStrings("--from-env-var-include2", configuration.include_args[1]); try testing.expectEqual(2, configuration.exclude_args.len); try testing.expectEqualStrings("--from-env-var-exclude1", configuration.exclude_args[0]); try testing.expectEqualStrings("--from-env-var-exclude2", configuration.exclude_args[1]); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.nodejs_instrumentation_disabled, "!configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationFromPath: all configuration values from file, no environment variables" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/all_values.conf" }); defer allocator.free(absolute_path_to_config_file); const original_environ = try test_util.clearStdCEnviron(); defer test_util.resetStdCEnviron(original_environ); var configuration = try readConfigurationFromPath(allocator, absolute_path_to_config_file, proc_self_environ_parser.posixGetenv); defer configuration.deinit(allocator); try testing.expectEqualStrings( "/custom/path/to/dotnet/instrumentation", configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqual(@as(u32, 6), configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version); try testing.expectEqualStrings( "/custom/path/to/jvm/javaagent.jar", configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/custom/path/to/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js", configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/custom/path/to/python", configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( "/custom/path/to/auto_instrumentation_env.conf", configuration.all_auto_instrumentation_agents_env_path, ); try testing.expectEqual(0, configuration.all_auto_instrumentation_agents_env_vars.count()); try testing.expectEqual(3, configuration.include_paths.len); try testing.expectEqualStrings("/app/*", configuration.include_paths[0]); try testing.expectEqualStrings("/home/user/test/*", configuration.include_paths[1]); try testing.expectEqualStrings("/another_dir/*", configuration.include_paths[2]); try testing.expectEqual(3, configuration.exclude_paths.len); try testing.expectEqualStrings("/usr/*", configuration.exclude_paths[0]); try testing.expectEqualStrings("/opt/*", configuration.exclude_paths[1]); try testing.expectEqualStrings("/another_excluded_dir/*", configuration.exclude_paths[2]); try testing.expectEqual(4, configuration.include_args.len); try testing.expectEqualStrings("-jar", configuration.include_args[0]); try testing.expectEqualStrings("*my-app*", configuration.include_args[1]); try testing.expectEqualStrings("*.js", configuration.include_args[2]); try testing.expectEqualStrings("*.dll", configuration.include_args[3]); try testing.expectEqual(3, configuration.exclude_args.len); try testing.expectEqualStrings("-javaagent*", configuration.exclude_args[0]); try testing.expectEqualStrings("*@opentelemetry-js*", configuration.exclude_args[1]); try testing.expectEqualStrings("-debug", configuration.exclude_args[2]); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(configuration.nodejs_instrumentation_disabled, "configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationFromPath: override some configuration values from file with environment variables" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/all_values.conf" }); defer allocator.free(absolute_path_to_config_file); const original_environ = try test_util.setStdCEnviron(&[6][]const u8{ "DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/env/var/dotnet", "DOTNET_AUTO_INSTRUMENTATION_MINIMUM_DOTNET_MAJOR_VERSION=9", "NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH=/path/from/env/var/nodejs", "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=python", "OTEL_INJECTOR_INCLUDE_PATHS=/path/from/env/var/include1,/path/from/env/var/include2", "OTEL_INJECTOR_EXCLUDE_WITH_ARGUMENTS=--from-env-var-exclude1,--from-env-var-exclude2", }); defer test_util.resetStdCEnviron(original_environ); var configuration = try readConfigurationFromPath(allocator, absolute_path_to_config_file, proc_self_environ_parser.posixGetenv); defer configuration.deinit(allocator); try testing.expectEqualStrings( "/path/from/env/var/dotnet", configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqual(@as(u32, 9), configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version); try testing.expectEqualStrings( "/custom/path/to/jvm/javaagent.jar", configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/path/from/env/var/nodejs", configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/custom/path/to/python", configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( "/custom/path/to/auto_instrumentation_env.conf", configuration.all_auto_instrumentation_agents_env_path, ); try testing.expectEqual(0, configuration.all_auto_instrumentation_agents_env_vars.count()); try testing.expectEqual(2, configuration.include_paths.len); try testing.expectEqualStrings("/path/from/env/var/include1", configuration.include_paths[0]); try testing.expectEqualStrings("/path/from/env/var/include2", configuration.include_paths[1]); try testing.expectEqual(3, configuration.exclude_paths.len); try testing.expectEqualStrings("/usr/*", configuration.exclude_paths[0]); try testing.expectEqualStrings("/opt/*", configuration.exclude_paths[1]); try testing.expectEqualStrings("/another_excluded_dir/*", configuration.exclude_paths[2]); try testing.expectEqual(4, configuration.include_args.len); try testing.expectEqualStrings("-jar", configuration.include_args[0]); try testing.expectEqualStrings("*my-app*", configuration.include_args[1]); try testing.expectEqualStrings("*.js", configuration.include_args[2]); try testing.expectEqualStrings("*.dll", configuration.include_args[3]); try testing.expectEqual(2, configuration.exclude_args.len); try testing.expectEqualStrings("--from-env-var-exclude1", configuration.exclude_args[0]); try testing.expectEqualStrings("--from-env-var-exclude2", configuration.exclude_args[1]); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.nodejs_instrumentation_disabled, "!configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(configuration.python_instrumentation_disabled, "configuration.python_instrumentation_disabled"); } fn createDefaultConfiguration(arena_allocator: std.mem.Allocator) std.mem.Allocator.Error!InjectorConfiguration { return InjectorConfiguration{ .dotnet_auto_instrumentation_agent_path_prefix = try std.fmt.allocPrint(arena_allocator, "{s}", .{default_dotnet_auto_instrumentation_agent_path_prefix}), .jvm_auto_instrumentation_agent_path = try std.fmt.allocPrint(arena_allocator, "{s}", .{default_jvm_auto_instrumentation_agent_path}), .nodejs_auto_instrumentation_agent_path = try std.fmt.allocPrint(arena_allocator, "{s}", .{default_nodejs_auto_instrumentation_agent_path}), .python_auto_instrumentation_agent_path_prefix = try std.fmt.allocPrint(arena_allocator, "{s}", .{default_python_auto_instrumentation_agent_path}), .ruby_auto_instrumentation_agent_path_prefix = try std.fmt.allocPrint(arena_allocator, "{s}", .{default_ruby_auto_instrumentation_agent_path}), .all_auto_instrumentation_agents_env_path = try std.fmt.allocPrint(arena_allocator, "{s}", .{default_all_auto_instrumentation_agents_env_path}), .all_auto_instrumentation_agents_env_vars = std.StringHashMap([]u8).init(arena_allocator), .include_paths = &.{}, .exclude_paths = &.{}, .include_args = &.{}, .exclude_args = &.{}, .dotnet_auto_instrumentation_minimum_dotnet_major_version = default_dotnet_auto_instrumentation_minimum_dotnet_major_version, .dotnet_instrumentation_disabled = false, .jvm_instrumentation_disabled = false, .nodejs_instrumentation_disabled = false, .python_instrumentation_disabled = false, .ruby_instrumentation_disabled = false, }; } fn applyAutoInstrumentationDisabledValue(trimmed_value: []const u8, source: []const u8, configuration: *InjectorConfiguration) void { if (std.mem.eql(u8, trimmed_value, "*")) { configuration.dotnet_instrumentation_disabled = true; configuration.jvm_instrumentation_disabled = true; configuration.nodejs_instrumentation_disabled = true; configuration.python_instrumentation_disabled = true; configuration.ruby_instrumentation_disabled = true; } else { // In case the configuration file specifies auto_instrumentation_disabled and this is the second call of // applyAutoInstrumentationDisabledValue for parsing OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED (if present), // we need to reset all disabled flags to make sure the environment variable completely overrides what the // config file said. configuration.dotnet_instrumentation_disabled = false; configuration.jvm_instrumentation_disabled = false; configuration.nodejs_instrumentation_disabled = false; configuration.python_instrumentation_disabled = false; configuration.ruby_instrumentation_disabled = false; var it = std.mem.splitScalar(u8, trimmed_value, ','); while (it.next()) |part| { const trimmed_part = std.mem.trim(u8, part, " \t"); if (std.mem.eql(u8, trimmed_part, "dotnet")) { configuration.dotnet_instrumentation_disabled = true; } else if (std.mem.eql(u8, trimmed_part, "jvm")) { configuration.jvm_instrumentation_disabled = true; } else if (std.mem.eql(u8, trimmed_part, "nodejs")) { configuration.nodejs_instrumentation_disabled = true; } else if (std.mem.eql(u8, trimmed_part, "python")) { configuration.python_instrumentation_disabled = true; } else if (std.mem.eql(u8, trimmed_part, "ruby")) { configuration.ruby_instrumentation_disabled = true; } else if (trimmed_part.len > 0) { print.printWarn( "Unknown runtime in the list of disabled runtimes from {s}: \"{s}\" - this list item will be ignored.", .{ source, trimmed_part }, ); } } } } fn applyDotnetMinimumDotnetMajorVersionValue(value: []const u8, source: []const u8, configuration: *InjectorConfiguration) void { const trimmed_value = std.mem.trim(u8, value, " \t"); const parsed_version = std.fmt.parseUnsigned(u32, trimmed_value, 10) catch { print.printWarn( "Ignoring invalid value for {s} from {s}: \"{s}\" - a non-negative integer is required.", .{ dotnet_minimum_dotnet_major_version_key, source, trimmed_value }, ); return; }; configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version = parsed_version; } test "applyDotnetMinimumDotnetMajorVersionValue: parses a valid value" { var configuration = createEmptyConfiguration(testing.allocator); defer configuration.all_auto_instrumentation_agents_env_vars.deinit(); applyDotnetMinimumDotnetMajorVersionValue("6", "test", &configuration); try testing.expectEqual(@as(u32, 6), configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version); } test "applyDotnetMinimumDotnetMajorVersionValue: trims whitespace" { var configuration = createEmptyConfiguration(testing.allocator); defer configuration.all_auto_instrumentation_agents_env_vars.deinit(); applyDotnetMinimumDotnetMajorVersionValue(" 7\t", "test", &configuration); try testing.expectEqual(@as(u32, 7), configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version); } test "applyDotnetMinimumDotnetMajorVersionValue: ignores an invalid value and keeps the default" { var configuration = createEmptyConfiguration(testing.allocator); defer configuration.all_auto_instrumentation_agents_env_vars.deinit(); applyDotnetMinimumDotnetMajorVersionValue("not-a-number", "test", &configuration); try testing.expectEqual( default_dotnet_auto_instrumentation_minimum_dotnet_major_version, configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version, ); } fn applyCommaSeparatedPatternsOption(arena_allocator: std.mem.Allocator, setting: *[][]const u8, value: []u8, pattern_name: []const u8, cfg_file_path: []const u8) void { const new_patterns = patterns_util.splitByComma(arena_allocator, value) catch |err| { print.printError("error parsing {s} value from configuration file {s}: {}", .{ pattern_name, cfg_file_path, err }); return; }; setting.* = std.mem.concat(arena_allocator, []const u8, &.{ setting.*, new_patterns }) catch |err| { print.printError("error concatenating {s} from configuration file {s}: {}", .{ pattern_name, cfg_file_path, err }); return; }; } fn applyKeyValueToGeneralOptions(arena_allocator: std.mem.Allocator, key: []const u8, value: []u8, _cfg_file_path: []const u8, _configuration: *InjectorConfiguration) void { if (std.mem.eql(u8, key, dotnet_path_prefix_key)) { _configuration.dotnet_auto_instrumentation_agent_path_prefix = value; } else if (std.mem.eql(u8, key, dotnet_minimum_dotnet_major_version_key)) { applyDotnetMinimumDotnetMajorVersionValue(value, _cfg_file_path, _configuration); } else if (std.mem.eql(u8, key, jvm_path_key)) { _configuration.jvm_auto_instrumentation_agent_path = value; } else if (std.mem.eql(u8, key, nodejs_path_key)) { _configuration.nodejs_auto_instrumentation_agent_path = value; } else if (std.mem.eql(u8, key, python_path_prefix_key)) { _configuration.python_auto_instrumentation_agent_path_prefix = value; } else if (std.mem.eql(u8, key, ruby_path_prefix_key)) { _configuration.ruby_auto_instrumentation_agent_path_prefix = value; } else if (std.mem.eql(u8, key, all_agents_env_path_key)) { _configuration.all_auto_instrumentation_agents_env_path = value; } else if (std.mem.eql(u8, key, include_paths_key)) { applyCommaSeparatedPatternsOption(arena_allocator, &_configuration.include_paths, value, "include_paths", _cfg_file_path); } else if (std.mem.eql(u8, key, exclude_paths_key)) { applyCommaSeparatedPatternsOption(arena_allocator, &_configuration.exclude_paths, value, "exclude_paths", _cfg_file_path); } else if (std.mem.eql(u8, key, include_args_key)) { applyCommaSeparatedPatternsOption(arena_allocator, &_configuration.include_args, value, "include_arguments", _cfg_file_path); } else if (std.mem.eql(u8, key, exclude_args_key)) { applyCommaSeparatedPatternsOption(arena_allocator, &_configuration.exclude_args, value, "exclude_arguments", _cfg_file_path); } else if (std.mem.eql(u8, key, auto_instrumentation_disabled_key)) { applyAutoInstrumentationDisabledValue(value, _cfg_file_path, _configuration); } else { print.printError("ignoring unknown configuration key in {s}: {s}={s}", .{ _cfg_file_path, key, value }); } } fn readConfigurationFile(arena_allocator: std.mem.Allocator, cfg_file_path: []const u8, configuration: *InjectorConfiguration) void { print.printDebug("reading configuration file from {s}.", .{cfg_file_path}); const config_file = std.fs.cwd().openFile(cfg_file_path, .{}) catch |err| { print.printDebug( "The configuration file {s} does not exist or cannot be opened. Configuration will use default values and environment variables only. Error: {t}", .{ cfg_file_path, err }, ); return; }; defer config_file.close(); parseConfiguration( arena_allocator, configuration, config_file, cfg_file_path, applyKeyValueToGeneralOptions, ); print.printDebug("successfully read configuration file from {s}.", .{cfg_file_path}); } /// Reads configuration drop-in files from the conf.d directory. Each installed language package /// (e.g., opentelemetry-java-autoinstrumentation) places its configuration in this directory. /// Files are read in alphabetical order; later files can override earlier ones. fn readConfigurationDirectory(arena_allocator: std.mem.Allocator, configuration: *InjectorConfiguration) void { var config_dir_path: []const u8 = default_config_dir_path; if (std.posix.getenv(config_dir_path_env_var)) |value| { config_dir_path = std.mem.trim(u8, value, " \t\r\n"); if (config_dir_path.len == 0) { config_dir_path = default_config_dir_path; } } print.printDebug("reading configuration drop-in files from {s}.", .{config_dir_path}); var dir = std.fs.cwd().openDir(config_dir_path, .{ .iterate = true }) catch |err| { print.printDebug( "The configuration directory {s} does not exist or cannot be opened. Error: {t}", .{ config_dir_path, err }, ); return; }; defer dir.close(); // Collect and sort file names to ensure deterministic ordering var file_names: std.ArrayList([]const u8) = std.ArrayList([]const u8).initCapacity(arena_allocator, 16) catch { print.printDebug("Failed to allocate memory for conf.d file list", .{}); return; }; var dir_iter = dir.iterate(); while (dir_iter.next() catch null) |entry| { if (entry.kind != .file) continue; if (!std.mem.endsWith(u8, entry.name, ".conf")) continue; const name_copy = std.fmt.allocPrint(arena_allocator, "{s}", .{entry.name}) catch continue; file_names.append(arena_allocator, name_copy) catch continue; } // Sort alphabetically std.mem.sort([]const u8, file_names.items, {}, struct { fn lessThan(_: void, a: []const u8, b: []const u8) bool { return std.mem.order(u8, a, b) == .lt; } }.lessThan); // Read each file for (file_names.items) |file_name| { const full_path = std.fmt.allocPrint(arena_allocator, "{s}/{s}", .{ config_dir_path, file_name }) catch continue; print.printDebug("reading configuration drop-in file: {s}", .{full_path}); readConfigurationFile(arena_allocator, full_path, configuration); } } fn applyKeyValueToAllAgentsEnv(_: std.mem.Allocator, key: []const u8, value: []u8, _file_path: []const u8, _configuration: *InjectorConfiguration) void { if (!isAllowedEnvVarKey(key)) { print.printWarn( "environment variable {s} does not match any allowed prefix from this build ({s}). ignoring.", .{ key, allowed_env_var_prefixes }, ); return; } _configuration.all_auto_instrumentation_agents_env_vars.put(key, value) catch |e| { print.printError("error storing environment variable {s} from file {s}: {}", .{ key, _file_path, e }); }; } fn isAllowedEnvVarKey(key: []const u8) bool { return hasAllowedPrefix(key, allowed_env_var_prefixes); } fn hasAllowedPrefix(key: []const u8, prefixes_csv: []const u8) bool { var prefixes = std.mem.splitScalar(u8, prefixes_csv, ','); while (prefixes.next()) |raw_prefix| { const prefix = std.mem.trim(u8, raw_prefix, " \t\r\n"); if (prefix.len == 0) { continue; } if (std.mem.startsWith(u8, key, prefix)) { return true; } } return false; } fn readAllAgentsEnvFile(arena_allocator: std.mem.Allocator, env_file_path: []const u8, configuration: *InjectorConfiguration) void { if (env_file_path.len == 0) { return; } const env_file = std.fs.cwd().openFile(env_file_path, .{}) catch |err| { print.printDebug("The configuration file {s} does not exist or cannot be opened. Error: {}", .{ env_file_path, err }); return; }; defer env_file.close(); parseConfiguration( arena_allocator, configuration, env_file, env_file_path, applyKeyValueToAllAgentsEnv, ); } fn parseConfiguration( arena_allocator: std.mem.Allocator, configuration: *InjectorConfiguration, config_file: std.fs.File, cfg_file_path: []const u8, comptime applyKeyValueToConfig: ConfigApplier, ) void { var buf: [max_line_length]u8 = undefined; var reader = config_file.reader(&buf); while (takeSentinelOrDiscardOverlyLongLine(&reader, cfg_file_path)) |line| { if (parseLine(arena_allocator, line, cfg_file_path)) |kv| { applyKeyValueToConfig(arena_allocator, kv.key, kv.value, cfg_file_path, configuration); } } else |err| switch (err) { error.ReadFailed => { print.printError("Failed to read configuration file {s}", .{cfg_file_path}); return; }, // if the file does not end with a newline, we still need to parse the last line error.EndOfStream => { var buffer: [max_line_length]u8 = undefined; const chars = reader.interface.readSliceShort(&buffer) catch 0; if (parseLine(arena_allocator, buffer[0..chars], cfg_file_path)) |kv| { applyKeyValueToConfig(arena_allocator, kv.key, kv.value, cfg_file_path, configuration); } }, } } fn copyToPermanentlyAllocatedHeap( allocator: std.mem.Allocator, preliminary_configuration: InjectorConfiguration, ) std.mem.Allocator.Error!InjectorConfiguration { return InjectorConfiguration{ .dotnet_auto_instrumentation_agent_path_prefix = try std.fmt.allocPrint( allocator, "{s}", .{preliminary_configuration.dotnet_auto_instrumentation_agent_path_prefix}, ), .jvm_auto_instrumentation_agent_path = try std.fmt.allocPrint( allocator, "{s}", .{preliminary_configuration.jvm_auto_instrumentation_agent_path}, ), .nodejs_auto_instrumentation_agent_path = try std.fmt.allocPrint( allocator, "{s}", .{preliminary_configuration.nodejs_auto_instrumentation_agent_path}, ), .python_auto_instrumentation_agent_path_prefix = try std.fmt.allocPrint( allocator, "{s}", .{preliminary_configuration.python_auto_instrumentation_agent_path_prefix}, ), .ruby_auto_instrumentation_agent_path_prefix = try std.fmt.allocPrint( allocator, "{s}", .{preliminary_configuration.ruby_auto_instrumentation_agent_path_prefix}, ), .all_auto_instrumentation_agents_env_path = try std.fmt.allocPrint( allocator, "{s}", .{preliminary_configuration.all_auto_instrumentation_agents_env_path}, ), .all_auto_instrumentation_agents_env_vars = try copyMap( allocator, preliminary_configuration.all_auto_instrumentation_agents_env_vars, ), .include_paths = try copyStringArray(allocator, preliminary_configuration.include_paths), .exclude_paths = try copyStringArray(allocator, preliminary_configuration.exclude_paths), .include_args = try copyStringArray(allocator, preliminary_configuration.include_args), .exclude_args = try copyStringArray(allocator, preliminary_configuration.exclude_args), .dotnet_auto_instrumentation_minimum_dotnet_major_version = preliminary_configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version, .dotnet_instrumentation_disabled = preliminary_configuration.dotnet_instrumentation_disabled, .jvm_instrumentation_disabled = preliminary_configuration.jvm_instrumentation_disabled, .nodejs_instrumentation_disabled = preliminary_configuration.nodejs_instrumentation_disabled, .python_instrumentation_disabled = preliminary_configuration.python_instrumentation_disabled, .ruby_instrumentation_disabled = preliminary_configuration.ruby_instrumentation_disabled, }; } fn takeSentinelOrDiscardOverlyLongLine(reader: *std.fs.File.Reader, cfg_file_path: []const u8) ![]u8 { if (reader.interface.takeSentinel('\n')) |slice| { return slice; } else |err| switch (err) { error.StreamTooLong => { print.printError( "A line in configuration file {s} exceeds the maximum allowed length of {d} characters and will be ignored.", .{ cfg_file_path, max_line_length }, ); // Ignore lines that are too long for the buffer; advance the the read positon to the next delimiter to // avoid stream corruption. _ = try reader.interface.discardDelimiterInclusive('\n'); return empty_string; }, else => |leftover_err| return leftover_err, } } test "readConfigurationFile: file does not exist" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFile(arena_allocator, "/does/not/exist", &configuration); try testing.expectEqualStrings( default_dotnet_auto_instrumentation_agent_path_prefix, configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_jvm_auto_instrumentation_agent_path, configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_nodejs_auto_instrumentation_agent_path, configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_python_auto_instrumentation_agent_path, configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_all_auto_instrumentation_agents_env_path, configuration.all_auto_instrumentation_agents_env_path, ); try testing.expectEqual(0, configuration.all_auto_instrumentation_agents_env_vars.count()); try testing.expectEqual(0, configuration.include_paths.len); try testing.expectEqual(0, configuration.exclude_paths.len); try testing.expectEqual(0, configuration.include_args.len); try testing.expectEqual(0, configuration.exclude_args.len); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.nodejs_instrumentation_disabled, "!configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationFile: empty file" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/empty.conf" }); defer allocator.free(absolute_path_to_config_file); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFile(arena_allocator, absolute_path_to_config_file, &configuration); try testing.expectEqualStrings( default_dotnet_auto_instrumentation_agent_path_prefix, configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_jvm_auto_instrumentation_agent_path, configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_nodejs_auto_instrumentation_agent_path, configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_python_auto_instrumentation_agent_path, configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_all_auto_instrumentation_agents_env_path, configuration.all_auto_instrumentation_agents_env_path, ); try testing.expectEqual(0, configuration.all_auto_instrumentation_agents_env_vars.count()); try testing.expectEqual(0, configuration.include_paths.len); try testing.expectEqual(0, configuration.exclude_paths.len); try testing.expectEqual(0, configuration.include_args.len); try testing.expectEqual(0, configuration.exclude_args.len); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.nodejs_instrumentation_disabled, "!configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationFile: all configuration values" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/all_values.conf" }); defer allocator.free(absolute_path_to_config_file); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFile(arena_allocator, absolute_path_to_config_file, &configuration); try testing.expectEqualStrings( "/custom/path/to/dotnet/instrumentation", configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( "/custom/path/to/jvm/javaagent.jar", configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/custom/path/to/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js", configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/custom/path/to/python", configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( "/custom/path/to/ruby", configuration.ruby_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( "/custom/path/to/auto_instrumentation_env.conf", configuration.all_auto_instrumentation_agents_env_path, ); try testing.expectEqual(0, configuration.all_auto_instrumentation_agents_env_vars.count()); try testing.expectEqual(3, configuration.include_paths.len); try testing.expectEqualStrings("/app/*", configuration.include_paths[0]); try testing.expectEqualStrings("/home/user/test/*", configuration.include_paths[1]); try testing.expectEqualStrings("/another_dir/*", configuration.include_paths[2]); try testing.expectEqual(3, configuration.exclude_paths.len); try testing.expectEqualStrings("/usr/*", configuration.exclude_paths[0]); try testing.expectEqualStrings("/opt/*", configuration.exclude_paths[1]); try testing.expectEqualStrings("/another_excluded_dir/*", configuration.exclude_paths[2]); try testing.expectEqual(4, configuration.include_args.len); try testing.expectEqualStrings("-jar", configuration.include_args[0]); try testing.expectEqualStrings("*my-app*", configuration.include_args[1]); try testing.expectEqualStrings("*.js", configuration.include_args[2]); try testing.expectEqualStrings("*.dll", configuration.include_args[3]); try testing.expectEqual(3, configuration.exclude_args.len); try testing.expectEqualStrings("-javaagent*", configuration.exclude_args[0]); try testing.expectEqualStrings("*@opentelemetry-js*", configuration.exclude_args[1]); try testing.expectEqualStrings("-debug", configuration.exclude_args[2]); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(configuration.nodejs_instrumentation_disabled, "configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationFile: all configuration values plus whitespace and comments" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/with_comments_and_whitespace.conf" }); defer allocator.free(absolute_path_to_config_file); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFile(arena_allocator, absolute_path_to_config_file, &configuration); try testing.expectEqualStrings( "/custom/path/to/dotnet/instrumentation", configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( "/custom/path/to/jvm/javaagent.jar", configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/custom/path/to/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js", configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_python_auto_instrumentation_agent_path, configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( "/custom/path/to/auto_instrumentation_env.conf", configuration.all_auto_instrumentation_agents_env_path, ); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(configuration.jvm_instrumentation_disabled, "configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.nodejs_instrumentation_disabled, "!configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationFile: does not parse overly long lines" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/very_long_lines.conf" }); defer allocator.free(absolute_path_to_config_file); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFile(arena_allocator, absolute_path_to_config_file, &configuration); try testing.expectEqualStrings( default_dotnet_auto_instrumentation_agent_path_prefix, configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( "/this/line/should/be/parsed", configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_nodejs_auto_instrumentation_agent_path, configuration.nodejs_auto_instrumentation_agent_path, ); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.nodejs_instrumentation_disabled, "!configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationFile: auto_instrumentation_disabled=* disables all runtimes" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/auto_instrumentation_disabled_star.conf" }); defer allocator.free(absolute_path_to_config_file); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFile(arena_allocator, absolute_path_to_config_file, &configuration); try test_util.expectWithMessage(configuration.dotnet_instrumentation_disabled, "configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(configuration.jvm_instrumentation_disabled, "configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(configuration.nodejs_instrumentation_disabled, "configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(configuration.python_instrumentation_disabled, "configuration.python_instrumentation_disabled"); } test "readConfigurationFile: auto_instrumentation_disabled with comma-separated list" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/auto_instrumentation_disabled_list.conf" }); defer allocator.free(absolute_path_to_config_file); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFile(arena_allocator, absolute_path_to_config_file, &configuration); try test_util.expectWithMessage(configuration.dotnet_instrumentation_disabled, "configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(configuration.jvm_instrumentation_disabled, "configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(configuration.nodejs_instrumentation_disabled, "configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(configuration.python_instrumentation_disabled, "configuration.python_instrumentation_disabled"); } test "readConfigurationFile: multiple auto_instrumentation_disabled line: last one wins" { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_config_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/config/auto_instrumentation_disabled_multiple_times.conf" }); defer allocator.free(absolute_path_to_config_file); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFile(arena_allocator, absolute_path_to_config_file, &configuration); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(configuration.jvm_instrumentation_disabled, "configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.nodejs_instrumentation_disabled, "!configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "hasAllowedPrefix: default OTEL_ prefix only" { try test_util.expectWithMessage( hasAllowedPrefix("OTEL_SDK_DISABLED", "OTEL_"), "OTEL_SDK_DISABLED should match OTEL_", ); try test_util.expectWithMessage( !hasAllowedPrefix("CUSTOM_PREFIX_ACCESS_TOKEN", "OTEL_"), "CUSTOM_PREFIX_ACCESS_TOKEN should not match OTEL_", ); } test "hasAllowedPrefix: supports multiple prefixes and whitespace" { try test_util.expectWithMessage( hasAllowedPrefix("OTEL_SDK_DISABLED", " OTEL_ , CUSTOM_PREFIX_ ,, VENDOR_ "), "OTEL_SDK_DISABLED should match OTEL_", ); try test_util.expectWithMessage( hasAllowedPrefix("CUSTOM_PREFIX_ACCESS_TOKEN", " OTEL_ , CUSTOM_PREFIX_ ,, VENDOR_ "), "CUSTOM_PREFIX_ACCESS_TOKEN should match CUSTOM_PREFIX_", ); try test_util.expectWithMessage( hasAllowedPrefix("VENDOR_TRACE_MODE", " OTEL_ , CUSTOM_PREFIX_ ,, VENDOR_ "), "VENDOR_TRACE_MODE should match VENDOR_", ); try test_util.expectWithMessage( !hasAllowedPrefix("PATH", " OTEL_ , CUSTOM_PREFIX_ ,, VENDOR_ "), "PATH should not match any configured prefix", ); } test "readAllAgentsEnvFile: stores only variables matching allowed prefixes" { // Derive the sample keys from the build's configured allowed prefixes so this test // stays correct regardless of the -Dallowed-env-var-prefixes value it was built with. const allowed_key = comptime blk: { var prefixes = std.mem.splitScalar(u8, build_options.allowed_env_var_prefixes, ','); while (prefixes.next()) |raw_prefix| { const prefix = std.mem.trim(u8, raw_prefix, " \t\r\n"); if (prefix.len == 0) continue; break :blk prefix ++ "TEST_ALLOWED_KEY"; } @compileError("build has no non-empty allowed_env_var_prefixes"); }; const disallowed_key = comptime blk: { // Pick a sentinel key that provably does not match any configured prefix. const candidates = [_][]const u8{ "ZZZ_TEST_DISALLOWED_KEY", "QQQ_TEST_DISALLOWED_KEY", "JJJ_TEST_DISALLOWED_KEY", }; for (candidates) |c| { if (!hasAllowedPrefix(c, build_options.allowed_env_var_prefixes)) { break :blk c; } } @compileError("could not find a sentinel disallowed key for this build's allowed_env_var_prefixes"); }; const allocator = testing.allocator; var tmp_dir = testing.tmpDir(.{}); defer tmp_dir.cleanup(); { const file = try tmp_dir.dir.createFile("default_env.conf", .{}); defer file.close(); try file.writeAll(allowed_key ++ "=allowed-value\n" ++ disallowed_key ++ "=disallowed-value\n"); } const absolute_path_to_env_file = try tmp_dir.dir.realpathAlloc(allocator, "default_env.conf"); defer allocator.free(absolute_path_to_env_file); var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); var configuration = try createDefaultConfiguration(arena_allocator); readAllAgentsEnvFile(arena_allocator, absolute_path_to_env_file, &configuration); try testing.expectEqual(1, configuration.all_auto_instrumentation_agents_env_vars.count()); try testing.expectEqualStrings( "allowed-value", configuration.all_auto_instrumentation_agents_env_vars.get(allowed_key).?, ); try test_util.expectWithMessage( configuration.all_auto_instrumentation_agents_env_vars.get(disallowed_key) == null, "key with disallowed prefix should not be loaded", ); } /// Unquotes a configuration value using shell-style quoting rules for the /// well-formed cases, with a deliberate divergence from bash for malformed /// input (see the "not shell-standard" note below). /// /// - Values wrapped in matching `"..."`: the outer quotes are stripped and backslash /// escapes are processed only for `\"`, `\\`, `\$`, and `` \` `` — the backslash /// is consumed and the next character is kept. Any other `\` is preserved /// verbatim (both the backslash and `c`), which matches bash's behavior inside /// double quotes: `"\n"`, `"\t"`, `"\z"` all stay literal two-character sequences. /// - Values wrapped in matching `'...'`: the outer quotes are stripped and no /// escape processing is performed. This matches bash single-quote semantics /// (a `'` cannot appear inside single quotes at all). /// - Values without matching surrounding quotes are returned unchanged. THIS IS /// NOT SHELL-STANDARD: bash would either report a syntax error or continue /// reading additional lines looking for a matching closing quote. This parser /// is line-oriented and does neither. Instead, parseLine emits a warning via /// `hasUnbalancedQuotes` so the user can spot the typo, and passes the value /// through literally rather than silently dropping the line. /// /// May allocate on the arena when escape processing shortens the value. fn unquoteValue(arena_allocator: std.mem.Allocator, value: []const u8) std.mem.Allocator.Error![]u8 { if (value.len >= 2) { const first = value[0]; const last = value[value.len - 1]; if (first == last and (first == '"' or first == '\'')) { const inner = value[1 .. value.len - 1]; if (first == '\'') { return arena_allocator.dupe(u8, inner); } // Double-quoted: fast path when there are no backslashes to process. if (std.mem.indexOfScalar(u8, inner, '\\') == null) { return arena_allocator.dupe(u8, inner); } // Two-pass: compute the final length first so the returned slice // length matches the underlying allocation exactly. That keeps // callers using a GPA (e.g. tests) able to free the value cleanly. var final_len: usize = 0; var i: usize = 0; while (i < inner.len) : (i += 1) { if (isEscapedByBackslash(inner, i)) { i += 1; } final_len += 1; } const buf = try arena_allocator.alloc(u8, final_len); var w: usize = 0; i = 0; while (i < inner.len) : (i += 1) { if (isEscapedByBackslash(inner, i)) { buf[w] = inner[i + 1]; w += 1; i += 1; continue; } buf[w] = inner[i]; w += 1; } return buf; } } return arena_allocator.dupe(u8, value); } fn isEscapedByBackslash(inner: []const u8, i: usize) bool { if (inner[i] != '\\' or i + 1 >= inner.len) return false; const next = inner[i + 1]; return next == '"' or next == '\\' or next == '$' or next == '`'; } /// Reports whether a raw value (as read from a config line, after whitespace /// trimming and before unquoteValue) contains quote characters that do not /// form a well-formed pair. Intended to catch user typos like `KEY="value`, /// `KEY=value"`, or `KEY=va"lue`. /// /// This exists precisely because the parser diverges from shell semantics /// for malformed input: a shell would refuse to parse `KEY="value` (or keep /// reading until it finds a closing quote on a later line), but this parser /// is line-oriented and accepts the value literally. The warning surfaces /// that divergence so users can spot the mistake instead of only finding /// out when the downstream process rejects the injected env var. fn hasUnbalancedQuotes(value: []const u8) bool { if (value.len >= 2) { const first = value[0]; const last = value[value.len - 1]; if (first == last and (first == '"' or first == '\'')) { const inner = value[1 .. value.len - 1]; if (first == '"') { // Inside a double-quoted value, any unescaped " is stray. var i: usize = 0; while (i < inner.len) : (i += 1) { if (isEscapedByBackslash(inner, i)) { i += 1; continue; } if (inner[i] == '"') return true; } return false; } // Inside a single-quoted value, any ' is stray (bash cannot // escape ' inside '...'). return std.mem.indexOfScalar(u8, inner, '\'') != null; } } // No matching outer pair: any quote character in the value is stray. return std.mem.indexOfAny(u8, value, "\"'") != null; } test "hasUnbalancedQuotes: balanced values" { try testing.expectEqual(false, hasUnbalancedQuotes("")); try testing.expectEqual(false, hasUnbalancedQuotes("plain")); try testing.expectEqual(false, hasUnbalancedQuotes("\"quoted\"")); try testing.expectEqual(false, hasUnbalancedQuotes("'quoted'")); try testing.expectEqual(false, hasUnbalancedQuotes("\"\"")); try testing.expectEqual(false, hasUnbalancedQuotes("''")); // Escaped double quotes inside a double-quoted value are balanced. try testing.expectEqual(false, hasUnbalancedQuotes("\"he said \\\"hi\\\"\"")); // Escaped backslash before an unrelated char is balanced. try testing.expectEqual(false, hasUnbalancedQuotes("\"a\\\\b\"")); // Single quotes inside a double-quoted value are fine. try testing.expectEqual(false, hasUnbalancedQuotes("\"don't\"")); // Double quotes inside a single-quoted value are fine. try testing.expectEqual(false, hasUnbalancedQuotes("'he said \"hi\"'")); } test "hasUnbalancedQuotes: stray or unmatched quotes" { // Unmatched leading double quote. try testing.expectEqual(true, hasUnbalancedQuotes("\"value")); // Unmatched trailing double quote. try testing.expectEqual(true, hasUnbalancedQuotes("value\"")); // Interior double quote in an otherwise unquoted value. try testing.expectEqual(true, hasUnbalancedQuotes("va\"lue")); // Unmatched leading single quote (typical: apostrophe in unquoted word). try testing.expectEqual(true, hasUnbalancedQuotes("don't")); // Mismatched quote types on the outside. try testing.expectEqual(true, hasUnbalancedQuotes("\"value'")); try testing.expectEqual(true, hasUnbalancedQuotes("'value\"")); // Single stray character. try testing.expectEqual(true, hasUnbalancedQuotes("\"")); try testing.expectEqual(true, hasUnbalancedQuotes("'")); // Double-quoted value with unescaped " inside. try testing.expectEqual(true, hasUnbalancedQuotes("\"\"nested\"\"")); // Single-quoted value with a ' inside (bash cannot escape ' in '...'). try testing.expectEqual(true, hasUnbalancedQuotes("'don\\'t'")); } /// Parses a single line from a configuration file. /// Returns a key-value pair if the line is a valid key-value pair, and null for empty /// lines, comments and invalid lines. fn parseLine(arena_allocator: std.mem.Allocator, line: []u8, cfg_file_path: []const u8) ?struct { key: []const u8, value: []u8, } { var l = line; if (std.mem.indexOfScalar(u8, l, '#')) |commentStartIdx| { // strip end-of-line comment (might be the whole line if the line starts with #) l = l[0..commentStartIdx]; } const trimmed = std.mem.trim(u8, l, " \t\r\n"); if (trimmed.len == 0) { // ignore empty lines or lines that only contain whitespace return null; } if (std.mem.indexOfScalar(u8, trimmed, '=')) |equalsIdx| { const key_trimmed = std.mem.trim(u8, trimmed[0..equalsIdx], " \t\r\n"); const key = std.fmt.allocPrint(arena_allocator, "{s}", .{key_trimmed}) catch |err| { print.printError("error in allocPrint while allocating key from file {s}: {}", .{ cfg_file_path, err }); return null; }; const value_trimmed = std.mem.trim(u8, trimmed[equalsIdx + 1 ..], " \t\r\n"); if (hasUnbalancedQuotes(value_trimmed)) { // Divergence from shell semantics: bash would refuse to parse this // (or keep reading additional lines looking for a matching closing // quote). This parser is line-oriented, so it uses the value as-is // and warns instead of failing. print.printWarn( "value for key \"{s}\" in {s} contains unbalanced or unquoted quote characters and will be used literally: {s}", .{ key, cfg_file_path, value_trimmed }, ); } const value = unquoteValue(arena_allocator, value_trimmed) catch |err| { print.printError("error while allocating value from file {s}: {}", .{ cfg_file_path, err }); return null; }; return .{ .key = key, .value = value, }; } else { // ignore malformed lines print.printError("cannot parse line in {s}: \"{s}\"", .{ cfg_file_path, line }); return null; } } test "parseLine: empty line" { const allocator = testing.allocator; const result = parseLine( allocator, "", "/path/to/configuration", ); try test_util.expectWithMessage(result == null, "parseLine(\"\") returns null"); } test "parseLine: whitespace only" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, " \t ", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result == null, "parseLine(whitespace) returns null"); } test "parseLine: full line comment" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "# this is a comment", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result == null, "parseLine(full line comment) returns null"); } test "parseLine: end of line comment" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=value # comment", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(end-of-line comment) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("value", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: valid key-value pair for unknown key" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=value", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(key-value pair/unknown key) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("value", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: valid key-value pair for known key" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "jvm_auto_instrumentation_agent_path=/custom/path/to/jvm/agent", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(key-value pair/known key) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("jvm_auto_instrumentation_agent_path", kv.key); try testing.expectEqualStrings("/custom/path/to/jvm/agent", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: valid key-value pair for known key with end-of-line comment" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "jvm_auto_instrumentation_agent_path=/custom/path/to/jvm/agent # comment", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(key-value pair/known key/eol comment) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("jvm_auto_instrumentation_agent_path", kv.key); try testing.expectEqualStrings("/custom/path/to/jvm/agent", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: valid key-value pair with whitespace" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, " jvm_auto_instrumentation_agent_path \t = /custom/path/to/jvm/agent ", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(key-value pair/known key/whitespace) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("jvm_auto_instrumentation_agent_path", kv.key); try testing.expectEqualStrings("/custom/path/to/jvm/agent", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: multiple equals characters" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "jvm_auto_instrumentation_agent_path=/path/with/=/character/===", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(key-value pair/known key/multiple equals) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("jvm_auto_instrumentation_agent_path", kv.key); try testing.expectEqualStrings("/path/with/=/character/===", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: invalid line (no = character)" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "this line is invalid", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result == null, "parseLine(invalid line) returns null"); } test "parseLine: invalid line (line too long)" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "this line is invalid", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result == null, "parseLine(invalid line) returns null"); } test "parseLine: strips surrounding double quotes from value" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "OTEL_EXPORTER_OTLP_PROTOCOL=\"http/protobuf\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(double-quoted value) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("OTEL_EXPORTER_OTLP_PROTOCOL", kv.key); try testing.expectEqualStrings("http/protobuf", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: strips surrounding single quotes from value" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "OTEL_EXPORTER_OTLP_ENDPOINT='https://example.com'", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(single-quoted value) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("OTEL_EXPORTER_OTLP_ENDPOINT", kv.key); try testing.expectEqualStrings("https://example.com", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: strips surrounding quotes around whitespace" { const allocator = testing.allocator; // Whitespace inside the quotes must be preserved, only whitespace outside the // quotes is trimmed. const line = try std.fmt.allocPrint(allocator, " key = \" value with spaces \" ", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(quoted value with padding) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings(" value with spaces ", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: preserves inner quotes and only strips one surrounding pair" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"\"nested\"\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(nested quotes) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("\"nested\"", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: preserves unbalanced quotes" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"unbalanced", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(unbalanced quote) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("\"unbalanced", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: preserves mismatched quote characters" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"mismatched'", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(mismatched quote types) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("\"mismatched'", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: empty double-quoted value becomes empty string" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(empty quoted value) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: single quote character is preserved" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(single quote char) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("\"", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: quoted value with commas is preserved verbatim" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "OTEL_EXPORTER_OTLP_HEADERS=\"Authorization=Bearer%20abc,Custom-Header=value\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(quoted headers) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("OTEL_EXPORTER_OTLP_HEADERS", kv.key); try testing.expectEqualStrings("Authorization=Bearer%20abc,Custom-Header=value", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: empty single-quoted value" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=''", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(empty single-quoted) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: unbalanced single quote is preserved" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key='unbalanced", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(unbalanced single quote) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("'unbalanced", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } // --- Double-quoted escape processing --- test "parseLine: double quote escapes an inner double quote" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"a\\\"b\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(\\\") returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a\"b", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: double quote escapes a backslash" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"a\\\\b\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(\\\\) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a\\b", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: double quote escapes a dollar sign" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"a\\$b\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(\\$) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a$b", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: double quote escapes a backtick" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"a\\`b\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(\\`) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a`b", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: double-quoted \\n is a literal two-character sequence" { // Matches bash: \n inside double quotes is NOT a newline. const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"a\\nb\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(\\n literal) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a\\nb", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: double-quoted \\t is a literal two-character sequence" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"a\\tb\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(\\t literal) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a\\tb", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: double-quoted unknown escape stays literal" { // Bash: \ for c not in {$, `, \", \\, newline} keeps both chars. const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"a\\zb\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(\\z literal) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a\\zb", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: escaped quote as sole content produces a single quote character" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"\\\"\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(escaped quote only) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("\"", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: mixed escapes \\\\ then \\\"" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key=\"\\\\\\\"\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(\\\\\\\") returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("\\\"", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } // --- Single-quoted values: no escape processing --- test "parseLine: single-quoted backslash-quote is literal" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key='a\\\"b'", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(single-quoted \\\") returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a\\\"b", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: single-quoted double backslash is literal" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key='a\\\\b'", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(single-quoted \\\\) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a\\\\b", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: single-quoted dollar sign is literal" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key='a$b'", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(single-quoted $) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a$b", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: single-quoted backslash-n is literal" { const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "key='a\\nb'", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(single-quoted \\n literal) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a\\nb", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } // --- Trailing-backslash edge cases (bash would treat these as unmatched // quotes / line continuation; a line-oriented parser cannot, so we keep // the backslash literal). --- test "parseLine: unquoted value with interior double quote is preserved literally" { // This should also emit a warning, but the parser still returns the value // rather than dropping it — losing a line silently would be worse than // passing through a possibly-mistyped value. const allocator = testing.allocator; const line = try std.fmt.allocPrint(allocator, "a=b\"c", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(interior stray quote) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("a", kv.key); try testing.expectEqualStrings("b\"c", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } test "parseLine: trailing backslash inside double quotes is literal" { const allocator = testing.allocator; // The inner content after stripping outer quotes is `a\`. The trailing // backslash has no following character to escape, so it stays literal. const line = try std.fmt.allocPrint(allocator, "key=\"a\\\"", .{}); defer allocator.free(line); const result = parseLine( allocator, line, "/path/to/configuration", ); try test_util.expectWithMessage(result != null, "parseLine(trailing backslash) returns key-value"); if (result) |kv| { try testing.expectEqualStrings("key", kv.key); try testing.expectEqualStrings("a\\", kv.value); allocator.free(kv.key); allocator.free(kv.value); } } fn readConfigurationFromEnvironment(arena_allocator: std.mem.Allocator, configuration: *InjectorConfiguration, getenv_fn: proc_self_environ_parser.GetenvFn) void { if (getenv_fn(arena_allocator, dotnet_agent_path_prefix_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); const dotnet_value = std.fmt.allocPrint(arena_allocator, "{s}", .{trimmed_value}) catch |err| { print.printError("Cannot allocate memory to read the injector configuration from the environment: {}", .{err}); return; }; configuration.dotnet_auto_instrumentation_agent_path_prefix = dotnet_value; } if (getenv_fn(arena_allocator, dotnet_minimum_dotnet_major_version_env_var)) |value| { applyDotnetMinimumDotnetMajorVersionValue(value, dotnet_minimum_dotnet_major_version_env_var, configuration); } if (getenv_fn(arena_allocator, jvm_agent_path_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); const jvm_value = std.fmt.allocPrint(arena_allocator, "{s}", .{trimmed_value}) catch |err| { print.printError("Cannot allocate memory to read the injector configuration from the environment: {}", .{err}); return; }; configuration.jvm_auto_instrumentation_agent_path = jvm_value; } if (getenv_fn(arena_allocator, nodejs_agent_path_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); const nodejs_value = std.fmt.allocPrint(arena_allocator, "{s}", .{trimmed_value}) catch |err| { print.printError("Cannot allocate memory to read the injector configuration from the environment: {}", .{err}); return; }; configuration.nodejs_auto_instrumentation_agent_path = nodejs_value; } if (getenv_fn(arena_allocator, python_agent_path_prefix_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); const python_value = std.fmt.allocPrint(arena_allocator, "{s}", .{trimmed_value}) catch |err| { print.printError("Cannot allocate memory to read the injector configuration from the environment: {}", .{err}); return; }; configuration.python_auto_instrumentation_agent_path_prefix = python_value; } if (getenv_fn(arena_allocator, ruby_agent_path_prefix_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); const ruby_value = std.fmt.allocPrint(arena_allocator, "{s}", .{trimmed_value}) catch |err| { print.printError("Cannot allocate memory to read the injector configuration from the environment: {}", .{err}); return; }; configuration.ruby_auto_instrumentation_agent_path_prefix = ruby_value; } if (getenv_fn(arena_allocator, auto_instrumentation_disabled_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); applyAutoInstrumentationDisabledValue(trimmed_value, auto_instrumentation_disabled_env_var, configuration); } if (getenv_fn(arena_allocator, include_paths_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); const include_paths_value = std.fmt.allocPrint(arena_allocator, "{s}", .{trimmed_value}) catch |err| { print.printError("Cannot allocate memory to read the injector configuration from the environment: {}", .{err}); return; }; configuration.include_paths = patterns_util.splitByComma(arena_allocator, include_paths_value) catch |err| { print.printError("error parsing include_paths value from the environment {s}: {}", .{ include_paths_value, err }); return; }; } if (getenv_fn(arena_allocator, exclude_paths_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); const exclude_paths_value = std.fmt.allocPrint(arena_allocator, "{s}", .{trimmed_value}) catch |err| { print.printError("Cannot allocate memory to read the injector configuration from the environment: {}", .{err}); return; }; configuration.exclude_paths = patterns_util.splitByComma(arena_allocator, exclude_paths_value) catch |err| { print.printError("error parsing exclude_paths value from the environment {s}: {}", .{ exclude_paths_value, err }); return; }; } if (getenv_fn(arena_allocator, include_args_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); const include_args_value = std.fmt.allocPrint(arena_allocator, "{s}", .{trimmed_value}) catch |err| { print.printError("Cannot allocate memory to read the injector configuration from the environment: {}", .{err}); return; }; configuration.include_args = patterns_util.splitByComma(arena_allocator, include_args_value) catch |err| { print.printError("error parsing include_arguments value from the environment {s}: {}", .{ include_args_value, err }); return; }; } if (getenv_fn(arena_allocator, exclude_args_env_var)) |value| { const trimmed_value = std.mem.trim(u8, value, " \t\r\n"); const exclude_args_value = std.fmt.allocPrint(arena_allocator, "{s}", .{trimmed_value}) catch |err| { print.printError("Cannot allocate memory to read the injector configuration from the environment: {}", .{err}); return; }; configuration.exclude_args = patterns_util.splitByComma(arena_allocator, exclude_args_value) catch |err| { print.printError("error parsing exclude_arguments value from the environment {s}: {}", .{ exclude_args_value, err }); return; }; } } test "readConfigurationFromEnvironment: empty environment values" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.clearStdCEnviron(); defer test_util.resetStdCEnviron(original_environ); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFromEnvironment(arena_allocator, &configuration, proc_self_environ_parser.posixGetenv); try testing.expectEqualStrings( default_dotnet_auto_instrumentation_agent_path_prefix, configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_jvm_auto_instrumentation_agent_path, configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_nodejs_auto_instrumentation_agent_path, configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_python_auto_instrumentation_agent_path, configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_all_auto_instrumentation_agents_env_path, configuration.all_auto_instrumentation_agents_env_path, ); try testing.expectEqual(0, configuration.include_paths.len); try testing.expectEqual(0, configuration.exclude_paths.len); try testing.expectEqual(0, configuration.include_args.len); try testing.expectEqual(0, configuration.exclude_args.len); } test "readConfigurationFromEnvironment: all values" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.setStdCEnviron(&[9][]const u8{ "DOTNET_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/env/var/dotnet", "DOTNET_AUTO_INSTRUMENTATION_MINIMUM_DOTNET_MAJOR_VERSION=6", "JVM_AUTO_INSTRUMENTATION_AGENT_PATH=/path/from/env/var/jvm", "NODEJS_AUTO_INSTRUMENTATION_AGENT_PATH=/path/from/env/var/nodejs", "PYTHON_AUTO_INSTRUMENTATION_AGENT_PATH_PREFIX=/path/from/env/var/python", "OTEL_INJECTOR_INCLUDE_PATHS=/path/from/env/var/include1,/path/from/env/var/include2", "OTEL_INJECTOR_EXCLUDE_PATHS=/path/from/env/var/exclude1,/path/from/env/var/exclude2", "OTEL_INJECTOR_INCLUDE_WITH_ARGUMENTS=--from-env-var-include1,--from-env-var-include2", "OTEL_INJECTOR_EXCLUDE_WITH_ARGUMENTS=--from-env-var-exclude1,--from-env-var-exclude2", }); defer test_util.resetStdCEnviron(original_environ); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFromEnvironment(arena_allocator, &configuration, proc_self_environ_parser.posixGetenv); try testing.expectEqualStrings( "/path/from/env/var/dotnet", configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqual(@as(u32, 6), configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version); try testing.expectEqualStrings( "/path/from/env/var/jvm", configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/path/from/env/var/nodejs", configuration.nodejs_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( "/path/from/env/var/python", configuration.python_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_all_auto_instrumentation_agents_env_path, configuration.all_auto_instrumentation_agents_env_path, ); try testing.expectEqual(2, configuration.include_paths.len); try testing.expectEqualStrings("/path/from/env/var/include1", configuration.include_paths[0]); try testing.expectEqualStrings("/path/from/env/var/include2", configuration.include_paths[1]); try testing.expectEqual(2, configuration.exclude_paths.len); try testing.expectEqualStrings("/path/from/env/var/exclude1", configuration.exclude_paths[0]); try testing.expectEqualStrings("/path/from/env/var/exclude2", configuration.exclude_paths[1]); try testing.expectEqual(2, configuration.include_args.len); try testing.expectEqualStrings("--from-env-var-include1", configuration.include_args[0]); try testing.expectEqualStrings("--from-env-var-include2", configuration.include_args[1]); try testing.expectEqual(2, configuration.exclude_args.len); try testing.expectEqualStrings("--from-env-var-exclude1", configuration.exclude_args[0]); try testing.expectEqualStrings("--from-env-var-exclude2", configuration.exclude_args[1]); } test "readConfigurationFromEnvironment: OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=* disables all runtimes" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=*", }); defer test_util.resetStdCEnviron(original_environ); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFromEnvironment(arena_allocator, &configuration, proc_self_environ_parser.posixGetenv); try test_util.expectWithMessage(configuration.dotnet_instrumentation_disabled, "configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(configuration.jvm_instrumentation_disabled, "configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(configuration.nodejs_instrumentation_disabled, "configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(configuration.python_instrumentation_disabled, "configuration.python_instrumentation_disabled"); } test "readConfigurationFromEnvironment: OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED with specific runtimes" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=nodejs,python", }); defer test_util.resetStdCEnviron(original_environ); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFromEnvironment(arena_allocator, &configuration, proc_self_environ_parser.posixGetenv); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(configuration.nodejs_instrumentation_disabled, "configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(configuration.python_instrumentation_disabled, "configuration.python_instrumentation_disabled"); } test "readConfigurationFromEnvironment: OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED with all runtimes individually" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=dotnet,jvm,nodejs,python", }); defer test_util.resetStdCEnviron(original_environ); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFromEnvironment(arena_allocator, &configuration, proc_self_environ_parser.posixGetenv); try test_util.expectWithMessage(configuration.dotnet_instrumentation_disabled, "configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(configuration.jvm_instrumentation_disabled, "configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(configuration.nodejs_instrumentation_disabled, "configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(configuration.python_instrumentation_disabled, "configuration.python_instrumentation_disabled"); } test "readConfigurationFromEnvironment: OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED with unknown runtime is ignored" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{ "OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED=nodejs,unknown,nodejs", }); defer test_util.resetStdCEnviron(original_environ); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFromEnvironment(arena_allocator, &configuration, proc_self_environ_parser.posixGetenv); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(configuration.nodejs_instrumentation_disabled, "configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationFromEnvironment: if OTEL_INJECTOR_AUTO_INSTRUMENTATION_DISABLED is not set, all runtimes are enabled" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.clearStdCEnviron(); defer test_util.resetStdCEnviron(original_environ); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationFromEnvironment(arena_allocator, &configuration, proc_self_environ_parser.posixGetenv); try test_util.expectWithMessage(!configuration.dotnet_instrumentation_disabled, "!configuration.dotnet_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.jvm_instrumentation_disabled, "!configuration.jvm_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.nodejs_instrumentation_disabled, "!configuration.nodejs_instrumentation_disabled"); try test_util.expectWithMessage(!configuration.python_instrumentation_disabled, "!configuration.python_instrumentation_disabled"); } test "readConfigurationDirectory: directory does not exist" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{ "OTEL_INJECTOR_CONFIG_DIR=/does/not/exist", }); defer test_util.resetStdCEnviron(original_environ); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationDirectory(arena_allocator, &configuration); // Configuration should remain at defaults when the directory does not exist. try testing.expectEqualStrings( default_dotnet_auto_instrumentation_agent_path_prefix, configuration.dotnet_auto_instrumentation_agent_path_prefix, ); try testing.expectEqualStrings( default_jvm_auto_instrumentation_agent_path, configuration.jvm_auto_instrumentation_agent_path, ); try testing.expectEqualStrings( default_nodejs_auto_instrumentation_agent_path, configuration.nodejs_auto_instrumentation_agent_path, ); } test "readConfigurationDirectory: reads .conf files in alphabetical order and ignores non-.conf files" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{ "OTEL_INJECTOR_CONFIG_DIR=unit-test-assets/config/conf.d", }); defer test_util.resetStdCEnviron(original_environ); var configuration = try createDefaultConfiguration(arena_allocator); readConfigurationDirectory(arena_allocator, &configuration); // 01-java.conf sets the JVM path try testing.expectEqualStrings( "/conf.d/path/to/jvm/javaagent.jar", configuration.jvm_auto_instrumentation_agent_path, ); // 02-nodejs.conf sets the Node.js path try testing.expectEqualStrings( "/conf.d/path/to/nodejs/register.js", configuration.nodejs_auto_instrumentation_agent_path, ); // not-a-conf-file.txt should be ignored, so the dotnet path should remain at the default. try testing.expectEqualStrings( default_dotnet_auto_instrumentation_agent_path_prefix, configuration.dotnet_auto_instrumentation_agent_path_prefix, ); } test "readConfigurationDirectory: conf.d files override values from the main configuration file" { const allocator = testing.allocator; var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{ "OTEL_INJECTOR_CONFIG_DIR=unit-test-assets/config/conf.d", }); defer test_util.resetStdCEnviron(original_environ); // First set a JVM path from the main config, then apply conf.d which should override it. var configuration = try createDefaultConfiguration(arena_allocator); configuration.jvm_auto_instrumentation_agent_path = @constCast("/original/jvm/path.jar"); readConfigurationDirectory(arena_allocator, &configuration); try testing.expectEqualStrings( "/conf.d/path/to/jvm/javaagent.jar", configuration.jvm_auto_instrumentation_agent_path, ); } fn copyMap(allocator: std.mem.Allocator, source: std.StringHashMap([]u8)) !std.StringHashMap([]u8) { var target = std.StringHashMap([]u8).init(allocator); try target.ensureTotalCapacity(source.count()); var it = source.iterator(); while (it.next()) |entry| { const key = try std.fmt.allocPrint(allocator, "{s}", .{entry.key_ptr.*}); const value = try std.fmt.allocPrint(allocator, "{s}", .{entry.value_ptr.*}); try target.put(key, value); } return target; } fn copyStringArray(allocator: std.mem.Allocator, source: [][]const u8) std.mem.Allocator.Error![][]const u8 { const target = try allocator.alloc([]const u8, source.len); for (source, 0..) |p, i| { target[i] = try std.fmt.allocPrint(allocator, "{s}", .{p}); } return target; } fn deinitStringArray(allocator: std.mem.Allocator, array: [][]const u8) void { for (array) |item| { allocator.free(item); } allocator.free(array); } open-telemetry-opentelemetry-injector-ea9dca1/src/dotnet.zig000066400000000000000000001225371524057645600245700ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const builtin = @import("builtin"); const std = @import("std"); const args_parser = @import("args_parser.zig"); const config = @import("config.zig"); const libc = @import("libc.zig"); const print = @import("print.zig"); const test_util = @import("test_util.zig"); const types = @import("types.zig"); const testing = std.testing; pub const DotnetValues = struct { coreclr_enable_profiling: [:0]const u8, coreclr_profiler: [:0]const u8, coreclr_profiler_path: [:0]u8, additional_deps: ?[:0]u8, shared_store: ?[:0]u8, startup_hooks: [:0]u8, otel_auto_home: [:0]u8, pub fn freeAll(self: DotnetValues, allocator: std.mem.Allocator) void { allocator.free(self.coreclr_profiler_path); if (self.additional_deps) |ad| allocator.free(ad); if (self.shared_store) |ss| allocator.free(ss); allocator.free(self.startup_hooks); allocator.free(self.otel_auto_home); } }; const coreclr_enable_profiling_value = "1"; // See https://opentelemetry.io/docs/zero-code/dotnet/configuration/#net-clr-profiler. const coreclr_profiler_value = "{918728DD-259F-4A6A-AC2B-B85E1B658318}"; const dotnet_host_name = "dotnet"; const max_dotnet_metadata_file_size = 1024 * 1024; const opentelemetry_dependency_prefix = "OpenTelemetry"; pub const CachedDotnetValues = struct { values: ?DotnetValues, done: bool, }; const DotnetError = error{ UnknownLibCFlavor, OutOfMemory, }; const DotnetMetadataPaths = struct { deps_path: []u8, runtimeconfig_path: []u8, fn freeAll(self: DotnetMetadataPaths, allocator: std.mem.Allocator) void { allocator.free(self.deps_path); allocator.free(self.runtimeconfig_path); } }; pub const coreclr_enable_profiling_env_var_name = "CORECLR_ENABLE_PROFILING"; pub const coreclr_profiler_env_var_name = "CORECLR_PROFILER"; pub const coreclr_profiler_path_env_var_name = "CORECLR_PROFILER_PATH"; pub const dotnet_additional_deps_env_var_name = "DOTNET_ADDITIONAL_DEPS"; pub const dotnet_shared_store_env_var_name = "DOTNET_SHARED_STORE"; pub const dotnet_startup_hooks_env_var_name = "DOTNET_STARTUP_HOOKS"; pub const otel_dotnet_auto_home_env_var_name = "OTEL_DOTNET_AUTO_HOME"; const conflicting_dotnet_env_var_names = [_][:0]const u8{ coreclr_enable_profiling_env_var_name, coreclr_profiler_env_var_name, coreclr_profiler_path_env_var_name, dotnet_additional_deps_env_var_name, dotnet_shared_store_env_var_name, dotnet_startup_hooks_env_var_name, }; // We usually do not cache any values for environment variable modifications (i.e. we do not cache the modified // NODE_OPTIONS value or the modified OTEL_RESOURCE_ATTRIBUTES) because we are only called once, on startup via // root.zig#initEnviron. For .NET we deviate from this pattern a bit - we calculate all .NET-related environment // variables once based on CPU architecture and libc flavor, and then call getDotnetValues multiple times from // root.zig#initEnviron for eaech .NET-related env var. This is simply because .NET requires multiple environment // variables to be set. var cached_dotnet_values = CachedDotnetValues{ .values = null, .done = false, }; var libc_info: ?types.LibCInfo = null; pub fn setLibcInfo(info: types.LibCInfo) void { libc_info = info; } /// Returns the values for .NET-profiler related environment variables. /// /// The caller is responsible for freeing the returned strings (unless the results are passed on to setenv and need to /// stay in memory). pub fn getDotnetValues( gpa: std.mem.Allocator, configuration: config.InjectorConfiguration, ) ?DotnetValues { return doGetDotnetValues( gpa, configuration.dotnet_auto_instrumentation_agent_path_prefix, configuration.dotnet_instrumentation_disabled, configuration.dotnet_auto_instrumentation_minimum_dotnet_major_version, ); } fn doGetDotnetValues( gpa: std.mem.Allocator, dotnet_path_prefix: []u8, dotnet_instrumentation_disabled: bool, minimum_dotnet_major_version: u32, ) ?DotnetValues { if (dotnet_instrumentation_disabled or dotnet_path_prefix.len == 0) { print.printInfo("Skipping the injection of the .NET OpenTelemetry instrumentation because it has been explicitly disabled.", .{}); return null; } if (libc_info == null) { print.printError("invariant violated: libc info has not been set prior to calling getDotnetValues().", .{}); return null; } if (libc_info.?.flavor == .UNKNOWN) { print.printError("Cannot determine libc flavor", .{}); return null; } if (cached_dotnet_values.done) { return cached_dotnet_values.values; } if (!shouldInjectDotnet(gpa, minimum_dotnet_major_version)) { cached_dotnet_values = .{ .values = null, .done = true, }; return null; } if (libc_info) |info| { var dotnet_values = determineDotnetValues( gpa, dotnet_path_prefix, info.flavor, builtin.cpu.arch, ) catch |err| { print.printError("Cannot determine .NET environment variables: {}", .{err}); cached_dotnet_values = .{ .values = null, // do not try to determine the .NET values again .done = true, }; return null; }; const paths_to_check = [_][:0]const u8{ dotnet_values.coreclr_profiler_path, dotnet_values.otel_auto_home, dotnet_values.startup_hooks, }; for (paths_to_check) |p| { std.fs.cwd().access(p, .{}) catch |err| { print.printError("Skipping injection of the .NET OpenTelemetry instrumentation because of an issue accessing {s}: {}", .{ p, err }); cached_dotnet_values = .{ .values = null, // do not try to determine the .NET values again .done = true, }; // free strings allocated in determineDotnetValues dotnet_values.freeAll(gpa); return null; }; } // DOTNET_ADDITIONAL_DEPS is optional: only add the env var if the additional deps directory exists, do not skip .NET // injection if it does not exist. if (dotnet_values.additional_deps) |additional_deps_path| { std.fs.cwd().access(additional_deps_path, .{}) catch |err| { print.printDebug("Not setting DOTNET_ADDITIONAL_DEPS because of an issue accessing {s}: {}", .{ additional_deps_path, err }); gpa.free(additional_deps_path); dotnet_values.additional_deps = null; }; } // DOTNET_SHARED_STORE is optional: only add the env var if the additional deps directory exists, do not skip .NET // injection if it does not exist. if (dotnet_values.shared_store) |shared_store_path| { std.fs.cwd().access(shared_store_path, .{}) catch |err| { print.printDebug("Not setting DOTNET_SHARED_STORE because of an issue accessing {s}: {}", .{ shared_store_path, err }); gpa.free(shared_store_path); dotnet_values.shared_store = null; }; } cached_dotnet_values = .{ .values = dotnet_values, .done = true, }; return dotnet_values; } unreachable; } fn shouldInjectDotnet(allocator: std.mem.Allocator, minimum_dotnet_major_version: u32) bool { if (findConflictingPreExistingDotnetEnvVar()) |conflicting_env_var_name| { print.printInfo( "Skipping the injection of the .NET OpenTelemetry instrumentation because {s} is already set.", .{conflicting_env_var_name}, ); return false; } const cmdline_args = args_parser.cmdLineForPID(allocator) catch |err| { print.printDebug("Proceeding with the injection of the .NET OpenTelemetry instrumentation. Could not read the process command line: {}", .{err}); return true; }; defer { for (cmdline_args) |arg| allocator.free(arg); allocator.free(cmdline_args); } const self_exe_path = std.fs.selfExePathAlloc(allocator) catch |err| { print.printDebug("Proceeding with the injection of the .NET OpenTelemetry instrumentation. Could not resolve the executable path: {}", .{err}); return true; }; defer allocator.free(self_exe_path); const maybe_app_path = resolveManagedApplicationPath(allocator, cmdline_args, self_exe_path) catch |err| { print.printDebug("Proceeding with the injection of the .NET OpenTelemetry instrumentation. Could not determine the managed application path: {}", .{err}); return true; }; const app_path = maybe_app_path orelse { print.printDebug("Proceeding with the injection of the .NET OpenTelemetry instrumentation. The process does not look like a recognized .NET application startup.", .{}); return true; }; defer allocator.free(app_path); const metadata_paths = createDotnetMetadataPaths(allocator, app_path) catch |err| { print.printDebug("Proceeding with the injection of the .NET OpenTelemetry instrumentation. Could not determine the application metadata paths: {}", .{err}); return true; }; defer metadata_paths.freeAll(allocator); const runtimeconfig_content = readSmallTextFileAlloc(allocator, metadata_paths.runtimeconfig_path) catch |err| { print.printDebug("Proceeding with the injection of the .NET OpenTelemetry instrumentation. Could not read {s}: {}", .{ metadata_paths.runtimeconfig_path, err }); return true; }; defer allocator.free(runtimeconfig_content); const runtimeconfig_targets_supported_dotnet = runtimeConfigTargetsSupportedDotnet(allocator, runtimeconfig_content, minimum_dotnet_major_version) catch |err| { print.printDebug("Proceeding with the injection of the .NET OpenTelemetry instrumentation. Could not parse {s} safely: {}", .{ metadata_paths.runtimeconfig_path, err }); return true; }; if (!runtimeconfig_targets_supported_dotnet) { print.printInfo("Skipping the injection of the .NET OpenTelemetry instrumentation because {s} does not target a supported .NET runtime (minimum supported .NET major version: {d}).", .{ metadata_paths.runtimeconfig_path, minimum_dotnet_major_version }); return false; } const deps_content = readSmallTextFileAlloc(allocator, metadata_paths.deps_path) catch |err| { print.printDebug("Proceeding with the injection of the .NET OpenTelemetry instrumentation. Could not read {s}: {}", .{ metadata_paths.deps_path, err }); return true; }; defer allocator.free(deps_content); if (depsJsonContainsOpenTelemetryDependency(allocator, deps_content)) |contains_opentelemetry| { if (contains_opentelemetry) { print.printInfo("Skipping the injection of the .NET OpenTelemetry instrumentation because {s} already references OpenTelemetry packages.", .{metadata_paths.deps_path}); return false; } } else |err| { print.printDebug("Proceeding with the injection of the .NET OpenTelemetry instrumentation. Could not parse {s} safely: {}", .{ metadata_paths.deps_path, err }); return true; } return true; } fn findConflictingPreExistingDotnetEnvVar() ?[:0]const u8 { const getenv_fn = libc_info.?.getenv_fn_ptr; for (conflicting_dotnet_env_var_names) |env_var_name| { if (getenv_fn(env_var_name) != null) { return env_var_name; } } return null; } fn resolveManagedApplicationPath( allocator: std.mem.Allocator, cmdline_args: []const []const u8, self_exe_path: []const u8, ) !?[]u8 { if (cmdline_args.len == 0) { return null; } if (std.mem.eql(u8, std.fs.path.basename(cmdline_args[0]), dotnet_host_name)) { for (cmdline_args[1..]) |arg| { if (arg.len == 0 or arg[0] == '-') { continue; } if (std.mem.endsWith(u8, arg, ".dll") or std.mem.endsWith(u8, arg, ".exe")) { return try allocator.dupe(u8, arg); } } return null; } return try allocator.dupe(u8, self_exe_path); } fn createDotnetMetadataPaths(allocator: std.mem.Allocator, app_path: []const u8) !DotnetMetadataPaths { const app_base_path = if (std.mem.endsWith(u8, app_path, ".dll") or std.mem.endsWith(u8, app_path, ".exe")) app_path[0 .. app_path.len - 4] else app_path; return .{ .deps_path = try std.fmt.allocPrint(allocator, "{s}.deps.json", .{app_base_path}), .runtimeconfig_path = try std.fmt.allocPrint(allocator, "{s}.runtimeconfig.json", .{app_base_path}), }; } fn readSmallTextFileAlloc(allocator: std.mem.Allocator, path: []const u8) ![]u8 { const file = if (std.fs.path.isAbsolute(path)) try std.fs.openFileAbsolute(path, .{}) else try std.fs.cwd().openFile(path, .{}); defer file.close(); return file.readToEndAlloc(allocator, max_dotnet_metadata_file_size); } fn depsJsonContainsOpenTelemetryDependency(allocator: std.mem.Allocator, content: []const u8) !bool { var parsed = try std.json.parseFromSlice(std.json.Value, allocator, content, .{}); defer parsed.deinit(); return jsonContainsOpenTelemetryDependency(parsed.value); } fn jsonContainsOpenTelemetryDependency(value: std.json.Value) bool { switch (value) { .object => |object| { var iterator = object.iterator(); while (iterator.next()) |entry| { if (jsonObjectKeyLooksLikeOpenTelemetryDependency(entry.key_ptr.*)) { return true; } if (jsonContainsOpenTelemetryDependency(entry.value_ptr.*)) { return true; } } return false; }, .array => |array| { for (array.items) |item| { if (jsonContainsOpenTelemetryDependency(item)) { return true; } } return false; }, else => return false, } } fn jsonObjectKeyLooksLikeOpenTelemetryDependency(key: []const u8) bool { const dependency_name = if (std.mem.indexOfScalar(u8, key, '/')) |slash_index| key[0..slash_index] else key; return std.mem.startsWith(u8, dependency_name, opentelemetry_dependency_prefix); } fn runtimeConfigTargetsSupportedDotnet(allocator: std.mem.Allocator, content: []const u8, minimum_dotnet_major_version: u32) !bool { var parsed = try std.json.parseFromSlice(std.json.Value, allocator, content, .{}); defer parsed.deinit(); const root = switch (parsed.value) { .object => |obj| obj, else => return true, }; const runtime_options_value = root.get("runtimeOptions") orelse return true; const runtime_options = switch (runtime_options_value) { .object => |obj| obj, else => return true, }; const tfm = runtime_options.get("tfm") orelse return true; const tfm_str = switch (tfm) { .string => |str| str, else => return true, }; return tfmTargetsSupportedDotnet(tfm_str, minimum_dotnet_major_version); } fn tfmTargetsSupportedDotnet(tfm: []const u8, minimum_dotnet_major_version: u32) bool { if (!std.mem.startsWith(u8, tfm, "net")) return false; const rest = tfm[3..]; // We MUST find a dot to ensure it's a major.minor modern layout (e.g., net8.0) // This immediately filters out legacy dotless TFMs like "net48" or "net472" const dot_index = std.mem.indexOfScalar(u8, rest, '.') orelse return false; if (dot_index == 0) return false; // Extract just the major version up to the dot (e.g., "8" from "8.0-windows") const major_str = rest[0..dot_index]; const major = std.fmt.parseUnsigned(u32, major_str, 10) catch return false; return major >= minimum_dotnet_major_version; } test "doGetDotnetValues: should return null value if the libc flavor has not been set" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path = try std.fmt.allocPrint(allocator, "", .{}); defer allocator.free(path); // libc_info is null after _resetState() const dotnet_values = doGetDotnetValues(allocator, path, false, 8); try test_util.expectWithMessage(dotnet_values == null, "dotnet_values == null"); } test "doGetDotnetValues: should return null value if dotnet_instrumentation_disabled is true" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path = try std.fmt.allocPrint(allocator, "/some/valid/path", .{}); defer allocator.free(path); libc_info = test_util.testLibcInfo(.GNU); const dotnet_values = doGetDotnetValues(allocator, path, true, 8); try test_util.expectWithMessage(dotnet_values == null, "dotnet_values == null"); } test "doGetDotnetValues: should return null value if dotnet_path_prefix is the empty string" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path = try std.fmt.allocPrint(allocator, "", .{}); defer allocator.free(path); libc_info = test_util.testLibcInfo(.GNU); const dotnet_values = doGetDotnetValues(allocator, path, false, 8); try test_util.expectWithMessage(dotnet_values == null, "dotnet_values == null"); } test "doGetDotnetValues: should return null value if the profiler path cannot be accessed" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path = try std.fmt.allocPrintSentinel(allocator, "/invalid/path", .{}, 0); defer allocator.free(path); libc_info = test_util.testLibcInfo(.GNU); const dotnet_values = doGetDotnetValues(allocator, path, false, 8); try test_util.expectWithMessage(dotnet_values == null, "dotnet_values == null"); } test "doGetDotnetValues: should return null value if conflicting .NET env var already exists" { const allocator = testing.allocator; _resetState(); defer _resetState(); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"CORECLR_PROFILER={existing-profiler-guid}"}); defer test_util.resetStdCEnviron(original_environ); const path = try std.fmt.allocPrintSentinel(allocator, "/some/valid/path", .{}, 0); defer allocator.free(path); libc_info = test_util.testLibcInfo(.GNU); const dotnet_values = doGetDotnetValues(allocator, path, false, 8); try test_util.expectWithMessage(dotnet_values == null, "dotnet_values == null"); } test "findConflictingPreExistingDotnetEnvVar: returns null when no conflicting .NET env var is set" { const original_environ = try test_util.clearStdCEnviron(); defer test_util.resetStdCEnviron(original_environ); libc_info = test_util.testLibcInfo(.GNU); try test_util.expectWithMessage(findConflictingPreExistingDotnetEnvVar() == null, "conflicting env var should be null"); } test "findConflictingPreExistingDotnetEnvVar: returns CORECLR_ENABLE_PROFILING when set" { const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"CORECLR_ENABLE_PROFILING=1"}); defer test_util.resetStdCEnviron(original_environ); libc_info = test_util.testLibcInfo(.GNU); try testing.expectEqualStrings(coreclr_enable_profiling_env_var_name, findConflictingPreExistingDotnetEnvVar() orelse return error.Unexpected); } test "findConflictingPreExistingDotnetEnvVar: returns CORECLR_PROFILER when set" { const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"CORECLR_PROFILER={existing-profiler-guid}"}); defer test_util.resetStdCEnviron(original_environ); libc_info = test_util.testLibcInfo(.GNU); try testing.expectEqualStrings(coreclr_profiler_env_var_name, findConflictingPreExistingDotnetEnvVar() orelse return error.Unexpected); } test "findConflictingPreExistingDotnetEnvVar: returns CORECLR_PROFILER_PATH when set" { const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"CORECLR_PROFILER_PATH=/tmp/existing-profiler.so"}); defer test_util.resetStdCEnviron(original_environ); libc_info = test_util.testLibcInfo(.GNU); try testing.expectEqualStrings(coreclr_profiler_path_env_var_name, findConflictingPreExistingDotnetEnvVar() orelse return error.Unexpected); } test "findConflictingPreExistingDotnetEnvVar: returns DOTNET_ADDITIONAL_DEPS when set" { const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"DOTNET_ADDITIONAL_DEPS=/tmp/existing-additional-deps"}); defer test_util.resetStdCEnviron(original_environ); libc_info = test_util.testLibcInfo(.GNU); try testing.expectEqualStrings(dotnet_additional_deps_env_var_name, findConflictingPreExistingDotnetEnvVar() orelse return error.Unexpected); } test "findConflictingPreExistingDotnetEnvVar: returns DOTNET_SHARED_STORE when set" { const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"DOTNET_SHARED_STORE=/tmp/existing-shared-store"}); defer test_util.resetStdCEnviron(original_environ); libc_info = test_util.testLibcInfo(.GNU); try testing.expectEqualStrings(dotnet_shared_store_env_var_name, findConflictingPreExistingDotnetEnvVar() orelse return error.Unexpected); } test "findConflictingPreExistingDotnetEnvVar: returns DOTNET_STARTUP_HOOKS when set" { const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"DOTNET_STARTUP_HOOKS=/tmp/existing-startup-hook.dll"}); defer test_util.resetStdCEnviron(original_environ); libc_info = test_util.testLibcInfo(.GNU); try testing.expectEqualStrings(dotnet_startup_hooks_env_var_name, findConflictingPreExistingDotnetEnvVar() orelse return error.Unexpected); } test "findConflictingPreExistingDotnetEnvVar: ignores OTEL_DOTNET_AUTO_HOME on its own" { const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"OTEL_DOTNET_AUTO_HOME=/tmp/existing-otel-home"}); defer test_util.resetStdCEnviron(original_environ); libc_info = test_util.testLibcInfo(.GNU); try test_util.expectWithMessage(findConflictingPreExistingDotnetEnvVar() == null, "OTEL_DOTNET_AUTO_HOME should not be treated as conflicting"); } test "resolveManagedApplicationPath: dotnet host uses managed assembly argument" { const allocator = testing.allocator; const cmdline_args = [_][]const u8{ "/usr/bin/dotnet", "/app/MyApp.dll", "--urls", "http://localhost:8080", }; const app_path = (try resolveManagedApplicationPath(allocator, &cmdline_args, "/usr/bin/dotnet")) orelse return error.Unexpected; defer allocator.free(app_path); try testing.expectEqualStrings("/app/MyApp.dll", app_path); } test "resolveManagedApplicationPath: direct apphost launch uses executable path" { const allocator = testing.allocator; const cmdline_args = [_][]const u8{ "/app/MyApp", "--urls", "http://localhost:8080", }; const app_path = (try resolveManagedApplicationPath(allocator, &cmdline_args, "/app/MyApp")) orelse return error.Unexpected; defer allocator.free(app_path); try testing.expectEqualStrings("/app/MyApp", app_path); } test "resolveManagedApplicationPath: dotnet host without managed assembly returns null" { const cmdline_args = [_][]const u8{ "/usr/bin/dotnet", "--info", }; try test_util.expectWithMessage((try resolveManagedApplicationPath(testing.allocator, &cmdline_args, "/usr/bin/dotnet")) == null, "app path should be null"); } test "createDotnetMetadataPaths: managed dll path produces deps path" { const allocator = testing.allocator; const metadata_paths = try createDotnetMetadataPaths(allocator, "/app/MyApp.dll"); defer metadata_paths.freeAll(allocator); try testing.expectEqualStrings("/app/MyApp.deps.json", metadata_paths.deps_path); try testing.expectEqualStrings("/app/MyApp.runtimeconfig.json", metadata_paths.runtimeconfig_path); } test "createDotnetMetadataPaths: managed exe path produces runtimeconfig path" { const allocator = testing.allocator; const metadata_paths = try createDotnetMetadataPaths(allocator, "/app/MyApp.exe"); defer metadata_paths.freeAll(allocator); try testing.expectEqualStrings("/app/MyApp.deps.json", metadata_paths.deps_path); try testing.expectEqualStrings("/app/MyApp.runtimeconfig.json", metadata_paths.runtimeconfig_path); } test "createDotnetMetadataPaths: apphost path produces deps path" { const allocator = testing.allocator; const metadata_paths = try createDotnetMetadataPaths(allocator, "/app/MyApp"); defer metadata_paths.freeAll(allocator); try testing.expectEqualStrings("/app/MyApp.deps.json", metadata_paths.deps_path); try testing.expectEqualStrings("/app/MyApp.runtimeconfig.json", metadata_paths.runtimeconfig_path); } test "runtimeConfigTargetsSupportedDotnet: true for net8.0 Microsoft.NETCore.App" { const content = \\{ \\ "runtimeOptions": { \\ "tfm": "net8.0", \\ "framework": { \\ "name": "Microsoft.NETCore.App", \\ "version": "8.0.0" \\ } \\ } \\} ; try test_util.expectWithMessage(try runtimeConfigTargetsSupportedDotnet(testing.allocator, content, 8), "runtimeconfig should qualify"); } test "runtimeConfigTargetsSupportedDotnet: true for ASP.NET Core runtimeconfig" { const content = \\{ \\ "runtimeOptions": { \\ "tfm": "net8.0", \\ "framework": { \\ "name": "Microsoft.AspNetCore.App", \\ "version": "8.0.1" \\ } \\ } \\} ; try test_util.expectWithMessage(try runtimeConfigTargetsSupportedDotnet(testing.allocator, content, 8), "runtimeconfig should qualify"); } test "runtimeConfigTargetsSupportedDotnet: false for net7.0 runtimeconfig" { const content = \\{ \\ "runtimeOptions": { \\ "tfm": "net7.0", \\ "framework": { \\ "name": "Microsoft.NETCore.App", \\ "version": "7.0.0" \\ } \\ } \\} ; try test_util.expectWithMessage(!(try runtimeConfigTargetsSupportedDotnet(testing.allocator, content, 8)), "runtimeconfig should not qualify"); } test "runtimeConfigTargetsSupportedDotnet: false for net6.0 runtimeconfig with the default minimum major version" { const content = \\{ \\ "runtimeOptions": { \\ "tfm": "net6.0", \\ "framework": { \\ "name": "Microsoft.NETCore.App", \\ "version": "6.0.0" \\ } \\ } \\} ; try test_util.expectWithMessage(!(try runtimeConfigTargetsSupportedDotnet(testing.allocator, content, 8)), "runtimeconfig should not qualify"); } test "runtimeConfigTargetsSupportedDotnet: true for net6.0 runtimeconfig when the minimum major version is lowered to 6" { const content = \\{ \\ "runtimeOptions": { \\ "tfm": "net6.0", \\ "framework": { \\ "name": "Microsoft.NETCore.App", \\ "version": "6.0.36" \\ } \\ } \\} ; try test_util.expectWithMessage(try runtimeConfigTargetsSupportedDotnet(testing.allocator, content, 6), "runtimeconfig should qualify"); } test "tfmTargetsSupportedDotnet: respects the configured minimum major version" { try test_util.expectWithMessage(tfmTargetsSupportedDotnet("net8.0", 8), "net8.0 should qualify with minimum 8"); try test_util.expectWithMessage(!tfmTargetsSupportedDotnet("net6.0", 8), "net6.0 should not qualify with minimum 8"); try test_util.expectWithMessage(tfmTargetsSupportedDotnet("net6.0", 6), "net6.0 should qualify with minimum 6"); try test_util.expectWithMessage(tfmTargetsSupportedDotnet("net8.0", 6), "net8.0 should qualify with minimum 6"); try test_util.expectWithMessage(!tfmTargetsSupportedDotnet("net5.0", 6), "net5.0 should not qualify with minimum 6"); try test_util.expectWithMessage(!tfmTargetsSupportedDotnet("net472", 6), "legacy dotless TFMs should never qualify"); } test "runtimeConfigTargetsSupportedDotnet: true for incomplete runtimeconfig" { const content = \\{ \\ "runtimeOptions": { \\ "rollForward": "Major" \\ } \\} ; try test_util.expectWithMessage(try runtimeConfigTargetsSupportedDotnet(testing.allocator, content, 8), "runtimeconfig with no tfm should proceed with injection"); } test "runtimeConfigTargetsSupportedDotnet: rejects malformed json" { try testing.expectError(error.UnexpectedEndOfInput, runtimeConfigTargetsSupportedDotnet(testing.allocator, "{", 8)); } test "runtimeConfigTargetsSupportedDotnet: true for net9.0 Microsoft.NETCore.App" { const content = \\{ \\ "runtimeOptions": { \\ "tfm": "net9.0", \\ "framework": { \\ "name": "Microsoft.NETCore.App", \\ "version": "9.0.0" \\ } \\ } \\} ; try test_util.expectWithMessage(try runtimeConfigTargetsSupportedDotnet(testing.allocator, content, 8), "runtimeconfig should qualify"); } test "runtimeConfigTargetsSupportedDotnet: true for net11.0 future multi-digit major" { const content = \\{ \\ "runtimeOptions": { \\ "tfm": "net11.0", \\ "framework": { \\ "name": "Microsoft.NETCore.App", \\ "version": "11.0.0" \\ } \\ } \\} ; try test_util.expectWithMessage(try runtimeConfigTargetsSupportedDotnet(testing.allocator, content, 8), "runtimeconfig should qualify"); } test "runtimeConfigTargetsSupportedDotnet: true for OS-specific TFM net8.0-windows" { const content = \\{ \\ "runtimeOptions": { \\ "tfm": "net8.0-windows", \\ "framework": { \\ "name": "Microsoft.NETCore.App", \\ "version": "8.0.0" \\ } \\ } \\} ; try test_util.expectWithMessage(try runtimeConfigTargetsSupportedDotnet(testing.allocator, content, 8), "runtimeconfig should qualify"); } test "depsJsonContainsOpenTelemetryDependency: false when no OpenTelemetry packages are present" { const content = \\{ \\ "libraries": { \\ "Newtonsoft.Json/13.0.3": { \\ "type": "package" \\ } \\ } \\} ; try test_util.expectWithMessage(!(try depsJsonContainsOpenTelemetryDependency(testing.allocator, content)), "deps should not contain OpenTelemetry"); } test "depsJsonContainsOpenTelemetryDependency: true when OpenTelemetry package is present" { const content = \\{ \\ "libraries": { \\ "OpenTelemetry/1.11.0": { \\ "type": "package" \\ } \\ } \\} ; try test_util.expectWithMessage(try depsJsonContainsOpenTelemetryDependency(testing.allocator, content), "deps should contain OpenTelemetry"); } test "depsJsonContainsOpenTelemetryDependency: true when OpenTelemetry target entry is present" { const content = \\{ \\ "targets": { \\ ".NETCoreApp,Version=v9.0": { \\ "OpenTelemetry.Extensions.Hosting/1.11.0": { \\ "runtime": {} \\ } \\ } \\ } \\} ; try test_util.expectWithMessage(try depsJsonContainsOpenTelemetryDependency(testing.allocator, content), "deps should contain OpenTelemetry"); } test "depsJsonContainsOpenTelemetryDependency: rejects malformed json" { try testing.expectError(error.UnexpectedEndOfInput, depsJsonContainsOpenTelemetryDependency(testing.allocator, "{")); } fn determineDotnetValues( gpa: std.mem.Allocator, dotnet_path_prefix: []u8, libc_f: types.LibCFlavor, architecture: std.Target.Cpu.Arch, ) DotnetError!DotnetValues { const libc_flavor_prefix: []const u8 = switch (libc_f) { .GNU => "glibc", .MUSL => "musl", else => return error.UnknownLibCFlavor, }; // Map known architectures to their .NET RID names; for everything else fall // through to Zig's tag name so that downstream adopters can place their SDK at // the expected path without the injector hard-blocking unknown architectures. const arch_dotnet_name: []const u8 = switch (architecture) { .x86_64 => "x64", .aarch64 => "arm64", else => |arch| @tagName(arch), }; const platform = switch (libc_f) { .GNU => try std.fmt.allocPrint(gpa, "linux-{s}", .{arch_dotnet_name}), .MUSL => try std.fmt.allocPrint(gpa, "linux-musl-{s}", .{arch_dotnet_name}), else => return error.UnknownLibCFlavor, }; defer gpa.free(platform); const coreclr_profiler_path = try std.fmt.allocPrintSentinel(gpa, "{s}/{s}/{s}/OpenTelemetry.AutoInstrumentation.Native.so", .{ dotnet_path_prefix, libc_flavor_prefix, platform, }, 0); const additional_deps = try std.fmt.allocPrintSentinel(gpa, "{s}/{s}/AdditionalDeps", .{ dotnet_path_prefix, libc_flavor_prefix, }, 0); const otel_auto_home = try std.fmt.allocPrintSentinel(gpa, "{s}/{s}", .{ dotnet_path_prefix, libc_flavor_prefix }, 0); const shared_store = try std.fmt.allocPrintSentinel(gpa, "{s}/{s}/store", .{ dotnet_path_prefix, libc_flavor_prefix, }, 0); const startup_hooks = try std.fmt.allocPrintSentinel(gpa, "{s}/{s}/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll", .{ dotnet_path_prefix, libc_flavor_prefix, }, 0); return .{ .coreclr_enable_profiling = coreclr_enable_profiling_value, .coreclr_profiler = coreclr_profiler_value, .coreclr_profiler_path = coreclr_profiler_path, .additional_deps = additional_deps, .otel_auto_home = otel_auto_home, .shared_store = shared_store, .startup_hooks = startup_hooks, }; } test "determineDotnetValues: returns values for glibc/s390x using Zig arch tag name" { const allocator = testing.allocator; const path = try std.fmt.allocPrint(allocator, "/usr/lib/opentelemetry/dotnet", .{}); defer allocator.free(path); const dotnet_values = try determineDotnetValues(allocator, path, .GNU, .s390x); defer dotnet_values.freeAll(allocator); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc/linux-s390x/OpenTelemetry.AutoInstrumentation.Native.so", dotnet_values.coreclr_profiler_path, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc", dotnet_values.otel_auto_home, ); } test "determineDotnetValues: returns values for musl/s390x using Zig arch tag name" { const allocator = testing.allocator; const path = try std.fmt.allocPrint(allocator, "/usr/lib/opentelemetry/dotnet", .{}); defer allocator.free(path); const dotnet_values = try determineDotnetValues(allocator, path, .MUSL, .s390x); defer dotnet_values.freeAll(allocator); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl/linux-musl-s390x/OpenTelemetry.AutoInstrumentation.Native.so", dotnet_values.coreclr_profiler_path, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl", dotnet_values.otel_auto_home, ); } test "determineDotnetValues: should return error for unknown libc flavor" { try testing.expectError(error.UnknownLibCFlavor, determineDotnetValues( testing.allocator, "", .UNKNOWN, .x86_64, )); } test "determineDotnetValues: should return values for glibc/x86_64" { const allocator = testing.allocator; const path = try std.fmt.allocPrint(allocator, "/usr/lib/opentelemetry/dotnet", .{}); defer allocator.free(path); const dotnet_values = try determineDotnetValues( allocator, path, .GNU, .x86_64, ); defer dotnet_values.freeAll(allocator); try testing.expectEqualStrings( coreclr_enable_profiling_value, dotnet_values.coreclr_enable_profiling, ); try testing.expectEqualStrings( coreclr_profiler_value, dotnet_values.coreclr_profiler, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc/linux-x64/OpenTelemetry.AutoInstrumentation.Native.so", dotnet_values.coreclr_profiler_path, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc/AdditionalDeps", dotnet_values.additional_deps orelse return error.Unexpected, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc", dotnet_values.otel_auto_home, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc/store", dotnet_values.shared_store orelse return error.Unexpected, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll", dotnet_values.startup_hooks, ); } test "determineDotnetValues: should return values for glibc/arm64" { const allocator = testing.allocator; const path = try std.fmt.allocPrint(allocator, "/usr/lib/opentelemetry/dotnet", .{}); defer allocator.free(path); const dotnet_values = try determineDotnetValues( allocator, path, .GNU, .aarch64, ); defer dotnet_values.freeAll(allocator); try testing.expectEqualStrings( coreclr_enable_profiling_value, dotnet_values.coreclr_enable_profiling, ); try testing.expectEqualStrings( coreclr_profiler_value, dotnet_values.coreclr_profiler, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc/linux-arm64/OpenTelemetry.AutoInstrumentation.Native.so", dotnet_values.coreclr_profiler_path, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc/AdditionalDeps", dotnet_values.additional_deps orelse return error.Unexpected, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc", dotnet_values.otel_auto_home, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc/store", dotnet_values.shared_store orelse return error.Unexpected, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/glibc/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll", dotnet_values.startup_hooks, ); } test "determineDotnetValues: should return values for musl/x86_64" { const allocator = testing.allocator; const path = try std.fmt.allocPrint(allocator, "/usr/lib/opentelemetry/dotnet", .{}); defer allocator.free(path); const dotnet_values = try determineDotnetValues( allocator, path, .MUSL, .x86_64, ); defer dotnet_values.freeAll(allocator); try testing.expectEqualStrings( coreclr_enable_profiling_value, dotnet_values.coreclr_enable_profiling, ); try testing.expectEqualStrings( coreclr_profiler_value, dotnet_values.coreclr_profiler, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl/linux-musl-x64/OpenTelemetry.AutoInstrumentation.Native.so", dotnet_values.coreclr_profiler_path, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl/AdditionalDeps", dotnet_values.additional_deps orelse return error.Unexpected, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl", dotnet_values.otel_auto_home, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl/store", dotnet_values.shared_store orelse return error.Unexpected, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll", dotnet_values.startup_hooks, ); } test "determineDotnetValues: should return values for musl/arm64" { const allocator = testing.allocator; const path = try std.fmt.allocPrint(allocator, "/usr/lib/opentelemetry/dotnet", .{}); defer allocator.free(path); const dotnet_values = try determineDotnetValues( allocator, path, .MUSL, .aarch64, ); defer dotnet_values.freeAll(allocator); try testing.expectEqualStrings( coreclr_enable_profiling_value, dotnet_values.coreclr_enable_profiling, ); try testing.expectEqualStrings( coreclr_profiler_value, dotnet_values.coreclr_profiler, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl/linux-musl-arm64/OpenTelemetry.AutoInstrumentation.Native.so", dotnet_values.coreclr_profiler_path, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl/AdditionalDeps", dotnet_values.additional_deps orelse return error.Unexpected, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl", dotnet_values.otel_auto_home, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl/store", dotnet_values.shared_store orelse return error.Unexpected, ); try testing.expectEqualStrings( "/usr/lib/opentelemetry/dotnet/musl/net/OpenTelemetry.AutoInstrumentation.StartupHook.dll", dotnet_values.startup_hooks, ); } /// Only used for unit tests. fn _resetState() void { cached_dotnet_values = CachedDotnetValues{ .values = null, .done = false, }; libc_info = null; } open-telemetry-opentelemetry-injector-ea9dca1/src/elf.zig000066400000000000000000000226631524057645600240400ustar00rootroot00000000000000const builtin = @import("builtin"); const std = @import("std"); const print = @import("print.zig"); const ElfError = error{ ElfHashTableNotFound, ElfInvalidClass, ElfNot64Bit, ElfNotDynamicLibrary, ElfStringSectionNotFound, ElfSymSectionNotFound, MissingDynamicLinkingInformation, }; /// Provide a minimal support for looking up symbols from the memory location where an ELF binary has been memory-mapped /// upon load. /// /// Very loosely modelled after https://codeberg.org/ziglang/zig/src/tag/0.14.1/lib/std/dynamic_library.zig's ElfDynLib, /// but with a few changes: /// 1. Only support ELF on 64 bits. /// 2. Instead of mapping the library's binary to memory, as we would do when loading a library, we are working with /// already mapped memory regions, which also means no allocations are necessary. /// 3. Skip the implementation of symbol version checking: we use this facility only to look up the dlsym symbol, which /// is a function provided by libc that will allow us to look up all other symbols across the entire process. And, /// between us, if your binary has TWO versions of the dlsym symbol, I would like to hear about it. /// 4. Compensate for some quirks in the updating (or lack thereof) of dynamic symbol offsets. pub const ElfDynLib = struct { start_memory_range: usize, end_memory_range: usize, strings: [*:0]u8, syms: [*]std.elf.Sym, hash: ?[*]u32, gnu_hash: ?*std.elf.gnu_hash.Header, pub fn open(start_memory_range: usize, end_memory_range: usize) !ElfDynLib { const elf_header = @as(*std.elf.Ehdr, @ptrFromInt(start_memory_range)); if (!std.mem.eql(u8, elf_header.e_ident[0..4], std.elf.MAGIC)) { return error.NotElfFile; } switch (elf_header.e_ident[std.elf.EI_CLASS]) { std.elf.ELFCLASS64 => { // All good }, std.elf.ELFCLASS32 => return error.ElfNot64Bit, else => return error.ElfInvalidClass, } if (elf_header.e_type != std.elf.ET.DYN) return error.ElfNotDynamicLibrary; var maybe_dynamic_program_header: ?std.elf.Elf64_Phdr = null; { const program_headers_ptr: [*]const std.elf.Elf64_Phdr = @ptrFromInt(start_memory_range + elf_header.e_phoff); const program_headers = program_headers_ptr[0..elf_header.e_phnum]; for (program_headers) |program_header| { switch (program_header.p_type) { std.elf.PT_DYNAMIC => maybe_dynamic_program_header = program_header, else => {}, } } } const dynamic_program_header = maybe_dynamic_program_header orelse return error.MissingDynamicLinkingInformation; var maybe_strings: ?[*:0]u8 = null; var maybe_syms: ?[*]std.elf.Elf64_Sym = null; var maybe_hashtab: ?[*]u32 = null; var maybe_gnu_hash: ?*std.elf.gnu_hash.Header = null; { const dynamic_symbols_count = dynamic_program_header.p_memsz / @sizeOf(std.elf.Elf64_Dyn); const dynamic_symbols_ptr: [*]std.elf.Elf64_Dyn = @ptrFromInt(start_memory_range + dynamic_program_header.p_vaddr); const dynamic_symbols = dynamic_symbols_ptr[0..dynamic_symbols_count]; for (dynamic_symbols) |dynamic_symbol| { var p = dynamic_symbol.d_val; if (p < start_memory_range) { // Musl libc does not update the dynamic symbol locations to add the start memory offset. GNU libc // seems to, except for DT_VERDEF. We compensate for it. p += start_memory_range; } switch (dynamic_symbol.d_tag) { std.elf.DT_STRTAB => maybe_strings = @ptrFromInt(p), std.elf.DT_SYMTAB => maybe_syms = @ptrFromInt(p), std.elf.DT_HASH => maybe_hashtab = @ptrFromInt(p), std.elf.DT_GNU_HASH => maybe_gnu_hash = @ptrFromInt(p), else => {}, } } } const strings = maybe_strings orelse return error.ElfStringSectionNotFound; const syms = maybe_syms orelse return error.ElfSymSectionNotFound; if (maybe_hashtab == null and maybe_gnu_hash == null) { return error.ElfHashTableNotFound; } return .{ .start_memory_range = start_memory_range, .end_memory_range = end_memory_range, .strings = strings, .syms = syms, .hash = maybe_hashtab, .gnu_hash = maybe_gnu_hash, }; } pub fn lookup(self: *const ElfDynLib, comptime T: type, name: [:0]const u8) ?T { if (self.lookupAddress(name)) |symbol_ptr| { return @as(T, @ptrFromInt(symbol_ptr)); } return null; } const GnuHashSection64 = struct { symoffset: u32, bloom_shift: u32, bloom: []u64, buckets: []u32, chain: [*]std.elf.gnu_hash.ChainEntry, fn fromPtr(header: *std.elf.gnu_hash.Header) @This() { const header_offset = @intFromPtr(header); const bloom_offset = header_offset + @sizeOf(std.elf.gnu_hash.Header); const buckets_offset = bloom_offset + header.bloom_size * @sizeOf(u64); const chain_offset = buckets_offset + header.nbuckets * @sizeOf(u32); const bloom_ptr: [*]u64 = @ptrFromInt(bloom_offset); const buckets_ptr: [*]u32 = @ptrFromInt(buckets_offset); const chain_ptr: [*]std.elf.gnu_hash.ChainEntry = @ptrFromInt(chain_offset); return .{ .symoffset = header.symoffset, .bloom_shift = header.bloom_shift, .bloom = bloom_ptr[0..header.bloom_size], .buckets = buckets_ptr[0..header.nbuckets], .chain = chain_ptr, }; } }; fn lookupAddress(self: *const ElfDynLib, name: []const u8) ?usize { const OK_TYPES = (1 << std.elf.STT_NOTYPE | 1 << std.elf.STT_OBJECT | 1 << std.elf.STT_FUNC | 1 << std.elf.STT_COMMON); const OK_BINDS = (1 << std.elf.STB_GLOBAL | 1 << std.elf.STB_WEAK | 1 << std.elf.STB_GNU_UNIQUE); if (self.gnu_hash) |gnu_hash_header| { const gnu_hash_section: GnuHashSection64 = .fromPtr(gnu_hash_header); const hash = std.elf.gnu_hash.calculate(name); const bloom_index = (hash / @bitSizeOf(usize)) % gnu_hash_header.bloom_size; const bloom_val = gnu_hash_section.bloom[bloom_index]; const bit_index_0 = hash % @bitSizeOf(usize); const bit_index_1 = (hash >> @intCast(gnu_hash_header.bloom_shift)) % @bitSizeOf(usize); const one: usize = 1; const bit_mask: usize = (one << @intCast(bit_index_0)) | (one << @intCast(bit_index_1)); if (bloom_val & bit_mask != bit_mask) { // Symbol is not in bloom filter, so it definitely isn't here. return null; } const bucket_index = hash % gnu_hash_header.nbuckets; const bucket_value = gnu_hash_section.buckets[bucket_index]; if (bucket_value < gnu_hash_header.symoffset) { // Empty bucket (value 0) or any value below symoffset: no symbol in this bucket. // Mirrors glibc's dl-lookup check. Without this guard, the subtraction below // would underflow as u32 and trigger a ReleaseSafe panic (issue #399). return null; } const chain_index = bucket_value - gnu_hash_header.symoffset; const chains = gnu_hash_section.chain; const hash_as_entry: std.elf.gnu_hash.ChainEntry = @bitCast(hash); var current_index = chain_index; var at_end_of_chain = false; while (!at_end_of_chain) : (current_index += 1) { const current_entry = chains[current_index]; at_end_of_chain = current_entry.end_of_chain; if (current_entry.hash != hash_as_entry.hash) continue; // check that symbol matches const symbol_index = current_index + gnu_hash_header.symoffset; const symbol = self.syms[symbol_index]; if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info & 0xf)) & OK_TYPES)) continue; if (0 == (@as(u32, 1) << @as(u5, @intCast(symbol.st_info >> 4)) & OK_BINDS)) continue; if (0 == symbol.st_shndx) continue; const symbol_name = std.mem.sliceTo(self.strings + symbol.st_name, 0); if (!std.mem.eql(u8, name, symbol_name)) { continue; } return self.start_memory_range + symbol.st_value; } } if (self.hash) |hashtab| { const symbol_count: usize = @intCast(hashtab[1]); var i: usize = 0; while (i < symbol_count) : (i += 1) { if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info & 0xf)) & OK_TYPES)) continue; if (0 == (@as(u32, 1) << @as(u5, @intCast(self.syms[i].st_info >> 4)) & OK_BINDS)) continue; if (0 == self.syms[i].st_shndx) continue; const symbol_name = std.mem.sliceTo(self.strings + self.syms[i].st_name, 0); if (!std.mem.eql(u8, name, symbol_name)) continue; return self.start_memory_range + self.syms[i].st_value; } } return null; } }; open-telemetry-opentelemetry-injector-ea9dca1/src/injector.exports.map000066400000000000000000000001561524057645600265670ustar00rootroot00000000000000{ # Deliberately not declaring a "global:" section, we do not export any symbols. local: *; };open-telemetry-opentelemetry-injector-ea9dca1/src/jvm.zig000066400000000000000000000211011524057645600240500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const config = @import("config.zig"); const print = @import("print.zig"); const types = @import("types.zig"); const test_util = @import("test_util.zig"); const testing = std.testing; pub const java_tool_options_env_var_name = "JAVA_TOOL_OPTIONS"; /// Returns the modified value for JAVA_TOOL_OPTIONS, including the -javaagent flag; based on the original value of /// JAVA_TOOL_OPTIONS. /// /// The caller is responsible for freeing the returned string (unless the result is passed on to setenv and needs to /// stay in memory). pub fn checkOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, configuration: config.InjectorConfiguration, ) ?[:0]u8 { return doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue( gpa, original_value_optional, configuration.jvm_auto_instrumentation_agent_path, configuration.jvm_instrumentation_disabled, ); } fn doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, jvm_auto_instrumentation_agent_path: []u8, jvm_instrumentation_disabled: bool, ) ?[:0]u8 { if (jvm_instrumentation_disabled or jvm_auto_instrumentation_agent_path.len == 0) { print.printInfo("Skipping the injection of the OpenTelemetry Java agent in \"JAVA_TOOL_OPTIONS\" because it has been explicitly disabled.", .{}); return null; } // Check the existence of the Jar file: by passing a `-javaagent` to a // jar file that does not exist or cannot be opened will crash the JVM std.fs.cwd().access(jvm_auto_instrumentation_agent_path, .{}) catch |err| { print.printError("Skipping the injection of the OpenTelemetry Java agent in \"JAVA_TOOL_OPTIONS\" because of an issue accessing the Jar file at \"{s}\": {}", .{ jvm_auto_instrumentation_agent_path, err }); return null; }; const javaagent_flag_value = std.fmt.allocPrintSentinel(gpa, "-javaagent:{s}", .{jvm_auto_instrumentation_agent_path}, 0) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\": {}", .{ java_tool_options_env_var_name, err }); return null; }; return getModifiedJavaToolOptionsValue( gpa, original_value_optional, javaagent_flag_value, ); } test "doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue: should return null if jvm_instrumentation_disabled is true" { const path = try std.fmt.allocPrint(testing.allocator, "/some/valid/path", .{}); defer testing.allocator.free(path); const modified_java_tool_options = doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue( testing.allocator, null, path, true, ); try test_util.expectWithMessage(modified_java_tool_options == null, "modified_java_tool_options == null"); } test "doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue: should return null if jvm_auto_instrumentation_agent_path the empty string" { const path = try std.fmt.allocPrint(testing.allocator, "", .{}); defer testing.allocator.free(path); const modified_java_tool_options = doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue( testing.allocator, null, path, false, ); try test_util.expectWithMessage(modified_java_tool_options == null, "modified_java_tool_options == null"); } test "doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue: should return null if the Java agent cannot be accessed (original value not set)" { const path = try std.fmt.allocPrint(testing.allocator, "/invalid/path", .{}); defer testing.allocator.free(path); const modified_java_tool_options = doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue( testing.allocator, null, path, false, ); try test_util.expectWithMessage(modified_java_tool_options == null, "modified_java_tool_options == null"); } test "doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue: should return null if the Java agent cannot be accessed (original value present)" { const path = try std.fmt.allocPrint(testing.allocator, "/invalid/path", .{}); defer testing.allocator.free(path); const modified_java_tool_options = doCheckOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue( testing.allocator, "original value", path, false, ); try test_util.expectWithMessage(modified_java_tool_options == null, "modified_java_tool_options == null"); } fn getModifiedJavaToolOptionsValue( gpa: std.mem.Allocator, original_java_tool_options_env_var_value_optional: ?[:0]const u8, javaagent_flag_value: [:0]u8, ) ?[:0]u8 { // For auto-instrumentation, we inject the -javaagent flag into the JAVA_TOOL_OPTIONS environment variable. if (original_java_tool_options_env_var_value_optional) |original_java_tool_options_env_var_value| { if (std.mem.indexOf(u8, original_java_tool_options_env_var_value, javaagent_flag_value)) |_| { // If our "-javaagent ..." flag is already present in JAVA_TOOL_OPTIONS, do nothing. This is particularly // important to avoid double injection, for example if we are injecting into a container which has a shell // executable as its entry point (into which we inject env var modifications), and then this shell starts // the JVM executable as a child process, which inherits the environment from the already injected shell. gpa.free(javaagent_flag_value); return null; } // If JAVA_TOOL_OPTIONS is already set, prepend the "-javaagent ..." flag to the original value. // Since we copy over javaagent_flag_value into newly allocated memory, we can free the parameter here. defer gpa.free(javaagent_flag_value); return std.fmt.allocPrintSentinel(gpa, "{s} {s}", .{ original_java_tool_options_env_var_value, javaagent_flag_value, }, 0) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\": {}", .{ java_tool_options_env_var_name, err }); return null; }; } // JAVA_TOOL_OPTIONS is not set, simply return the -javaagent flag. return javaagent_flag_value[0..]; } test "getModifiedJavaToolOptionsValue: should return -javaagent if original value is unset" { const javaagent_flag_value = try std.fmt.allocPrintSentinel( testing.allocator, "-javaagent:/usr/lib/opentelemetry/jvm/javaagent.jar", .{}, 0, ); const modified_java_tool_options = getModifiedJavaToolOptionsValue( testing.allocator, null, javaagent_flag_value, ); defer (if (modified_java_tool_options) |val| { testing.allocator.free(val); }); try testing.expectEqualStrings( "-javaagent:/usr/lib/opentelemetry/jvm/javaagent.jar", modified_java_tool_options orelse "-", ); } test "getModifiedJavaToolOptionsValue: should append -javaagent if original value exists" { const original_value: [:0]const u8 = "-Dsome.property=value"[0.. :0]; const javaagent_flag_value = try std.fmt.allocPrintSentinel( testing.allocator, "-javaagent:/usr/lib/opentelemetry/jvm/javaagent.jar", .{}, 0, ); const modified_java_tool_options = getModifiedJavaToolOptionsValue( testing.allocator, original_value, javaagent_flag_value, ); defer (if (modified_java_tool_options) |val| { testing.allocator.free(val); }); try testing.expectEqualStrings( "-Dsome.property=value -javaagent:/usr/lib/opentelemetry/jvm/javaagent.jar", modified_java_tool_options orelse "-", ); } test "getModifiedJavaToolOptionsValue: should do nothing if our -javaagent is already present" { const original_value: [:0]const u8 = "-Dsome.property=value -javaagent:/usr/lib/opentelemetry/jvm/javaagent.jar -Dsome.other.property=value"[0.. :0]; const javaagent_flag_value = try std.fmt.allocPrintSentinel( testing.allocator, "-javaagent:/usr/lib/opentelemetry/jvm/javaagent.jar", .{}, 0, ); const modified_java_tool_options = getModifiedJavaToolOptionsValue( testing.allocator, original_value, javaagent_flag_value, ); try test_util.expectWithMessage(modified_java_tool_options == null, "modified_java_tool_options == null"); } open-telemetry-opentelemetry-injector-ea9dca1/src/libc.zig000066400000000000000000001433771524057645600242110ustar00rootroot00000000000000const builtin = @import("builtin"); const std = @import("std"); const testing = std.testing; const auxv = @import("auxv.zig"); const elf = @import("elf.zig"); const print = @import("print.zig"); const test_util = @import("test_util.zig"); const types = @import("types.zig"); const proc_self_exe_path = "/proc/self/exe"; const proc_self_maps_path = "/proc/self/maps"; const glibc_name = "libc.so.6"; const musl_name_part = "musl"; const readable_executable_private = "r-xp"; const readable_private = "r--p"; const dlsym_function_name = "dlsym"; const getenv_function_name = "getenv"; const setenv_function_name = "setenv"; const reader_buffer_len = 4096; const empty_string = @constCast(""); const LibCNameAndFlavor = struct { flavor: types.LibCFlavor, name: []const u8, }; const UnknownLibC = LibCNameAndFlavor{ .flavor = types.LibCFlavor.UNKNOWN, .name = "", }; pub const DlsymLookupFn = *const fn (LibCNameAndFlavor, usize, usize) @typeInfo(@typeInfo(@TypeOf(tryToFindSymbolsInMemoryRange)).@"fn".return_type.?).error_union.error_set!types.LibCInfo; /// Look up which libc flavor (glibc vs. musl) is used (if any), and the memory addresses of key libc facilities we need /// (i.e. getenv, setenv). /// /// This is performed in three steps: /// 1. Inspect the ELF metadata of the program's executable ("/proc/self/exe"), using the DT_NEEDED symbols for the /// libraries that must be linked. /// 2. Look up pointer to the `dlsym` function in the libc loaded by the program, as springboard for the next look ups. /// We use a simplified version of the ELF support in Zig's std library (`dynamic_library`) because we do not want to /// have to support the infinite number of corner cases of the various libc flavors and versions. /// 3. Use the loaded libc's `dlsym` function to look up the symbols we need (getenv, setenv). pub fn getLibCInfo(gpa: std.mem.Allocator) !types.LibCInfo { const libc_name_and_flavor = try getLibCNameAndFlavor(gpa, proc_self_exe_path); defer if (libc_name_and_flavor.flavor != .UNKNOWN) gpa.free(libc_name_and_flavor.name); const libc_info = getLibCMemoryLocations( proc_self_maps_path, libc_name_and_flavor, tryToFindSymbolsInMemoryRange, ) catch |err| { if (err == error.CannotFindLibcMemoryRange and print.isDebug()) { // The error will be properly logged in the code calling getLibCInfo, but for this specific error, let's // include a dump of /proc/self/maps in the log output if the log level is debug. print.printDebug("printing content of {s} below as debugging information", .{proc_self_maps_path}); logProcSelfMaps(proc_self_maps_path) catch { // ignore errors from logProcSelfMaps deliberately }; } return err; }; print.printDebug("identified {s} libc loaded from {s}", .{ switch (libc_name_and_flavor.flavor) { .GNU => "GNU", .MUSL => "musl", else => "unknown", }, libc_name_and_flavor.name, }); return libc_info; } /// Inspect the ELF metadata of the program's executable ("/proc/self/exe"), using the DT_NEEDED symbols for the /// libraries that must be linked. We use the executable's file instead of its in-memory mapping to avoid annoyances /// with looking up the in-memory location of the ELF header (it is never in memory at location 0 is the virtual memory /// space of the program, is is usually offset by 40 bytes). fn getLibCNameAndFlavor(gpa: std.mem.Allocator, self_exe_path: []const u8) !LibCNameAndFlavor { // TODO MM: Rewrite this to use in-memory, finding u=out the ELF header location using auxv? If that would work, we // could make this logic allocation-free. const self_exe_file = std.fs.openFileAbsolute(self_exe_path, .{ .mode = .read_only }) catch |err| { print.printError("Cannot open \"{s}\": {}", .{ self_exe_path, err }); return UnknownLibC; }; defer self_exe_file.close(); var reader_buf: [reader_buffer_len]u8 = undefined; var reader = self_exe_file.reader(&reader_buf); const elf_header = std.elf.Header.read(&reader.interface) catch |err| { print.printError("Cannot read ELF header from \"{s}\": {}", .{ self_exe_path, err }); return UnknownLibC; }; if (!elf_header.is_64) { print.printError("ELF header from \"{s}\" seems to not be from a 64 bit binary", .{self_exe_path}); return error.ElfNot64Bit; } var dynamic_symbols_table_offset: u64 = 0; var dynamic_symbols_table_size: u64 = 0; try self_exe_file.seekTo(elf_header.shoff); const section_headers = try gpa.alloc(std.elf.Elf64_Shdr, elf_header.shnum); defer gpa.free(section_headers); _ = try self_exe_file.read(std.mem.sliceAsBytes(section_headers)); for (section_headers) |section_header| { switch (section_header.sh_type) { std.elf.SHT_DYNAMIC => { dynamic_symbols_table_offset = section_header.sh_offset; dynamic_symbols_table_size = section_header.sh_size; }, else => { // Ignore this section }, } } if (dynamic_symbols_table_offset == 0) { print.printError("No dynamic section found in ELF metadata when inspecting \"{s}\"", .{self_exe_path}); return error.ElfDynamicSymbolTableNotFound; } // Look for DT_NEEDED entries in the dynamic table, they state which libraries were used when the binary has been // compiled. Some Examples: // // JVM with GNU libc // ----------------- // $ readelf -Wd /usr/bin/java // Dynamic section at offset 0xfd28 contains 30 entries: // Tag Type Name/Value // 0x0000000000000001 (NEEDED) Shared library: [libz.so.1] // 0x0000000000000001 (NEEDED) Shared library: [libjli.so] // 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] // // JVM with musl libc // ------------------ // $ readelf -Wd /usr/bin/java // Dynamic section at offset 0xfd18 contains 33 entries: // Tag Type Name/Value // 0x0000000000000001 (NEEDED) Shared library: [libjli.so] // 0x0000000000000001 (NEEDED) Shared library: [libc.musl-aarch64.so.1] // read dynamic section try self_exe_file.seekTo(dynamic_symbols_table_offset); const dynamic_symbol_count = dynamic_symbols_table_size / @sizeOf(std.elf.Elf64_Dyn); const dynamic_symbols = try gpa.alloc(std.elf.Elf64_Dyn, dynamic_symbol_count); defer gpa.free(dynamic_symbols); _ = try self_exe_file.read(std.mem.sliceAsBytes(dynamic_symbols)); // find string table address (DT_STRTAB) var strtab_addr: u64 = 0; for (dynamic_symbols) |dyn| { if (dyn.d_tag == std.elf.DT_STRTAB) { strtab_addr = dyn.d_val; break; } } if (strtab_addr == 0) { print.printError("No string table found when inspecting ELF binary \"{s}\"", .{self_exe_path}); return error.ElfStringsTableNotFound; } var string_table_offset: u64 = 0; var string_table_size: u64 = 0; for (section_headers) |shdr| { if (shdr.sh_type == std.elf.SHT_STRTAB and shdr.sh_addr == strtab_addr) { string_table_offset = shdr.sh_offset; string_table_size = shdr.sh_size; break; } } if (string_table_offset == 0) { // Fallback: Use program headers if section headers don’t map it try self_exe_file.seekTo(elf_header.phoff); const phdrs = try gpa.alloc(std.elf.Elf64_Phdr, elf_header.phnum); defer gpa.free(phdrs); _ = try self_exe_file.read(std.mem.sliceAsBytes(phdrs)); for (phdrs) |phdr| { if (phdr.p_type == std.elf.PT_LOAD and phdr.p_vaddr <= strtab_addr and strtab_addr < phdr.p_vaddr + phdr.p_filesz) { string_table_offset = phdr.p_offset + (strtab_addr - phdr.p_vaddr); break; } } if (string_table_offset == 0) { print.printError("Could not map string table address when inspecting ELF binary \"{s}\"", .{self_exe_path}); return error.ElfStringsTableNotFound; } } for (dynamic_symbols) |dynamic_symbol| { if (dynamic_symbol.d_tag == std.elf.DT_NULL) { // End of the dynamic symbols break; } if (dynamic_symbol.d_tag == std.elf.DT_NEEDED) { const string_offset = string_table_offset + dynamic_symbol.d_val; try self_exe_file.seekTo(string_offset); var lib_name_buf: [256]u8 = undefined; var len: usize = 0; while (len < lib_name_buf.len) : (len += 1) { const bytes_read = try self_exe_file.read(lib_name_buf[len .. len + 1]); if (bytes_read == 0 or lib_name_buf[len] == 0) break; } const lib_name = lib_name_buf[0..len]; if (lib_name.len > 0) { if (std.mem.indexOf(u8, lib_name, musl_name_part)) |_| { // lib_name exists on the stack, we need to allocate a string with the same content on the heap const lib_name_owned = std.fmt.allocPrint(gpa, "{s}", .{lib_name}) catch |err| { print.printError("Failed to allocate memory for libc name: {}", .{err}); return error.CannotAllocateMemory; }; return LibCNameAndFlavor{ .flavor = types.LibCFlavor.MUSL, .name = lib_name_owned }; } if (std.mem.indexOf(u8, lib_name, glibc_name)) |_| { print.printDebug("found a libc: {s}", .{lib_name}); // lib_name exists on the stack, we need to allocate a string with the same content on the heap const lib_name_owned = std.fmt.allocPrint(gpa, "{s}", .{lib_name}) catch |err| { print.printError("Failed to allocate memory for libc name: {}", .{err}); return error.CannotAllocateMemory; }; return LibCNameAndFlavor{ .flavor = types.LibCFlavor.GNU, .name = lib_name_owned }; } } } } return UnknownLibC; } test "getLibCNameAndFlavor: should return libc flavor unknown when file does not exist" { const allocator = std.testing.allocator; const lib_c = try getLibCNameAndFlavor(allocator, "/does/not/exist"); defer allocator.free(lib_c.name); try testing.expectEqual(.UNKNOWN, lib_c.flavor); } test "getLibCNameAndFlavor: should return libc flavor unknown when file is not an ELF binary" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_binary = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/libc/not-an-elf-binary" }); defer allocator.free(absolute_path_to_binary); const lib_c = try getLibCNameAndFlavor(allocator, absolute_path_to_binary); defer allocator.free(lib_c.name); try testing.expectEqual(.UNKNOWN, lib_c.flavor); } test "getLibCNameAndFlavor: should identify glibc libc flavor (x86_64)" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_binary = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/libc/dotnet-app-x86_64-glibc" }); defer allocator.free(absolute_path_to_binary); const lib_c = try getLibCNameAndFlavor(allocator, absolute_path_to_binary); defer allocator.free(lib_c.name); try testing.expectEqual(.GNU, lib_c.flavor); } test "getLibCNameAndFlavor: should identify glibc libc flavor (arm64)" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_binary = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/libc/dotnet-app-arm64-glibc" }); defer allocator.free(absolute_path_to_binary); const lib_c = try getLibCNameAndFlavor(allocator, absolute_path_to_binary); defer allocator.free(lib_c.name); try testing.expectEqual(.GNU, lib_c.flavor); } test "getLibCNameAndFlavor: should identify musl libc flavor (x86_64)" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_binary = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/libc/dotnet-app-x86_64-musl" }); defer allocator.free(absolute_path_to_binary); const lib_c = try getLibCNameAndFlavor(allocator, absolute_path_to_binary); defer allocator.free(lib_c.name); try testing.expectEqual(.MUSL, lib_c.flavor); } test "getLibCNameAndFlavor: should identify musl libc flavor (arm64)" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_binary = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/libc/dotnet-app-arm64-musl" }); defer allocator.free(absolute_path_to_binary); const lib_c = try getLibCNameAndFlavor(allocator, absolute_path_to_binary); defer allocator.free(lib_c.name); try testing.expectEqual(.MUSL, lib_c.flavor); } fn getLibCMemoryLocations(self_maps_path: []const u8, libc_name_and_flavor: LibCNameAndFlavor, dlsym_lookup_fn: DlsymLookupFn) !types.LibCInfo { switch (libc_name_and_flavor.flavor) { types.LibCFlavor.GNU => { return findGlibcMemoryRangeAndLookupMemoryLocations( self_maps_path, libc_name_and_flavor, dlsym_lookup_fn, ); }, types.LibCFlavor.MUSL => { const at_base = auxv.getauxval(std.elf.AT_BASE); if (at_base == 0) { print.printError("cannot find AT_BASE in /proc/self/auxv", .{}); return error.CannotFindAtBase; } return findMuslMemoryRangeAndLookupMemoryLocations( self_maps_path, libc_name_and_flavor, at_base, dlsym_lookup_fn, ); }, else => return error.UnknownLibCFlavor, } } test "getLibCMemoryLocations: glibc" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-glibc-x86_64" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 2; const libc_info = try getLibCMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .GNU, .name = glibc_name, }, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.GNU, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7fffff2e6000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7fffff43c000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } // Note: Tests for getLibCMemoryLocations for musl are deliberately omitted because for that we would also have to mock // the auxv.getauxval() function. There are tests for findMuslMemoryRangeAndLookupMemoryLocations, see below. fn findGlibcMemoryRangeAndLookupMemoryLocations( self_maps_path: []const u8, libc_name_and_flavor: LibCNameAndFlavor, dlsym_lookup_fn: DlsymLookupFn, ) !types.LibCInfo { var maps_file = try std.fs.openFileAbsolute(self_maps_path, .{}); defer maps_file.close(); // Find the end of the memory range of the linker using /proc/self/maps var reader_buf: [reader_buffer_len]u8 = undefined; var reader = maps_file.reader(&reader_buf); // On a lot of modern distributions, the name returned by getLibCNameAndFlavor (e.g. "libc.so.6") will appear // verbatim in /proc/self/maps. But on other (older) distributions (Debian Bullseye for example), libc.so.6 // is a symbolic link to the actual file, i.e. a link to libc-2.31.so or similar; and /proc/self/maps has no entry // for "libc.so.6", only one for libc-2.31.so. The linker has resolved the symbolic link libc.so.6 by finding that // file system entry in its standard libary search paths before /proc/self/maps is provided. To avoid having to // reimplement the library search path logic of the linker, we will first try to find a /proc/self/maps entry for // the exact name (e.g. libc.so.6) and look for dlsym in the associcated memory range. If that fails, we will try // to find dlsym in all memory ranges referenced by an /proc/self/maps entry that has the correct permissions. // // First pass/fast path: look for an entry in /proc/self/maps that matches the libc name. while (takeSentinelOrDiscardOverlyLongLine(&reader)) |line| { if (try processOneGlibcProcSelfMapsLine( self_maps_path, libc_name_and_flavor, dlsym_lookup_fn, line, true, "via libc name", )) |libc_info| { return libc_info; } } else |err| switch (err) { error.ReadFailed => { print.printWarn("Failed to read {s}", .{self_maps_path}); return error.CannotFindLibcMemoryRange; }, // If the file does not end with a newline, we still need to read and process the last line until EOF. error.EndOfStream => { var buffer: [reader_buffer_len]u8 = undefined; const chars = reader.interface.readSliceShort(&buffer) catch 0; const line = buffer[0..chars]; if (try processOneGlibcProcSelfMapsLine( self_maps_path, libc_name_and_flavor, dlsym_lookup_fn, line, true, "via libc name", )) |libc_info| { return libc_info; } }, } // Second pass: try the dlsym lookup for all /proc/self/maps memory ranges with matching permissions and file names // that could be shared objects. try maps_file.seekTo(0); reader = maps_file.reader(&reader_buf); while (takeSentinelOrDiscardOverlyLongLine(&reader)) |line| { if (try processOneGlibcProcSelfMapsLine( self_maps_path, libc_name_and_flavor, dlsym_lookup_fn, line, false, "in second pass", )) |libc_info| { return libc_info; } } else |err| switch (err) { error.ReadFailed => { print.printWarn("Failed to read {s}", .{self_maps_path}); return error.CannotFindLibcMemoryRange; }, // If the file does not end with a newline, we still need to read and process the last line until EOF. error.EndOfStream => { var buffer: [reader_buffer_len]u8 = undefined; const chars = reader.interface.readSliceShort(&buffer) catch 0; const line = buffer[0..chars]; if (try processOneGlibcProcSelfMapsLine( self_maps_path, libc_name_and_flavor, dlsym_lookup_fn, line, false, "in second pass", )) |libc_info| { return libc_info; } }, } return error.CannotFindLibcMemoryRange; } fn processOneGlibcProcSelfMapsLine( self_maps_path: []const u8, libc_name_and_flavor: LibCNameAndFlavor, dlsym_lookup_fn: DlsymLookupFn, line: []u8, check_lib_name: bool, debug_label: []const u8, ) !?types.LibCInfo { if (std.mem.trim(u8, line, " ").len == 0) { return null; } // Parse the address range (e.g., "55b3e9c1a000-55b3e9e1a000 ...") // address perms offset dev inode pathname // aaaac5560000-aaaaca1fd000 r-xp 00000000 00:11e 8682241 /usr/local/bin/node var slices = std.mem.splitAny(u8, line, " "); const memory_range = slices.first(); const permissions = slices.next() orelse return null; if (!memoryRangeHasMatchingPermissions(permissions)) { return null; } if (check_lib_name) { if (!std.mem.endsWith(u8, slices.rest(), libc_name_and_flavor.name)) { return null; } } else { // If check_lib_name is false, we are in the second pass over the /proc/self/maps file where we not only inspect // the shared object that looks like the libc that is used, but instead inspect every shared object. Filter the // memory ranges where the path ends with a string that matches ".so([.0-9]+)?". // First, consume the remaining parts from the slices spliterator via .next() to get the file system path of the // shared object. _ = slices.next() orelse return null; // offset _ = slices.next() orelse return null; // device _ = slices.next() orelse return null; // inode if (!pathLooksLikeSharedObject(slices.rest())) { return null; } } if (std.mem.indexOf(u8, memory_range, "-")) |range_separator_index| { const start_memory_range_hex = memory_range[0..range_separator_index]; const end_memory_range_hex = memory_range[range_separator_index + 1 ..]; const start_memory_range = try std.fmt.parseInt(usize, start_memory_range_hex, 16); const end_memory_range = try std.fmt.parseInt(usize, end_memory_range_hex, 16); print.printDebug( "attempting dlsym lookup {s} for {s} line: {s}", .{ debug_label, self_maps_path, line }, ); if (dlsym_lookup_fn( libc_name_and_flavor, start_memory_range, end_memory_range, )) |libc_info| { print.printDebug( "dlsym lookup {s} succeeded for {s} line: {s}", .{ debug_label, self_maps_path, line }, ); return libc_info; } else |err| { print.printDebug( "dlsym lookup {s} failed for {s} line: {s} -- {}", .{ debug_label, self_maps_path, line, err }, ); return null; } } return null; } test "findGlibcMemoryRangeAndLookupMemoryLocations: x86_64" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-glibc-x86_64" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 2; const libc_info = try findGlibcMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .GNU, .name = glibc_name, }, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.GNU, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7fffff2e6000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7fffff43c000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findGlibcMemoryRangeAndLookupMemoryLocations: arm64" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-glibc-arm64" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 1; const libc_info = try findGlibcMemoryRangeAndLookupMemoryLocations(absolute_path_to_maps_file, .{ .flavor = .GNU, .name = glibc_name, }, mockFindSymbolsInMemoryRange); try testing.expectEqual(.GNU, libc_info.flavor); try test_util.expectMemoryRangeLimit(0xffff88c50000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0xffff88ddb000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findGlibcMemoryRangeAndLookupMemoryLocation: x86_64/Debian 11" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-glibc-x86_64-bullseye" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 2; const libc_info = try findGlibcMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .GNU, .name = glibc_name, }, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.GNU, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7fffff2c9000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7fffff422000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findGlibcMemoryRangeAndLookupMemoryLocations: arm64/Debian 11" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-glibc-arm64-bullseye" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 1; const libc_info = try findGlibcMemoryRangeAndLookupMemoryLocations(absolute_path_to_maps_file, .{ .flavor = .GNU, .name = glibc_name, }, mockFindSymbolsInMemoryRange); try testing.expectEqual(.GNU, libc_info.flavor); try test_util.expectMemoryRangeLimit(0xffffa72b3000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0xffffa740f000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findGlibcMemoryRangeAndLookupMemoryLocations: continue to read after discarding overly long line in first pass" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-glibc-first-pass-overly-long-line" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 2; const libc_info = try findGlibcMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .GNU, .name = glibc_name, }, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.GNU, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7fffff2e6000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7fffff43c000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findGlibcMemoryRangeAndLookupMemoryLocations: continue to read after discarding overly long line in second pass" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-glibc-second-pass-overly-long-line" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 2; const libc_info = try findGlibcMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .GNU, .name = glibc_name, }, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.GNU, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7fffff2c9000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7fffff422000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findGlibcMemoryRangeAndLookupMemoryLocations: read last line if not terminated by newline in first pass" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-glibc-first-pass-no-terminating-newline" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 2; const libc_info = try findGlibcMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .GNU, .name = glibc_name, }, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.GNU, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7fffff2e6000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7fffff43c000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findGlibcMemoryRangeAndLookupMemoryLocations: read last line if not terminated by newline in second pass" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-glibc-second-pass-no-terminating-newline" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 2; const libc_info = try findGlibcMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .GNU, .name = glibc_name, }, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.GNU, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7fffff2c9000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7fffff422000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } fn findMuslMemoryRangeAndLookupMemoryLocations( self_maps_path: []const u8, libc_name_and_flavor: LibCNameAndFlavor, at_base: usize, dlsym_lookup_fn: DlsymLookupFn, ) !types.LibCInfo { // musl bundles the linker and the libc itself in the same .so and it gets mapped in the same memory region. We can // find where the linker is, and so also the libc, we can look up the AT_BASE location in /proc/self/auxv. var maps_file = try std.fs.openFileAbsolute(self_maps_path, .{}); defer maps_file.close(); // Find the end of the memory range of the linker using /proc/self/maps var reader_buf: [reader_buffer_len]u8 = undefined; var reader = maps_file.reader(&reader_buf); while (takeSentinelOrDiscardOverlyLongLine(&reader)) |line| { if (try processOneMuslProcSelfMapsLine( self_maps_path, libc_name_and_flavor, dlsym_lookup_fn, at_base, line, )) |libc_info| { return libc_info; } } else |err| switch (err) { error.ReadFailed => { print.printWarn("Failed to read {s}", .{self_maps_path}); return error.CannotFindLibcMemoryRange; }, // If the file does not end with a newline, we still need to read and process the last line until EOF. error.EndOfStream => { var buffer: [reader_buffer_len]u8 = undefined; const chars = reader.interface.readSliceShort(&buffer) catch 0; const line = buffer[0..chars]; if (try processOneMuslProcSelfMapsLine( self_maps_path, libc_name_and_flavor, dlsym_lookup_fn, at_base, line, )) |libc_info| { return libc_info; } }, } return error.CannotFindLibcMemoryRange; } fn processOneMuslProcSelfMapsLine( self_maps_path: []const u8, libc_name_and_flavor: LibCNameAndFlavor, dlsym_lookup_fn: DlsymLookupFn, at_base: usize, line: []u8, ) !?types.LibCInfo { if (std.mem.trim(u8, line, " ").len == 0) { return null; } // Parse the address range (e.g., "55b3e9c1a000-55b3e9e1a000 ...") // address perms offset dev inode pathname // aaaac5560000-aaaaca1fd000 r-xp 00000000 00:11e 8682241 /usr/local/bin/node var slices = std.mem.splitAny(u8, line, " "); const memory_range = slices.first(); const permissions = slices.next() orelse return null; if (!memoryRangeHasMatchingPermissions(permissions)) { return null; } // In contrast to the logic for glibc we are deliberately not checking for the name of the libarary here for musl, // the start_memory_range == at_base check below will only let one specific memory range got into the // dlsym_lookup_fn, so no further checks are necessary. // This is also the reason why there is no second-pass over /proc/self/maps where we try to find the correct // musl memory range by attempting dlsym_lookup_fn for every entry with matching permissions. if (std.mem.indexOf(u8, memory_range, "-")) |range_separator_index| { const start_memory_range_hex = memory_range[0..range_separator_index]; const end_memory_range_hex = memory_range[range_separator_index + 1 ..]; const start_memory_range = try std.fmt.parseInt(usize, start_memory_range_hex, 16); if (start_memory_range == at_base) { const memory_range_end = try std.fmt.parseInt(usize, end_memory_range_hex, 16); if (dlsym_lookup_fn( libc_name_and_flavor, at_base, memory_range_end, )) |libc_info| { print.printDebug( "dlsym lookup (musl) succeeded for {s} line: {s}", .{ self_maps_path, line }, ); return libc_info; } else |err| { print.printDebug( "dlsym lookup (musl) failed for {s} line: {s} -- {}", .{ self_maps_path, line, err }, ); return null; } } } return null; } test "findMuslMemoryRangeAndLookupMemoryLocations: x86_64" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-musl-x86_64" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 1; const libc_info = try findMuslMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .MUSL, .name = musl_name_part, }, 0x7ffffff6e000, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.MUSL, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7ffffff6e000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7ffffffc5000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findMuslMemoryRangeAndLookupMemoryLocations: arm64" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-musl-arm64" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 1; const libc_info = try findMuslMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .MUSL, .name = musl_name_part, }, 0xffffb3670000, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.MUSL, libc_info.flavor); try test_util.expectMemoryRangeLimit(0xffffb3670000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0xffffb3712000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findMuslMemoryRangeAndLookupMemoryLocations: continue to read after discarding overly long line" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-musl-overly-long-line" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 1; const libc_info = try findMuslMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .MUSL, .name = musl_name_part, }, 0x7ffffff6e000, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.MUSL, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7ffffff6e000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7ffffffc5000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } test "findMuslMemoryRangeAndLookupMemoryLocations: read last line if not terminated by newline" { const allocator = std.testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_maps_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-maps/maps-musl-no-terminating-newline" }); defer allocator.free(absolute_path_to_maps_file); __test_find_symbol_actual_attempts = 0; __test_find_symbol_succeed_on_attempt = 1; const libc_info = try findMuslMemoryRangeAndLookupMemoryLocations( absolute_path_to_maps_file, .{ .flavor = .MUSL, .name = musl_name_part, }, 0x7ffffff6e000, mockFindSymbolsInMemoryRange, ); try testing.expectEqual(.MUSL, libc_info.flavor); try test_util.expectMemoryRangeLimit(0x7ffffff6e000, libc_info.getenv_fn_ptr); try test_util.expectMemoryRangeLimit(0x7ffffffc5000, libc_info.setenv_fn_ptr); try testing.expectEqual(__test_find_symbol_succeed_on_attempt, __test_find_symbol_actual_attempts); } /// Checks whether a given permission string (e.g. "r-xp") matches the permissions signature of a memory region that /// might potentially contain the dlsym symbol. fn memoryRangeHasMatchingPermissions(permissions: []const u8) bool { // Intuitively, one might thing that looking for dlsym in /proc/self/maps memory ranges with permission flags r-xp // (readable, not writable, executable & private i.e., copy-on-write) would be enough. But in some scenarios, the // memory range that actually contains dlsym has "r--p" instead. Two known cases: // - Node.js on x86_64/glibc with base image node:22.15.0-bookworm-slim. // - JVM & Node.js on Debian Bullseye (glibc), in particular when iterating over all /proc/self/maps entries in the // second pass (which is necessary because the maps entry is not named "libc.so.6", but dlsym is mapped from the // shared object "libdl-2.31.so" instead). // // Apparently allowing entries in /proc/self/maps with "r--p" permissions can be required for looking up dlsym, // because the _symbols_ (i.e., the names and metadata about functions) exist in the binary's read-only sections; // specifically, in the ELF file's .rodata and symbol tables, which are not part of the executable segment, even if // the actual executable code for those functions then resides in the "r-xp" segment. return std.mem.eql(u8, permissions, readable_executable_private) or std.mem.eql(u8, permissions, readable_private); } test "memoryRangeHasMatchingPermissions" { try test_util.expectWithMessage(memoryRangeHasMatchingPermissions("r-xp"), "memoryRangeHasMatchingPermissions(\"r-xp\")"); try test_util.expectWithMessage(memoryRangeHasMatchingPermissions("r--p"), "memoryRangeHasMatchingPermissions(\"r--p\")"); try test_util.expectWithMessage(!memoryRangeHasMatchingPermissions("rw-p"), "!memoryRangeHasMatchingPermissions(\"rw-p\")"); try test_util.expectWithMessage(!memoryRangeHasMatchingPermissions("rwxp"), "!memoryRangeHasMatchingPermissions(\"rwxp\")"); try test_util.expectWithMessage(!memoryRangeHasMatchingPermissions("rw-s"), "!memoryRangeHasMatchingPermissions(\"rw-s\")"); try test_util.expectWithMessage(!memoryRangeHasMatchingPermissions("r--s"), "!memoryRangeHasMatchingPermissions(\"r--s\")"); try test_util.expectWithMessage(!memoryRangeHasMatchingPermissions("----"), "!memoryRangeHasMatchingPermissions(\"----\")"); } /// Checks whether the given path ends with something that matches ".so([.0-9]+)?". fn pathLooksLikeSharedObject(path: []const u8) bool { if (std.mem.indexOf(u8, path, "libotelinject")) |_| { // never match the injector itself return false; } if (std.mem.lastIndexOf(u8, path, ".so")) |dot_so_index| { const after_dot_so = path[dot_so_index + 3 ..]; if (after_dot_so.len == 0) { return true; } if (after_dot_so[0] != '.') { return false; } // after_dot_so starts with a '.', check that the rest is only digits and dots for (after_dot_so[1..]) |c| { if (!std.ascii.isDigit(c) and c != '.') { return false; } } return true; } return false; } test "pathLooksLikeSharedObject" { try test_util.expectWithMessage(pathLooksLikeSharedObject("/lib/x86_64-linux-gnu/libc.so.6"), "pathLooksLikeSharedObject(\"/lib/x86_64-linux-gnu/libc.so.6\")"); try test_util.expectWithMessage(pathLooksLikeSharedObject("/lib/aarch64-linux-gnu/libc.so.1"), "pathLooksLikeSharedObject(\"/lib/aarch64-linux-gnu/libc.so.1\")"); try test_util.expectWithMessage(pathLooksLikeSharedObject("/lib/libc.so"), "pathLooksLikeSharedObject(\"/lib/libc.so\")"); try test_util.expectWithMessage(pathLooksLikeSharedObject("/usr/lib/libc.so.2.31"), "pathLooksLikeSharedObject(\"/usr/lib/libc.so.2.31\")"); try test_util.expectWithMessage(pathLooksLikeSharedObject("/usr/lib/libc.so.2.31.1"), "pathLooksLikeSharedObject(\"/usr/lib/libc.so.2.31.1\")"); try test_util.expectWithMessage(!pathLooksLikeSharedObject("/usr/lib/libc.so.backup"), "!pathLooksLikeSharedObject(\"/usr/lib/libc.so.backup\")"); try test_util.expectWithMessage(!pathLooksLikeSharedObject("/path/to/app.o"), "!pathLooksLikeSharedObject(\"/path/to/app.o\")"); try test_util.expectWithMessage(!pathLooksLikeSharedObject("/usr/lib/libc.s"), "!pathLooksLikeSharedObject(\"/usr/lib/libc.s\")"); try test_util.expectWithMessage(!pathLooksLikeSharedObject("/usr/lib/libc.a"), "!pathLooksLikeSharedObject(\"/usr/lib/libc.a\")"); try test_util.expectWithMessage(!pathLooksLikeSharedObject("/usr/lib/libc.dylib"), "!pathLooksLikeSharedObject(\"/usr/lib/libc.dylib\")"); try test_util.expectWithMessage(!pathLooksLikeSharedObject("/usr/lib/libc.dll"), "!pathLooksLikeSharedObject(\"/usr/lib/libc.dll\")"); try test_util.expectWithMessage(!pathLooksLikeSharedObject("/usr/lib/not-a-lib.txt"), "!pathLooksLikeSharedObject(\"/usr/lib/not-a-lib.txt\")"); try test_util.expectWithMessage(!pathLooksLikeSharedObject("/some/path/libotelinject_amd64.so"), "!pathLooksLikeSharedObject(\"/some/path/libotelinject_amd64.so\")"); try test_util.expectWithMessage(!pathLooksLikeSharedObject("/some/path/libotelinject_arm64.so"), "!pathLooksLikeSharedObject(\"/some/path/libotelinject_arm64.so\")"); } /// Reads the given memory range via elf.ElfDynLib.open and tries to look up setenv and getenv. /// Preferred path: find dlsym in the ELF and use it with handle=null (RTLD_DEFAULT) to resolve symbols across all /// loaded libraries. Fallback path for glibc < 2.34: dlsym lives in libdl.so, not libc.so, so if it is not found, /// look up setenv and getenv directly from the ELF symbol table instead. If getenv is not found, the injection is /// skipped for this memory range. fn tryToFindSymbolsInMemoryRange( libc_name_and_flavor: LibCNameAndFlavor, start: usize, end: usize, ) !types.LibCInfo { const elf_lib = elf.ElfDynLib.open(start, end) catch |err| { print.printWarn("cannot open libc mapped range {x}-{x} as ELF library: {}", .{ start, end, err }); return error.CannotOpenLibc; }; if (elf_lib.lookup(types.DlSymFn, dlsym_function_name)) |dlsym_fn| { // Preferred path: use dlsym with handle=null (RTLD_DEFAULT) to resolve symbols // across all loaded libraries. Available on glibc >= 2.34 (where dlsym is in // libc.so itself) and on any binary that links libdl.so. const maybe_setenv_fn = dlsym_fn(null, setenv_function_name); const maybe_getenv_fn = dlsym_fn(null, getenv_function_name); const setenv_fn_ptr: types.SetenvFnPtr = @ptrCast(@alignCast(maybe_setenv_fn orelse return error.CannotFindSetenvSymbol)); const getenv_fn_ptr: types.GetenvFnPtr = @ptrCast(@alignCast(maybe_getenv_fn orelse return error.CannotFindGetenvSymbol)); return .{ .flavor = libc_name_and_flavor.flavor, .getenv_fn_ptr = getenv_fn_ptr, .setenv_fn_ptr = setenv_fn_ptr, }; } // Fallback path for glibc < 2.34: on those versions dlsym is exported by libdl.so // rather than libc.so. A binary that does not link libdl.so will not have // libdl.so in its /proc/self/maps, so dlsym is unreachable via the second pass. // Look up setenv and getenv directly from this library's ELF symbol table. // Both symbols are exported by libc.so on all glibc versions, so the second pass // will find them here when it reaches the libc.so range. Libraries that do not // export these symbols (libpthread, ld-linux, etc.) return an error and the second // pass simply moves on to the next candidate. // Note: we intentionally do not look up __environ here. On binaries with copy // relocation on __environ, elf_lib.lookup would return the address of libc's own // (unused) data-segment slot rather than the BSS copy that setenv actually modifies. // Using getenv (a function) avoids this problem entirely. const setenv_fn_ptr = elf_lib.lookup(types.SetenvFnPtr, setenv_function_name) orelse return error.CannotFindSetenvSymbol; const getenv_fn_ptr = elf_lib.lookup(types.GetenvFnPtr, getenv_function_name) orelse return error.CannotFindGetenvSymbol; return .{ .flavor = libc_name_and_flavor.flavor, .getenv_fn_ptr = getenv_fn_ptr, .setenv_fn_ptr = setenv_fn_ptr, }; } var __test_find_symbol_actual_attempts: u32 = 0; var __test_find_symbol_succeed_on_attempt: u32 = 0; fn mockFindSymbolsInMemoryRange( libc_name_and_flavor: LibCNameAndFlavor, start: usize, end: usize, ) !types.LibCInfo { __test_find_symbol_actual_attempts += 1; if (__test_find_symbol_actual_attempts == __test_find_symbol_succeed_on_attempt) { return .{ .flavor = libc_name_and_flavor.flavor, .getenv_fn_ptr = @ptrFromInt(start), .setenv_fn_ptr = @ptrFromInt(end), }; } return error.CannotFindSetenvSymbol; } fn takeSentinelOrDiscardOverlyLongLine(reader: *std.fs.File.Reader) ![]u8 { if (reader.interface.takeSentinel('\n')) |slice| { return slice; } else |err| switch (err) { error.StreamTooLong => { // Ignore lines that are too long for the buffer; advance the the read positon to the next delimiter to // avoid stream corruption. _ = try reader.interface.discardDelimiterInclusive('\n'); return empty_string; }, else => |leftover_err| return leftover_err, } } fn logProcSelfMaps(self_maps_path: []const u8) !void { var maps_file = try std.fs.openFileAbsolute(self_maps_path, .{}); defer maps_file.close(); var reader_buf: [reader_buffer_len]u8 = undefined; var reader = maps_file.reader(&reader_buf); while (takeSentinelOrDiscardOverlyLongLine(&reader)) |line| { print.printDebug("{s}", .{line}); } else |err| switch (err) { error.ReadFailed => { print.printDebug("Failed to read {s} in logProcSelfMaps", .{self_maps_path}); // ignore error in function only used for logging debug output }, // If the file does not end with a newline, we still want to print the last line until EOF. error.EndOfStream => { var buffer: [reader_buffer_len]u8 = undefined; const chars = reader.interface.readSliceShort(&buffer) catch 0; const line = buffer[0..chars]; print.printDebug("{s}", .{line}); }, } } open-telemetry-opentelemetry-injector-ea9dca1/src/nodejs.zig000066400000000000000000000226671524057645600245600ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const config = @import("config.zig"); const print = @import("print.zig"); const types = @import("types.zig"); const test_util = @import("test_util.zig"); const testing = std.testing; pub const node_options_env_var_name = "NODE_OPTIONS"; /// Returns the modified value for NODE_OPTIONS, including the --require flag; based on the original value of /// NODE_OPTIONS. /// /// The caller is responsible for freeing the returned string (unless the result is passed on to setenv and needs to /// stay in memory). pub fn checkNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, configuration: config.InjectorConfiguration, ) ?[:0]u8 { return doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue( gpa, original_value_optional, configuration.nodejs_auto_instrumentation_agent_path, configuration.nodejs_instrumentation_disabled, ); } fn doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, nodejs_auto_instrumentation_agent_path: []u8, nodejs_instrumentation_disabled: bool, ) ?[:0]u8 { if (nodejs_instrumentation_disabled or nodejs_auto_instrumentation_agent_path.len == 0) { print.printInfo("Skipping the injection of the Node.js OpenTelemetry auto-instrumentation because it has been explicitly disabled.", .{}); return null; } // Check the existence of the Node module: requiring or importing a module // that does not exist or cannot be opened will crash the Node.js process // with an 'ERR_MODULE_NOT_FOUND' error. std.fs.cwd().access(nodejs_auto_instrumentation_agent_path, .{}) catch |err| { print.printError("Skipping the injection of the Node.js OpenTelemetry auto-instrumentation in \"{s}\" because of an issue accessing the Node.js module at \"{s}\": {}", .{ node_options_env_var_name, nodejs_auto_instrumentation_agent_path, err }); return null; }; const require_nodejs_auto_instrumentation_agent = std.fmt.allocPrintSentinel(gpa, "--require {s}", .{nodejs_auto_instrumentation_agent_path}, 0) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\": {}", .{ node_options_env_var_name, err }); return null; }; return getModifiedNodeOptionsValue( gpa, original_value_optional, require_nodejs_auto_instrumentation_agent, ); } test "doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue: should return null if nodejs_instrumentation_disabled is true" { const path = try std.fmt.allocPrint(testing.allocator, "/some/valid/path", .{}); defer testing.allocator.free(path); const modified_node_options_value = doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue( testing.allocator, null, path, true, ); try test_util.expectWithMessage(modified_node_options_value == null, "modified_node_options_value == null"); } test "doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue: should return null if nodejs_auto_instrumentation_agent_path is the empty string" { const path = try std.fmt.allocPrint(testing.allocator, "", .{}); defer testing.allocator.free(path); const modified_node_options_value = doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue( testing.allocator, null, path, false, ); try test_util.expectWithMessage(modified_node_options_value == null, "modified_node_options_value == null"); } test "doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue: should return null if the Node.js OTel auto-instrumentation agent cannot be accessed (no other NODE_OPTIONS are present)" { const path = try std.fmt.allocPrint(testing.allocator, "/invalid/path", .{}); defer testing.allocator.free(path); const modified_node_options_value = doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue( testing.allocator, null, path, false, ); try test_util.expectWithMessage(modified_node_options_value == null, "modified_node_options_value == null"); } test "doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue: should return null if the Node.js OTel auto-instrumentation agent cannot be accessed (other NODE_OPTIONS are present)" { const path = try std.fmt.allocPrint(testing.allocator, "/invalid/path", .{}); defer testing.allocator.free(path); const modified_node_options_value = doCheckNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue( testing.allocator, "--abort-on-uncaught-exception"[0.. :0], path, false, ); try test_util.expectWithMessage(modified_node_options_value == null, "modified_node_options_value == null"); } fn getModifiedNodeOptionsValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, require_nodejs_auto_instrumentation_agent: [:0]u8, ) ?[:0]u8 { if (original_value_optional) |original_value| { if (std.mem.indexOf(u8, original_value, require_nodejs_auto_instrumentation_agent)) |_| { // If the correct "--require ..." flag is already present in NODE_OPTIONS, do nothing. This is particularly // important to avoid double injection, for example if we are injecting into a container which has a shell // executable as its entry point (into which we inject env var modifications), and then this shell starts // the Node.js executable as a child process, which inherits the environment from the already injected // shell. gpa.free(require_nodejs_auto_instrumentation_agent); return null; } // If NODE_OPTIONS is already set, prepend the "--require ..." flag to the original value. // Since we copy over require_nodejs_auto_instrumentation_agent into newly allocated memory, we can free the // parameter here. defer gpa.free(require_nodejs_auto_instrumentation_agent); return std.fmt.allocPrintSentinel( gpa, "{s} {s}", .{ require_nodejs_auto_instrumentation_agent, original_value }, 0, ) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\": {}", .{ node_options_env_var_name, err }); return null; }; } // If NODE_OPTIONS is not set, simply return the "--require ..." flag. return require_nodejs_auto_instrumentation_agent[0..]; } test "getModifiedNodeOptionsValue: should return --require if original value is unset" { const require_nodejs_auto_instrumentation_agent = try std.fmt.allocPrintSentinel( testing.allocator, "--require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js", .{}, 0, ); const modified_node_options_value = getModifiedNodeOptionsValue( testing.allocator, null, require_nodejs_auto_instrumentation_agent, ); defer (if (modified_node_options_value) |val| { testing.allocator.free(val); }); try testing.expectEqualStrings( "--require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js", modified_node_options_value orelse "-", ); } test "getModifiedNodeOptionsValue: should prepend --require if original value exists" { const original_value: [:0]const u8 = "--abort-on-uncaught-exception"[0.. :0]; const require_nodejs_auto_instrumentation_agent = try std.fmt.allocPrintSentinel( testing.allocator, "--require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js", .{}, 0, ); const modified_node_options_value = getModifiedNodeOptionsValue( testing.allocator, original_value, require_nodejs_auto_instrumentation_agent, ); defer (if (modified_node_options_value) |val| { testing.allocator.free(val); }); try testing.expectEqualStrings( "--require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js --abort-on-uncaught-exception", modified_node_options_value orelse "-", ); } test "getModifiedNodeOptionsValue: should do nothing if our --require is already present" { const original_value: [:0]const u8 = "--abort-on-uncaught-exception --require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js --something-else"[0.. :0]; const require_nodejs_auto_instrumentation_agent = try std.fmt.allocPrintSentinel( testing.allocator, "--require /usr/lib/opentelemetry/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js", .{}, 0, ); const modified_node_options_value = getModifiedNodeOptionsValue( testing.allocator, original_value, require_nodejs_auto_instrumentation_agent, ); try test_util.expectWithMessage(modified_node_options_value == null, "modified_node_options_value == null"); } open-telemetry-opentelemetry-injector-ea9dca1/src/patterns_matcher.zig000066400000000000000000000300041524057645600266210ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const testing = std.testing; /// Matches a string against a glob pattern supporting * (any sequence) and ? (single char). /// Returns true if the text matches the pattern. fn matchGlob(pattern: []const u8, text: []const u8) bool { return matchGlobRecursive(pattern, text, 0, 0); } fn matchGlobRecursive(pattern: []const u8, text: []const u8, p_idx: usize, t_idx: usize) bool { // If we've consumed both pattern and text, it's a match if (p_idx >= pattern.len and t_idx >= text.len) { return true; } // If pattern is exhausted but text remains, no match if (p_idx >= pattern.len) { return false; } // Handle * wildcard if (pattern[p_idx] == '*') { // Try matching * with zero characters if (matchGlobRecursive(pattern, text, p_idx + 1, t_idx)) { return true; } // Try matching * with one or more characters if (t_idx < text.len and matchGlobRecursive(pattern, text, p_idx, t_idx + 1)) { return true; } return false; } // If text is exhausted but pattern has non-* characters, no match if (t_idx >= text.len) { return false; } // Handle ? wildcard or exact character match if (pattern[p_idx] == '?' or pattern[p_idx] == text[t_idx]) { return matchGlobRecursive(pattern, text, p_idx + 1, t_idx + 1); } return false; } /// Checks if a path matches any of the provided glob patterns. pub fn matchesAnyPattern(path: []const u8, patterns: []const []const u8) bool { for (patterns) |pattern| { if (matchGlob(pattern, path)) { return true; } } return false; } pub fn matchesManyAnyPattern(args: []const []const u8, patterns: []const []const u8) bool { if (args.len < 2) { return false; } // args[0] is the program name/path // args[1..] are the actual arguments for (args[1..]) |arg| { for (patterns) |pattern| { if (matchGlob(pattern, arg)) { return true; } } } return false; } /// Evaluates whether a process should be allowed based on include path and argument patterns. /// By default (no include constraints configured), all processes are allowed. Each non-empty include /// setting adds a condition: include_paths requires the exe path to match at least one pattern, and /// include_args requires at least one argument to match at least one pattern. All conditions must be /// met (AND across settings). pub fn evaluateAllow( exe_path: []const u8, args: []const []const u8, include_paths: []const []const u8, include_args: []const []const u8, ) bool { if (include_paths.len > 0 and !matchesAnyPattern(exe_path, include_paths)) return false; if (include_args.len > 0 and !matchesManyAnyPattern(args, include_args)) return false; return true; } /// Evaluates whether a process should be denied based on exclude path and argument patterns. /// Returns true if any exclude_paths pattern matches the exe path, or if any exclude_args pattern /// matches any argument. Returns false if no exclude constraints are configured. pub fn evaluateDeny( exe_path: []const u8, args: []const []const u8, exclude_paths: []const []const u8, exclude_args: []const []const u8, ) bool { return ((exclude_paths.len > 0) and matchesAnyPattern(exe_path, exclude_paths)) or ((exclude_args.len > 0) and matchesManyAnyPattern(args, exclude_args)); } test "matchGlob: exact match" { try testing.expect(matchGlob("/usr/bin/bash", "/usr/bin/bash")); try testing.expect(!matchGlob("/usr/bin/bash", "/usr/bin/zsh")); } test "matchGlob: star wildcard" { try testing.expect(matchGlob("/usr/bin/*", "/usr/bin/bash")); try testing.expect(matchGlob("/usr/bin/*", "/usr/bin/zsh")); try testing.expect(matchGlob("/usr/bin/*", "/usr/bin/")); try testing.expect(!matchGlob("/usr/bin/*", "/usr/local/bin/bash")); try testing.expect(matchGlob("/usr/*/bash", "/usr/bin/bash")); try testing.expect(matchGlob("/usr/*/bash", "/usr/local/bash")); try testing.expect(!matchGlob("/usr/*/bash", "/usr/bin/zsh")); } test "matchGlob: multiple stars" { try testing.expect(matchGlob("*/bin/*", "/usr/bin/bash")); try testing.expect(matchGlob("*/bin/*", "home/user/bin/app")); try testing.expect(matchGlob("/usr/*/*", "/usr/bin/bash")); try testing.expect(!matchGlob("/usr/*/*", "/usr/bin")); } test "matchGlob: question mark wildcard" { try testing.expect(matchGlob("/usr/bin/ba?h", "/usr/bin/bash")); try testing.expect(matchGlob("/usr/bin/ba?h", "/usr/bin/bath")); try testing.expect(!matchGlob("/usr/bin/ba?h", "/usr/bin/bas")); try testing.expect(!matchGlob("/usr/bin/ba?h", "/usr/bin/batch")); } test "matchGlob: mixed wildcards" { try testing.expect(matchGlob("/usr/*/ba?h", "/usr/bin/bash")); try testing.expect(matchGlob("*/?sr/bin/*", "/home/usr/bin/app")); try testing.expect(matchGlob("*.txt", "file.txt")); try testing.expect(matchGlob("*.txt", "archive.txt")); try testing.expect(!matchGlob("*.txt", "file.log")); } test "matchGlob: empty pattern and text" { try testing.expect(matchGlob("", "")); try testing.expect(!matchGlob("", "text")); try testing.expect(!matchGlob("pattern", "")); } test "matchGlob: star matches empty as well as any text" { try testing.expect(matchGlob("*", "")); try testing.expect(matchGlob("*", "anything")); try testing.expect(matchGlob("a*b", "ab")); try testing.expect(matchGlob("a*b", "axxxb")); } test "matchesAnyPattern: empty patterns" { try testing.expect(!matchesAnyPattern("/usr/bin/bash", &.{})); } test "matchesAnyPattern: single pattern match" { const patterns: []const []const u8 = &.{"/usr/bin/*"}; try testing.expect(matchesAnyPattern("/usr/bin/bash", patterns)); try testing.expect(!matchesAnyPattern("/opt/bin/bash", patterns)); } test "matchesAnyPattern: multiple patterns" { const patterns: []const []const u8 = &.{ "/usr/bin/*", "/opt/*/bin/*", "*.sh" }; try testing.expect(matchesAnyPattern("/usr/bin/bash", patterns)); try testing.expect(matchesAnyPattern("/opt/local/bin/app", patterns)); try testing.expect(matchesAnyPattern("script.sh", patterns)); try testing.expect(!matchesAnyPattern("/home/user/app", patterns)); } test "matchesManyAnyPattern: empty args" { const patterns: []const []const u8 = &.{"/usr/bin/*"}; try testing.expect(!matchesManyAnyPattern(&.{}, patterns)); } test "matchesManyAnyPattern: only program name" { const patterns: []const []const u8 = &.{"/usr/bin/*"}; const args: []const []const u8 = &.{"/usr/bin/myprogram"}; try testing.expect(!matchesManyAnyPattern(args, patterns)); } test "matchesManyAnyPattern: empty patterns" { const args: []const []const u8 = &.{ "/usr/bin/myprogram", "/usr/bin/bash", "arg2" }; try testing.expect(!matchesManyAnyPattern(args, &.{})); } test "matchesManyAnyPattern: single arg matches" { const patterns: []const []const u8 = &.{"-javaagent*"}; const args: []const []const u8 = &.{ "/usr/bin/myprogram", "-javaagent=myagent.jar" }; try testing.expect(matchesManyAnyPattern(args, patterns)); } test "matchesManyAnyPattern: single arg no match" { const patterns: []const []const u8 = &.{"/usr/bin/*"}; const args: []const []const u8 = &.{ "/usr/bin/myprogram", "/opt/bin/bash" }; try testing.expect(!matchesManyAnyPattern(args, patterns)); } test "matchesManyAnyPattern: multiple args first matches" { const patterns: []const []const u8 = &.{"*.sh"}; const args: []const []const u8 = &.{ "/usr/bin/myprogram", "script.sh", "file.txt" }; try testing.expect(matchesManyAnyPattern(args, patterns)); } test "matchesManyAnyPattern: multiple args last matches" { const patterns: []const []const u8 = &.{"*.txt"}; const args: []const []const u8 = &.{ "/usr/bin/myprogram", "script.sh", "file.txt" }; try testing.expect(matchesManyAnyPattern(args, patterns)); } test "matchesManyAnyPattern: multiple patterns and args" { const patterns: []const []const u8 = &.{ "/usr/bin/*", "*.sh", "/opt/*/bin/*" }; const args: []const []const u8 = &.{ "/usr/bin/myprogram", "file.txt", "/usr/bin/bash", "data.log" }; try testing.expect(matchesManyAnyPattern(args, patterns)); } test "matchesManyAnyPattern: multiple args none match" { const patterns: []const []const u8 = &.{ "/usr/bin/*", "*.sh" }; const args: []const []const u8 = &.{ "/usr/bin/myprogram", "/opt/bin/app", "file.txt" }; try testing.expect(!matchesManyAnyPattern(args, patterns)); } test "matchesManyAnyPattern: wildcard matches multiple args" { const patterns: []const []const u8 = &.{"*"}; const args: []const []const u8 = &.{ "/usr/bin/myprogram", "anything", "will", "match" }; try testing.expect(matchesManyAnyPattern(args, patterns)); } test "matchesManyAnyPattern: complex patterns" { const patterns: []const []const u8 = &.{ "/usr/*/ba?h", "*.conf", "/opt/*/*" }; const args: []const []const u8 = &.{ "/usr/bin/myprogram", "/usr/local/bash", "script.sh" }; try testing.expect(matchesManyAnyPattern(args, patterns)); } test "matchesManyAnyPattern: program name not matched" { const patterns: []const []const u8 = &.{"/usr/bin/myprogram"}; const args: []const []const u8 = &.{ "/usr/bin/myprogram", "arg1" }; try testing.expect(!matchesManyAnyPattern(args, patterns)); } test "evaluateAllow: no includes configured allows all processes" { const args: []const []const u8 = &.{"/usr/bin/bash"}; try testing.expect(evaluateAllow("/usr/bin/bash", args, &.{}, &.{})); try testing.expect(evaluateAllow("/opt/myapp", args, &.{}, &.{})); } test "evaluateAllow: include_paths set without include_args, matching path is allowed" { const include_paths: []const []const u8 = &.{"*/java"}; const args: []const []const u8 = &.{"/usr/bin/java"}; try testing.expect(evaluateAllow("/usr/bin/java", args, include_paths, &.{})); } test "evaluateAllow: include_paths set without include_args, non-matching path is denied" { const include_paths: []const []const u8 = &.{"*/java"}; const args: []const []const u8 = &.{"/bin/sh"}; try testing.expect(!evaluateAllow("/bin/sh", args, include_paths, &.{})); } test "evaluateAllow: both set, both match, process is allowed" { const include_paths: []const []const u8 = &.{"*/java"}; const include_args: []const []const u8 = &.{"-jar"}; const args: []const []const u8 = &.{ "/usr/bin/java", "-jar", "myapp.jar" }; try testing.expect(evaluateAllow("/usr/bin/java", args, include_paths, include_args)); } test "evaluateAllow: both set, path matches but args do not, process is denied" { const include_paths: []const []const u8 = &.{"*/java"}; const include_args: []const []const u8 = &.{"-jar"}; const args: []const []const u8 = &.{ "/usr/bin/java", "-verbose" }; try testing.expect(!evaluateAllow("/usr/bin/java", args, include_paths, include_args)); } test "evaluateDeny: no excludes configured denies nothing" { const args: []const []const u8 = &.{"/usr/bin/bash"}; try testing.expect(!evaluateDeny("/usr/bin/bash", args, &.{}, &.{})); try testing.expect(!evaluateDeny("/opt/myapp", args, &.{}, &.{})); } test "evaluateDeny: exclude_paths set without exclude_args, matching path is denied" { const exclude_paths: []const []const u8 = &.{"*/sh"}; const args: []const []const u8 = &.{"/bin/sh"}; try testing.expect(evaluateDeny("/bin/sh", args, exclude_paths, &.{})); } test "evaluateDeny: exclude_paths set without exclude_args, non-matching path is not denied" { const exclude_paths: []const []const u8 = &.{"*/sh"}; const args: []const []const u8 = &.{"/usr/bin/java"}; try testing.expect(!evaluateDeny("/usr/bin/java", args, exclude_paths, &.{})); } test "evaluateDeny: exclude_args set without exclude_paths, matching arg is denied" { const exclude_args: []const []const u8 = &.{"-debug"}; const args: []const []const u8 = &.{ "/usr/bin/java", "-debug" }; try testing.expect(evaluateDeny("/usr/bin/java", args, &.{}, exclude_args)); } open-telemetry-opentelemetry-injector-ea9dca1/src/patterns_util.zig000066400000000000000000000074261524057645600261670ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const print = @import("print.zig"); const testing = std.testing; /// Splits a comma-separated string into a slice of trimmed strings. pub fn splitByComma(allocator: std.mem.Allocator, input: []const u8) ![][]const u8 { var list: std.ArrayList([]const u8) = try .initCapacity(allocator, input.len); errdefer list.deinit(allocator); var iter = std.mem.splitScalar(u8, input, ','); while (iter.next()) |item| { const trimmed = std.mem.trim(u8, item, " \t\r\n"); if (trimmed.len > 0) { const owned = std.fmt.allocPrint(allocator, "{s}", .{trimmed}) catch |err| { print.printError("error allocating memory for path pattern from: {}", .{err}); return err; }; try list.append(allocator, owned); } } return list.toOwnedSlice(allocator); } test "splitByComma: empty string" { const allocator = testing.allocator; const result = try splitByComma(allocator, ""); defer allocator.free(result); try testing.expectEqual(0, result.len); } test "splitByComma: single value" { const allocator = testing.allocator; const result = try splitByComma(allocator, "/usr/bin/.*"); defer { for (result) |item| allocator.free(item); allocator.free(result); } try testing.expectEqual(1, result.len); try testing.expectEqualStrings("/usr/bin/.*", result[0]); } test "splitByComma: multiple values" { const allocator = testing.allocator; const result = try splitByComma(allocator, "/usr/bin/.*,/opt/.*,/home/.*"); defer { for (result) |item| allocator.free(item); allocator.free(result); } try testing.expectEqual(3, result.len); try testing.expectEqualStrings("/usr/bin/.*", result[0]); try testing.expectEqualStrings("/opt/.*", result[1]); try testing.expectEqualStrings("/home/.*", result[2]); } test "splitByComma: values with whitespace characters" { const allocator = testing.allocator; const result = try splitByComma(allocator, " /usr/bin/.* \n , /opt/.* \t, /home/.* "); defer { for (result) |item| allocator.free(item); allocator.free(result); } try testing.expectEqual(3, result.len); try testing.expectEqualStrings("/usr/bin/.*", result[0]); try testing.expectEqualStrings("/opt/.*", result[1]); try testing.expectEqualStrings("/home/.*", result[2]); } test "splitByComma: empty items filtered out" { const allocator = testing.allocator; const result = try splitByComma(allocator, "/usr/bin/.*,,/opt/.*, \n\t\r ,/home/.*"); defer { for (result) |item| allocator.free(item); allocator.free(result); } try testing.expectEqual(3, result.len); try testing.expectEqualStrings("/usr/bin/.*", result[0]); try testing.expectEqualStrings("/opt/.*", result[1]); try testing.expectEqualStrings("/home/.*", result[2]); } test "splitByComma: trailing comma" { const allocator = testing.allocator; const result = try splitByComma(allocator, "/usr/bin/.*,/opt/.*,"); defer { for (result) |item| allocator.free(item); allocator.free(result); } try testing.expectEqual(2, result.len); try testing.expectEqualStrings("/usr/bin/.*", result[0]); try testing.expectEqualStrings("/opt/.*", result[1]); } test "splitByComma: leading comma" { const allocator = testing.allocator; const result = try splitByComma(allocator, ",/usr/bin/.*,/opt/.*"); defer { for (result) |item| allocator.free(item); allocator.free(result); } try testing.expectEqual(2, result.len); try testing.expectEqualStrings("/usr/bin/.*", result[0]); try testing.expectEqualStrings("/opt/.*", result[1]); } open-telemetry-opentelemetry-injector-ea9dca1/src/print.zig000066400000000000000000000025421524057645600244200ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const proc_self_environ_values = @import("proc_self_environ_values.zig"); const types = @import("types.zig"); const log_prefix = "[otel-injector] "; pub inline fn getLogLevel() proc_self_environ_values.LogLevel { return proc_self_environ_values.getLogLevel(); } pub inline fn isDebug() bool { return getLogLevel() == .Debug; } pub fn printDebug(comptime fmt: []const u8, args: anytype) void { if (getLogLevel() == .Debug) { _printMessage(fmt, args); } } pub fn printInfo(comptime fmt: []const u8, args: anytype) void { if (@intFromEnum(getLogLevel()) <= @intFromEnum(proc_self_environ_values.LogLevel.Info)) { _printMessage(fmt, args); } } pub fn printWarn(comptime fmt: []const u8, args: anytype) void { if (@intFromEnum(getLogLevel()) <= @intFromEnum(proc_self_environ_values.LogLevel.Warn)) { _printMessage(fmt, args); } } pub fn printError(comptime fmt: []const u8, args: anytype) void { if (@intFromEnum(getLogLevel()) <= @intFromEnum(proc_self_environ_values.LogLevel.Error)) { _printMessage(fmt, args); } } fn _printMessage(comptime fmt: []const u8, args: anytype) void { std.debug.print(log_prefix ++ "[{d:>7}] " ++ fmt ++ "\n", .{proc_self_environ_values.getPid()} ++ args); } open-telemetry-opentelemetry-injector-ea9dca1/src/proc_self_environ_parser.zig000066400000000000000000000522461524057645600303620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const builtin = @import("builtin"); const std = @import("std"); const proc_self_environ_values = @import("proc_self_environ_values.zig"); const print = @import("print.zig"); const types = @import("types.zig"); const testing = std.testing; const proc_self_environ_path = "/proc/self/environ"; const otel_injector_log_level_env_var_name = "OTEL_INJECTOR_LOG_LEVEL"; pub const otel_injector_disabled_env_var_name = "OTEL_INJECTOR_DISABLED"; const max_getenv_entry_length = 8192; const max_getenv_buffer_len = max_getenv_entry_length * 2; /// Function type for reading environment variables, abstracting over reading from /// /proc/self/environ (production) vs std.posix.getenv (tests). pub const GetenvFn = *const fn (allocator: std.mem.Allocator, name: []const u8) ?[]u8; /// Looks up an environment variable by reading /proc/self/environ directly, /// without depending on libc or std.posix.getenv. /// Returns an allocated copy of the value, or null if not found or on error. /// Errors are treated the same as a missing variable: if /proc/self/environ is /// unreadable, callers that rely on this function (e.g. for OTEL_INJECTOR_CONFIG_FILE) /// will silently fall back to their defaults. This is intentional — initFromProcSelfEnviron /// runs earlier and will have already surfaced any read failure at a higher log level. /// The caller is responsible for freeing the returned slice. pub fn getenv(allocator: std.mem.Allocator, name: []const u8) ?[]u8 { return getenvFromFile(allocator, proc_self_environ_path, name) catch null; } /// Wraps std.posix.getenv with the GetenvFn signature for use in tests, /// where environment variables are set via setenv()/putenv() rather than /// being present in /proc/self/environ. pub fn posixGetenv(allocator: std.mem.Allocator, name: []const u8) ?[]u8 { const val = std.posix.getenv(name) orelse return null; return allocator.dupe(u8, val) catch unreachable; } fn getenvFromFile(allocator: std.mem.Allocator, path: []const u8, name: []const u8) !?[]u8 { var environ_file = try std.fs.openFileAbsolute(path, .{}); defer environ_file.close(); var buf: [max_getenv_buffer_len]u8 = undefined; var reader = environ_file.reader(&buf); while (takeSentinelOrDiscardOverlyLongLine(&reader)) |environ_entry| { if (matchEntry(environ_entry, name)) |value| { return try allocator.dupe(u8, value); } } else |err| switch (err) { error.EndOfStream => { var last_buf: [max_getenv_entry_length]u8 = undefined; const chars = reader.interface.readSliceShort(&last_buf) catch return null; if (matchEntry(last_buf[0..chars], name)) |value| { return try allocator.dupe(u8, value); } }, else => return err, } return null; } fn matchEntry(entry: []const u8, name: []const u8) ?[]const u8 { if (entry.len <= name.len) return null; if (!std.mem.startsWith(u8, entry, name)) return null; if (entry[name.len] != '=') return null; return entry[name.len + 1 ..]; } /// Initializes a few selected configuration settings (injector disabled, log level) based on environment variables /// (OTEL_INJECTOR_DISABLED, OTEL_INJECTOR_LOG_LEVEL), by reading /proc/self/environ directly. This runs before /// libc detection, config file reading, and allow/deny filtering — all of which depend on these settings or must /// complete before libc is accessed. Reading /proc/self/environ directly avoids any dependency on libc at this stage. pub fn initFromProcSelfEnviron() !void { try initFromEnvironFile(proc_self_environ_path); } fn initFromEnvironFile(self_environ_path: []const u8) !void { proc_self_environ_values.setPid(switch (builtin.target.os.tag) { .linux => @intCast(std.os.linux.getpid()), // Note: the injector does not support any OS besides Linux, this case is only here to support running Zig unit // tests directly on Darwin. .macos => @intCast(std.c.getpid()), else => { error.OsNotSupported; }, }); const allocator = std.heap.page_allocator; const log_level_value = getenvFromFile(allocator, self_environ_path, otel_injector_log_level_env_var_name) catch |err| switch (err) { // AccessDenied happens when the host process has dropped privileges before the injector runs // (e.g. a package postinst script that switches to a service user before starting a daemon). // Both errors mean the same thing here: we cannot read /proc/self/environ, so we fall back to defaults. error.ReadFailed, error.AccessDenied => { print.printWarn("Failed to read {s}", .{self_environ_path}); return; }, else => return err, }; defer if (log_level_value) |v| allocator.free(v); const disabled_value = getenvFromFile(allocator, self_environ_path, otel_injector_disabled_env_var_name) catch |err| switch (err) { error.ReadFailed, error.AccessDenied => null, else => return err, }; defer if (disabled_value) |v| allocator.free(v); if (disabled_value) |v| { proc_self_environ_values.setOtelInjectorDisabled(parseBooleanValue(v)); } if (log_level_value) |log_level| { if (std.ascii.eqlIgnoreCase("debug", log_level)) { proc_self_environ_values.setLogLevel(.Debug); } else if (std.ascii.eqlIgnoreCase("info", log_level)) { proc_self_environ_values.setLogLevel(.Info); } else if (std.ascii.eqlIgnoreCase("warn", log_level)) { proc_self_environ_values.setLogLevel(.Warn); } else if (std.ascii.eqlIgnoreCase("error", log_level)) { proc_self_environ_values.setLogLevel(.Error); } else if (std.ascii.eqlIgnoreCase("none", log_level)) { proc_self_environ_values.setLogLevel(.None); } else { print.printError("unknown value for OTEL_INJECTOR_LOG_LEVEL: \"{s}\" -- valid log levels are \"debug\", \"info\", \"warn\", \"error\", \"none\".", .{log_level}); } } print.printDebug("log level: {}", .{proc_self_environ_values.getLogLevel()}); } fn takeSentinelOrDiscardOverlyLongLine(reader: *std.fs.File.Reader) ![:0]u8 { if (reader.interface.takeSentinel(0)) |slice| { return slice; } else |err| switch (err) { error.StreamTooLong => { // Ignore lines that are too long for the buffer; advance the the read positon to the next delimiter to // avoid stream corruption. _ = try reader.interface.discardDelimiterInclusive(0); return @constCast(""); }, else => |leftover_err| return leftover_err, } } test "initFromEnvironFile: empty /proc/self/environ" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/empty" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); // verify that the default log level is set try testing.expectEqual(.Error, proc_self_environ_values.getLogLevel()); try testing.expectEqual(false, proc_self_environ_values.getOtelInjectorDisabled()); } test "initFromEnvironFile: nothing is not set" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-nothing-set" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); // verify that the default log level is set try testing.expectEqual(.Error, proc_self_environ_values.getLogLevel()); try testing.expectEqual(false, proc_self_environ_values.getOtelInjectorDisabled()); } test "initFromEnvironFile: OTEL_INJECTOR_LOG_LEVEL=debug" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-log-level-debug" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(.Debug, proc_self_environ_values.getLogLevel()); } test "initFromEnvironFile: OTEL_INJECTOR_LOG_LEVEL=info" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-log-level-info" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(.Info, proc_self_environ_values.getLogLevel()); } test "initFromEnvironFile: OTEL_INJECTOR_LOG_LEVEL=warn" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-log-level-warn" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(.Warn, proc_self_environ_values.getLogLevel()); } test "initFromEnvironFile: OTEL_INJECTOR_LOG_LEVEL=error" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-log-level-error" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(.Error, proc_self_environ_values.getLogLevel()); } test "initFromEnvironFile: OTEL_INJECTOR_LOG_LEVEL=none" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-log-level-none" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(.None, proc_self_environ_values.getLogLevel()); } test "initFromEnvironFile: OTEL_INJECTOR_LOG_LEVEL is an arbitrary string" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-log-level-arbitrary-string" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(.Error, proc_self_environ_values.getLogLevel()); } test "initFromEnvironFile: OTEL_INJECTOR_DISABLED=true" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-injector-disabled-true" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(true, proc_self_environ_values.getOtelInjectorDisabled()); } test "initFromEnvironFile: OTEL_INJECTOR_DISABLED=false" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-injector-disabled-false" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(false, proc_self_environ_values.getOtelInjectorDisabled()); } test "initFromEnvironFile: OTEL_INJECTOR_DISABLED=1" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-injector-disabled-1" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(true, proc_self_environ_values.getOtelInjectorDisabled()); } test "initFromEnvironFile: OTEL_INJECTOR_DISABLED is an arbitrary string" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/environ-injector-disabled-arbitrary-string" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(false, proc_self_environ_values.getOtelInjectorDisabled()); } test "initFromEnvironFile: both OTEL_INJECTOR_LOG_LEVEL and OTEL_INJECTOR_DISABLED are set" { defer proc_self_environ_values.reset(); const environ_files = [_][]const u8{ "unit-test-assets/proc-self-environ/environ-log-level-then-disabled", "unit-test-assets/proc-self-environ/environ-disabled-then-log-level", }; for (environ_files) |environ_file| { const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, environ_file }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(true, proc_self_environ_values.getOtelInjectorDisabled()); try testing.expectEqual(true, proc_self_environ_values.getOtelInjectorDisabled()); proc_self_environ_values.reset(); } } test "initFromEnvironFile: unreadable environ file falls back to defaults" { // Skip when running as root: chmod 0 does not prevent root from reading the file, // so we cannot exercise the AccessDenied path. if (builtin.target.os.tag == .linux and std.os.linux.geteuid() == 0) return; if (builtin.target.os.tag == .macos and std.c.geteuid() == 0) return; defer proc_self_environ_values.reset(); const allocator = testing.allocator; var tmp_dir = testing.tmpDir(.{}); defer tmp_dir.cleanup(); { const f = try tmp_dir.dir.createFile("environ-restricted", .{}); try f.writeAll("OTEL_INJECTOR_LOG_LEVEL=debug\x00OTEL_INJECTOR_DISABLED=true\x00"); f.close(); } const path = try tmp_dir.dir.realpathAlloc(allocator, "environ-restricted"); defer allocator.free(path); // Make the file unreadable to trigger AccessDenied when opening it. try std.posix.fchmodat(std.posix.AT.FDCWD, path, 0o000, 0); // Restore permissions on the way out so tmpDir cleanup succeeds. defer std.posix.fchmodat(std.posix.AT.FDCWD, path, 0o600, 0) catch {}; // Must return without an error so the caller does not log at Error level. try initFromEnvironFile(path); // Defaults must be used since the file was unreadable. try testing.expectEqual(.Error, proc_self_environ_values.getLogLevel()); try testing.expectEqual(false, proc_self_environ_values.getOtelInjectorDisabled()); } test "initFromEnvironFile: overly long environment variable" { defer proc_self_environ_values.reset(); const allocator = testing.allocator; const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); const absolute_path_to_environ_file = try std.fs.path.resolve(allocator, &.{ cwd_path, "unit-test-assets/proc-self-environ/overly-long-env-var" }); defer allocator.free(absolute_path_to_environ_file); try initFromEnvironFile(absolute_path_to_environ_file); try testing.expectEqual(.None, proc_self_environ_values.getLogLevel()); try testing.expectEqual(true, proc_self_environ_values.getOtelInjectorDisabled()); } inline fn parseBooleanValue(value: []const u8) bool { return std.ascii.eqlIgnoreCase(value, "true") or std.ascii.eqlIgnoreCase(value, "t") or std.mem.eql(u8, value, "1"); } test "parseBooleanValue: correctly identifies true and false values" { const true_values = [_][]const u8{ "true", "True", "TRUE", "t", "T", "1" }; const false_values = [_][]const u8{ "false", "False", "FALSE", "f", "F", "0", "", "random", "yes", "no", "ON" }; for (true_values) |value| { try testing.expect(parseBooleanValue(value)); } for (false_values) |value| { try testing.expect(!parseBooleanValue(value)); } } fn resolveTestAssetPath(allocator: std.mem.Allocator, relative_path: []const u8) ![]u8 { const cwd_path = try std.fs.cwd().realpathAlloc(allocator, "."); defer allocator.free(cwd_path); return std.fs.path.resolve(allocator, &.{ cwd_path, relative_path }); } test "getenvFromFile: variable found at start of file" { const allocator = testing.allocator; const path = try resolveTestAssetPath(allocator, "unit-test-assets/proc-self-environ/environ-log-level-debug"); defer allocator.free(path); const value = try getenvFromFile(allocator, path, "ENV_VAR_1"); defer if (value) |v| allocator.free(v); try testing.expectEqualStrings("value_1", value.?); } test "getenvFromFile: variable found in middle of file" { const allocator = testing.allocator; const path = try resolveTestAssetPath(allocator, "unit-test-assets/proc-self-environ/environ-log-level-debug"); defer allocator.free(path); const value = try getenvFromFile(allocator, path, "OTEL_INJECTOR_LOG_LEVEL"); defer if (value) |v| allocator.free(v); try testing.expectEqualStrings("debug", value.?); } test "getenvFromFile: variable found at end of file" { const allocator = testing.allocator; const path = try resolveTestAssetPath(allocator, "unit-test-assets/proc-self-environ/environ-log-level-debug"); defer allocator.free(path); const value = try getenvFromFile(allocator, path, "ENV_VAR_2"); defer if (value) |v| allocator.free(v); try testing.expectEqualStrings("value_2", value.?); } test "getenvFromFile: variable not found returns null" { const allocator = testing.allocator; const path = try resolveTestAssetPath(allocator, "unit-test-assets/proc-self-environ/environ-log-level-debug"); defer allocator.free(path); const value = try getenvFromFile(allocator, path, "DOES_NOT_EXIST"); try testing.expectEqual(null, value); } test "getenvFromFile: prefix of variable name does not match" { const allocator = testing.allocator; const path = try resolveTestAssetPath(allocator, "unit-test-assets/proc-self-environ/environ-log-level-debug"); defer allocator.free(path); // "ENV_VAR" is a prefix of "ENV_VAR_1" and "ENV_VAR_2" but should not match either const value = try getenvFromFile(allocator, path, "ENV_VAR"); try testing.expectEqual(null, value); } test "getenvFromFile: overlong entry is skipped, subsequent entries still found" { const allocator = testing.allocator; var tmp_dir = testing.tmpDir(.{}); defer tmp_dir.cleanup(); { const f = try tmp_dir.dir.createFile("environ", .{}); defer f.close(); // Write an entry strictly longer than max_getenv_entry_length to trigger StreamTooLong try f.writeAll("OVERLONG_VAR="); const chunk = [_]u8{'x'} ** 64; var written: usize = 0; while (written <= max_getenv_entry_length) { const n = @min(chunk.len, max_getenv_entry_length + 1 - written); try f.writeAll(chunk[0..n]); written += n; } try f.writeAll(&[1]u8{0}); // null terminator try f.writeAll("TARGET_VAR=found_after_overlong"); try f.writeAll(&[1]u8{0}); } const path = try tmp_dir.dir.realpathAlloc(allocator, "environ"); defer allocator.free(path); const value = try getenvFromFile(allocator, path, "TARGET_VAR"); defer if (value) |v| allocator.free(v); try testing.expectEqualStrings("found_after_overlong", value.?); } open-telemetry-opentelemetry-injector-ea9dca1/src/proc_self_environ_values.zig000066400000000000000000000020001524057645600303440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 pub const LogLevel = enum(u8) { Debug = 0, Info = 1, Warn = 2, Error = 3, None = 4, }; pub const ProcSelfEnvironValues = struct { log_level: LogLevel = .Error, pid: u32 = 0, otel_injector_disabled: bool = false, }; var proc_self_environ_values: ProcSelfEnvironValues = .{}; pub fn reset() void { proc_self_environ_values = .{}; } pub fn getPid() u32 { return proc_self_environ_values.pid; } pub fn setPid(pid: u32) void { proc_self_environ_values.pid = pid; } pub fn getLogLevel() LogLevel { return proc_self_environ_values.log_level; } pub fn setLogLevel(log_level: LogLevel) void { proc_self_environ_values.log_level = log_level; } pub fn getOtelInjectorDisabled() bool { return proc_self_environ_values.otel_injector_disabled; } pub fn setOtelInjectorDisabled(otel_injector_disabled: bool) void { proc_self_environ_values.otel_injector_disabled = otel_injector_disabled; } open-telemetry-opentelemetry-injector-ea9dca1/src/python.zig000066400000000000000000000342111524057645600246030ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const config = @import("config.zig"); const print = @import("print.zig"); const types = @import("types.zig"); const test_util = @import("test_util.zig"); const testing = std.testing; pub const pythonpath_env_var_name = "PYTHONPATH"; var libc_info: ?types.LibCInfo = null; pub fn setLibcInfo(info: types.LibCInfo) void { libc_info = info; } /// Returns the modified value for PYTHONPATH with the Python auto-instrumentation prepended to the original value of /// PYTHONPATH (if any). /// /// The caller is responsible for freeing the returned string (unless the result is passed on to setenv and needs to /// stay in memory). pub fn checkPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, configuration: config.InjectorConfiguration, ) ?[:0]u8 { return doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( gpa, original_value_optional, configuration.python_auto_instrumentation_agent_path_prefix, configuration.python_instrumentation_disabled, ); } fn doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, python_auto_instrumentation_agent_path_prefix: []u8, python_instrumentation_disabled: bool, ) ?[:0]u8 { if (python_instrumentation_disabled or python_auto_instrumentation_agent_path_prefix.len == 0) { print.printInfo("Skipping the injection of the Python OpenTelemetry auto-instrumentation because it has been explicitly disabled.", .{}); return null; } if (libc_info == null) { print.printError("invariant violated: libc info has not been set prior to calling doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue().", .{}); return null; } if (libc_info.?.flavor == .UNKNOWN) { print.printError("Cannot determine libc flavor", .{}); return null; } if (libc_info) |info| { const libc_flavor_suffix = switch (info.flavor) { .GNU => "glibc", .MUSL => "musl", else => unreachable, }; const python_auto_instrumentation_agent_path_optional = determinePythonpathWithLibcSuffix( gpa, python_auto_instrumentation_agent_path_prefix, libc_flavor_suffix, ); if (python_auto_instrumentation_agent_path_optional) |python_auto_instrumentation_agent_path| { // Check the existence of the Python directory, stand down if it does not exist. std.fs.cwd().access(python_auto_instrumentation_agent_path, .{}) catch |err| { defer gpa.free(python_auto_instrumentation_agent_path); print.printError("Skipping the injection of the Python OpenTelemetry auto-instrumentation in \"{s}\" because of an issue accessing the directory at \"{s}\": {}", .{ pythonpath_env_var_name, python_auto_instrumentation_agent_path, err }); return null; }; return getModifiedPythonpathValue( gpa, original_value_optional, python_auto_instrumentation_agent_path, ); } else { return null; } } unreachable; } test "doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue: should return null value if the libc flavor has not been set" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path_prefix: []u8 = try std.fmt.allocPrint(allocator, "/some/path", .{}); defer allocator.free(path_prefix); const modified_pythonpath_value = doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( allocator, null, path_prefix, false, ); try test_util.expectWithMessage(modified_pythonpath_value == null, "modified_pythonpath_value == null"); } test "doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue: should return null value for unknown libc flavor" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path_prefix = try std.fmt.allocPrint(allocator, "", .{}); defer allocator.free(path_prefix); libc_info = test_util.testLibcInfo(.UNKNOWN); const modified_pythonpath_value = doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( allocator, null, path_prefix, false, ); try test_util.expectWithMessage(modified_pythonpath_value == null, "modified_pythonpath_value == null"); } test "doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue: should return null if python_instrumentation_disabled is true" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path_prefix = try std.fmt.allocPrint(allocator, "/some/valid/path", .{}); defer allocator.free(path_prefix); libc_info = test_util.testLibcInfo(.GNU); const modified_pythonpath_value = doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( allocator, null, path_prefix, true, ); try test_util.expectWithMessage(modified_pythonpath_value == null, "modified_pythonpath_value == null"); } test "doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue: should return null if python_auto_instrumentation_agent_path_prefix is the empty string" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path_prefix = try std.fmt.allocPrint(allocator, "", .{}); defer allocator.free(path_prefix); libc_info = test_util.testLibcInfo(.GNU); const modified_pythonpath_value = doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( allocator, null, path_prefix, false, ); try test_util.expectWithMessage(modified_pythonpath_value == null, "modified_pythonpath_value == null"); } test "doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue: should return null if the Python OTel auto-instrumentation agent cannot be accessed (no other PYTHONPATH are present)" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path_prefix = try std.fmt.allocPrint(allocator, "/invalid/path", .{}); defer allocator.free(path_prefix); libc_info = test_util.testLibcInfo(.GNU); const modified_pythonpath_value = doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( allocator, null, path_prefix, false, ); try test_util.expectWithMessage(modified_pythonpath_value == null, "modified_pythonpath_value == null"); } test "doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue: should return null if the Python OTel auto-instrumentation agent cannot be accessed (other PYTHONPATH are present)" { const allocator = testing.allocator; _resetState(); defer _resetState(); const path_prefix = try std.fmt.allocPrint(allocator, "/invalid/path", .{}); defer allocator.free(path_prefix); libc_info = test_util.testLibcInfo(.GNU); const modified_pythonpath_value = doCheckPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( allocator, "/another/path"[0.. :0], path_prefix, false, ); try test_util.expectWithMessage(modified_pythonpath_value == null, "modified_pythonpath_value == null"); } fn determinePythonpathWithLibcSuffix( gpa: std.mem.Allocator, python_auto_instrumentation_agent_path_prefix: []u8, libc_flavor_suffix: []const u8, ) ?[:0]u8 { return std.fmt.allocPrintSentinel(gpa, "{s}/{s}", .{ python_auto_instrumentation_agent_path_prefix, libc_flavor_suffix, }, 0) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\" for libc flavor \"{s}\": {}", .{ pythonpath_env_var_name, libc_flavor_suffix, err, }); return null; }; } test "determinePythonpathWithLibcSuffix: should return full path for glibc" { const allocator = testing.allocator; const path_prefix = try std.fmt.allocPrint(allocator, "/usr/lib/opentelemetry/python", .{}); defer allocator.free(path_prefix); const libc_flavor_suffix = try std.fmt.allocPrint(allocator, "glibc", .{}); defer allocator.free(libc_flavor_suffix); const python_auto_instrumentation_agent_path_optional = determinePythonpathWithLibcSuffix( allocator, path_prefix, libc_flavor_suffix, ); if (python_auto_instrumentation_agent_path_optional) |python_auto_instrumentation_agent_path| { defer allocator.free(python_auto_instrumentation_agent_path); try testing.expectEqualStrings( "/usr/lib/opentelemetry/python/glibc", python_auto_instrumentation_agent_path, ); } else { return error.TestUnexpectedResult; } } test "determinePythonpathWithLibcSuffix: should return full path for musl" { const allocator = testing.allocator; const path_prefix = try std.fmt.allocPrint(allocator, "/usr/lib/opentelemetry/python", .{}); defer allocator.free(path_prefix); const libc_flavor_suffix = try std.fmt.allocPrint(allocator, "musl", .{}); defer allocator.free(libc_flavor_suffix); const python_auto_instrumentation_agent_path_optional = determinePythonpathWithLibcSuffix( allocator, path_prefix, @constCast("musl"), ); if (python_auto_instrumentation_agent_path_optional) |python_auto_instrumentation_agent_path| { defer allocator.free(python_auto_instrumentation_agent_path); try testing.expectEqualStrings( "/usr/lib/opentelemetry/python/musl", python_auto_instrumentation_agent_path, ); } else { return error.TestUnexpectedResult; } } fn getModifiedPythonpathValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, python_auto_instrumentation_agent_path: [:0]u8, ) ?[:0]u8 { if (original_value_optional) |original_value| { if (std.mem.indexOf(u8, original_value, python_auto_instrumentation_agent_path)) |_| { // If the correct path flag is already present in PYTHONPATH, do nothing. This is particularly important // to avoid double injection, for example if we are injecting into a container which has a shell // executable as its entry point (into which we inject env var modifications), and then this shell starts // the Python executable as a child process, which inherits the environment from the already injected // shell. gpa.free(python_auto_instrumentation_agent_path); return null; } // If PYTHONPATH is already set, prepend the our directory to the original value. Since we copy over // python_auto_instrumentation_agent_path into newly allocated memory, we can free the parameter here. defer gpa.free(python_auto_instrumentation_agent_path); return std.fmt.allocPrintSentinel( gpa, "{s}:{s}", .{ python_auto_instrumentation_agent_path, original_value }, 0, ) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\": {}", .{ pythonpath_env_var_name, err }); return null; }; } // If PYTHONPATH is not set, simply return our path, which will then become the only entry in PYTHONPATH. return python_auto_instrumentation_agent_path[0..]; } test "getModifiedPythonpathValue: should return the auto-instrumentation directory if original value is unset" { const allocator = testing.allocator; const python_auto_instrumentation_agent_path = try std.fmt.allocPrintSentinel( allocator, "/usr/lib/opentelemetry/python/glibc", .{}, 0, ); const modified_pythonpath_value = getModifiedPythonpathValue( allocator, null, python_auto_instrumentation_agent_path, ); defer (if (modified_pythonpath_value) |val| { allocator.free(val); }); try testing.expectEqualStrings( "/usr/lib/opentelemetry/python/glibc", modified_pythonpath_value orelse "-", ); } test "getModifiedPythonpathValue: should prepend the auto-instrumentation directory if original value exists" { const allocator = testing.allocator; const original_value: [:0]const u8 = "/another/path/1:/another/path/2"[0.. :0]; const python_auto_instrumentation_agent_path = try std.fmt.allocPrintSentinel( allocator, "/usr/lib/opentelemetry/python/glibc", .{}, 0, ); const modified_pythonpath_value = getModifiedPythonpathValue( allocator, original_value, python_auto_instrumentation_agent_path, ); defer (if (modified_pythonpath_value) |val| { allocator.free(val); }); try testing.expectEqualStrings( "/usr/lib/opentelemetry/python/glibc:/another/path/1:/another/path/2", modified_pythonpath_value orelse "-", ); } test "getModifiedPythonpathValue: should return null if the auto-instrumentation directory is already present" { const allocator = testing.allocator; const original_value: [:0]const u8 = "/path/before:/usr/lib/opentelemetry/python/glibc:/path/after"[0.. :0]; const python_auto_instrumentation_agent_path = try std.fmt.allocPrintSentinel( allocator, "/usr/lib/opentelemetry/python/glibc", .{}, 0, ); const modified_pythonpath_value = getModifiedPythonpathValue( allocator, original_value, python_auto_instrumentation_agent_path, ); try test_util.expectWithMessage(modified_pythonpath_value == null, "modified_pythonpath_value == null"); } /// Only used for unit tests. fn _resetState() void { libc_info = null; } open-telemetry-opentelemetry-injector-ea9dca1/src/resource_attributes.zig000066400000000000000000000240441524057645600273620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const print = @import("print.zig"); const types = @import("types.zig"); pub const modification_happened_msg = "adding additional OpenTelemetry resources attributes via {s}"; var libc_info: ?types.LibCInfo = null; pub fn setLibcInfo(info: types.LibCInfo) void { libc_info = info; } /// A type for a rule to map an environment variable to a resource attribute. The result of applying these rules (via /// getModifiedOtelResourceAttributesValue) is a string of key-value pairs, where each pair is of the form key=value, /// and pairs are separated by commas. If resource_attributes_key is not null, we append a key-value pair /// (that is, ${resource_attributes_key}=${value of environment variable}). If resource_attributes_key is null, the /// value of the enivronment variable is expected to already be a key-value pair (or a comma separated list of key-value /// pairs), and the value of the enivronment variable is appended as is. const EnvToResourceAttributeMapping = struct { environement_variable_name: [:0]const u8, resource_attributes_key: []const u8, }; pub const otel_resource_attributes_env_var_name = "OTEL_RESOURCE_ATTRIBUTES"; const otel_injector_resource_attributes_env_name = "OTEL_INJECTOR_RESOURCE_ATTRIBUTES"; /// A list of mappings from environment variables to resource attributes. const mappings: [7]EnvToResourceAttributeMapping = .{ // Kubernetes-related resource attributes: EnvToResourceAttributeMapping{ .environement_variable_name = "OTEL_INJECTOR_K8S_NAMESPACE_NAME", .resource_attributes_key = "k8s.namespace.name", }, EnvToResourceAttributeMapping{ .environement_variable_name = "OTEL_INJECTOR_K8S_POD_NAME", .resource_attributes_key = "k8s.pod.name", }, EnvToResourceAttributeMapping{ .environement_variable_name = "OTEL_INJECTOR_K8S_POD_UID", .resource_attributes_key = "k8s.pod.uid", }, EnvToResourceAttributeMapping{ .environement_variable_name = "OTEL_INJECTOR_K8S_CONTAINER_NAME", .resource_attributes_key = "k8s.container.name", }, // Service-related resource attributes: EnvToResourceAttributeMapping{ .environement_variable_name = "OTEL_INJECTOR_SERVICE_NAME", .resource_attributes_key = "service.name", }, EnvToResourceAttributeMapping{ .environement_variable_name = "OTEL_INJECTOR_SERVICE_VERSION", .resource_attributes_key = "service.version", }, EnvToResourceAttributeMapping{ .environement_variable_name = "OTEL_INJECTOR_SERVICE_NAMESPACE", .resource_attributes_key = "service.namespace", }, }; /// Derive the modified value for OTEL_RESOURCE_ATTRIBUTES based on the original value, and on other resource attributes /// provided via the OTEL_INJECTOR_* environment variables. /// The caller is responsible for freeing the returned string (unless the result is passed on to setenv and needs to /// stay in memory). pub fn getModifiedOtelResourceAttributesValue(gpa: std.mem.Allocator) !?[:0]u8 { if (libc_info == null) { print.printError("invariant violated: libc info has not been set prior to calling getModifiedOtelResourceAttributesValue().", .{}); return null; } const getenv_fn = libc_info.?.getenv_fn_ptr; var result_list = try std.ArrayList(u8).initCapacity(gpa, std.heap.pageSize()); const result_writer = result_list.writer(gpa); // Key-value pairs from the original environment variable value have the highest priority. We write them first, and // then later, when processing OTEL_INJECTOR_RESOURCE_ATTRIBUTES or any of the mappings, we check for each key if // we have already written it earlier. This avoids duplicate keys and resolves conflicts between the original // OTEL_RESOURCE_ATTRIBUTES value and our modifications in favor of the original OTEL_RESOURCE_ATTRIBUTES value. if (getenv_fn(otel_resource_attributes_env_var_name)) |original_ptr| { const original_value = std.mem.span(original_ptr); if (std.mem.trim(u8, original_value, " \t\r\n").len > 0) { try result_writer.writeAll(original_value); } } var has_modified_value = false; // OTEL_INJECTOR_RESOURCE_ATTRIBUTES should have priority over the built-in mappings, so we process // OTEL_INJECTOR_RESOURCE_ATTRIBUTES first. Then later, when iterating over `mappings`, we will check whether a key // has already been written (and if so, skip it in the mappings-loop). if (getenv_fn(otel_injector_resource_attributes_env_name)) |otel_injector_resource_attributes_value_ptr| { const otel_injector_resource_attributes_value = std.mem.span(otel_injector_resource_attributes_value_ptr); // Split the value of OTEL_INJECTOR_RESOURCE_ATTRIBUTES into individual key-value pairs. var all_key_value_pairs = std.mem.splitAny(u8, otel_injector_resource_attributes_value, ","); while (true) { // Terminate the while (true) loop if the SplitIterator resource_attributes returns null. const key_value_pair = all_key_value_pairs.next() orelse break; // Split the key-value pair into key and value. var key_value_pair_split = std.mem.splitAny(u8, key_value_pair, "="); const key = std.mem.trim(u8, key_value_pair_split.first(), " \t\r\n"); if (key.len == 0) { // Skip empty keys (e.g. if there is something like ",," in OTEL_INJECTOR_RESOURCE_ATTRIBUTES) continue; } // deliberately not trimming the value, strictly speaking leading or trailing whitespace could be part of // the value const value = key_value_pair_split.rest(); // Check if we have written this particular key already, either when copying over the original value of // OTEL_RESOURCE_ATTRIBUTES, or when processing the key-value pairs from OTEL_INJECTOR_RESOURCE_ATTRIBUTES: var value_already_written: ?[]const u8 = null; var all_key_value_pairs_already_written = std.mem.splitAny(u8, result_list.items, ","); while (true) { // Terminate the inner while (true) loop for the already-written check if the SplitIterator // already_set_attributes returns null. const key_value_pair_already_written: []const u8 = all_key_value_pairs_already_written.next() orelse break; var key_value_pair_already_written_split = std.mem.splitAny(u8, key_value_pair_already_written, "="); const key_already_written = key_value_pair_already_written_split.first(); if (std.mem.eql(u8, key_already_written, key)) { // We have written this key already. Set value_already_written so we can log this and skip this // key-value pair. value_already_written = key_value_pair_already_written_split.rest(); break; } } if (value_already_written) |val| { print.printDebug("OTEL_RESOURCE_ATTRIBUTES already sets '{s}={s}'; skipping appending '{s}={s}' from OTEL_INJECTOR_RESOURCE_ATTRIBUTES", .{ key, val, key, value }); continue; } // We have not written this key before, let's write the key-value pair now: if (result_list.items.len > 0) { // write leading comma if we have already written key-value pairs try result_writer.writeAll(","); } // write key-value pair try result_writer.writeAll(key); try result_writer.writeAll("="); try result_writer.writeAll(value); has_modified_value = true; } } // Now iterate over the mappings from OTEL_INJECTOR_* environment variables to resource attributes: for (mappings) |mapping| { const key = mapping.resource_attributes_key; // If no value is set in the process environment, i.e. there we are processing the mapping from // OTEL_INJECTOR_K8S_POD_NAME to k8s.pod.name but OTEL_INJECTOR_K8S_POD_NAME is not set, we skip this mapping. const value = std.mem.span(getenv_fn(mapping.environement_variable_name) orelse continue); // Check if the original value contains the key already: var value_already_written: ?[]const u8 = null; var all_key_value_pairs_already_written = std.mem.splitAny(u8, result_list.items, ","); while (true) { // Terminate the inner while (true) loop for the already-written check if the SplitIterator // already_set_attributes returns null. const key_value_pair_already_written: []const u8 = all_key_value_pairs_already_written.next() orelse break; var key_value_pair_already_written_split = std.mem.splitAny(u8, key_value_pair_already_written, "="); const key_already_written = key_value_pair_already_written_split.first(); if (std.mem.eql(u8, key_already_written, key)) { // We have written this key already. Set value_already_written so we can log this and skip this // key-value pair. value_already_written = key_value_pair_already_written_split.rest(); break; } } if (value_already_written) |val| { print.printDebug("OTEL_RESOURCE_ATTRIBUTES already sets '{s}={s}'; skipping appending '{s}={s}' from {s}", .{ key, val, key, value, mapping.environement_variable_name }); continue; } if (result_list.items.len > 0) { try result_writer.writeAll(","); } try result_writer.writeAll(key); try result_writer.writeAll("="); try result_writer.writeAll(value); has_modified_value = true; } if (!has_modified_value) { result_list.deinit(gpa); return null; } return try result_list.toOwnedSliceSentinel(gpa, 0); } open-telemetry-opentelemetry-injector-ea9dca1/src/resource_attributes_test.zig000066400000000000000000000400601524057645600304150ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const res_attrs = @import("resource_attributes.zig"); const test_util = @import("test_util.zig"); const testing = std.testing; // Note on having tests embedded in the actual source files versus having them in a separate *_test.zig file: Proper // pure unit tests are usually directly in the source file of the production function they are testing. More invasive // tests that need to change the environment variables (for example) should go in a separate file, so we never run the // risk of even compiling the test mechanism to modify the environment. test "getModifiedOtelResourceAttributesValue: no original value, no new resource attributes (null)" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.clearStdCEnviron(); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator); try test_util.expectWithMessage(modified_value == null, "modified_value == null"); } test "getModifiedOtelResourceAttributesValue: no original value, no new resource attributes (empty string)" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"OTEL_RESOURCE_ATTRIBUTES="}); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator); try test_util.expectWithMessage(modified_value == null, "modified_value == null"); } test "getModifiedOtelResourceAttributesValue: no original value (null), new resource attributes: namespace only" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"OTEL_INJECTOR_K8S_NAMESPACE_NAME=namespace"}); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings("k8s.namespace.name=namespace", modified_value); } test "getModifiedOtelResourceAttributesValue: no original value (null), new resource attributes: pod name only" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"OTEL_INJECTOR_K8S_POD_NAME=pod"}); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings("k8s.pod.name=pod", modified_value); } test "getModifiedOtelResourceAttributesValue: no original value (null), new resource attributes: pod uid only" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"OTEL_INJECTOR_K8S_POD_UID=uid"}); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings("k8s.pod.uid=uid", modified_value); } test "getModifiedOtelResourceAttributesValue: no original value (null), new resource attributes: container name only" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"OTEL_INJECTOR_K8S_CONTAINER_NAME=container"}); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings("k8s.container.name=container", modified_value); } test "getModifiedOtelResourceAttributesValue: no original value (null), new resource attributes: OTEL_INJECTOR_RESOURCE_ATTRIBUTES only" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"OTEL_INJECTOR_RESOURCE_ATTRIBUTES=aaa=bbb,ccc=ddd"}); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings("aaa=bbb,ccc=ddd", modified_value); } test "getModifiedOtelResourceAttributesValue: no original value (null), several new resource attributes" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[3][]const u8{ "OTEL_INJECTOR_K8S_NAMESPACE_NAME=namespace", "OTEL_INJECTOR_K8S_POD_NAME=pod", "OTEL_INJECTOR_K8S_POD_UID=uid", }); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings("k8s.namespace.name=namespace,k8s.pod.name=pod,k8s.pod.uid=uid", modified_value); } test "getModifiedOtelResourceAttributesValue: no original value (empty string), several new resource attributes" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[4][]const u8{ "OTEL_RESOURCE_ATTRIBUTES=", "OTEL_INJECTOR_K8S_NAMESPACE_NAME=namespace", "OTEL_INJECTOR_K8S_POD_NAME=pod", "OTEL_INJECTOR_K8S_POD_UID=uid", }); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings("k8s.namespace.name=namespace,k8s.pod.name=pod,k8s.pod.uid=uid", modified_value); } test "getModifiedOtelResourceAttributesValue: no original value (null), new resource attributes: everything is set" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[8][]const u8{ "OTEL_INJECTOR_K8S_NAMESPACE_NAME=namespace", "OTEL_INJECTOR_K8S_POD_NAME=pod", "OTEL_INJECTOR_K8S_POD_UID=uid", "OTEL_INJECTOR_K8S_CONTAINER_NAME=container", "OTEL_INJECTOR_SERVICE_NAME=service", "OTEL_INJECTOR_SERVICE_VERSION=version", "OTEL_INJECTOR_SERVICE_NAMESPACE=servicenamespace", "OTEL_INJECTOR_RESOURCE_ATTRIBUTES=aaa=bbb,ccc=ddd", }); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings( "aaa=bbb,ccc=ddd,k8s.namespace.name=namespace,k8s.pod.name=pod,k8s.pod.uid=uid,k8s.container.name=container,service.name=service,service.version=version,service.namespace=servicenamespace", modified_value, ); } test "getModifiedOtelResourceAttributesValue: original value exists, no new resource attributes" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"OTEL_RESOURCE_ATTRIBUTES=aaa=bbb,ccc=ddd"}); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator); try test_util.expectWithMessage(modified_value == null, "modified_value == null"); } test "getModifiedOtelResourceAttributesValue: original value and new resource attributes" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[4][]const u8{ "OTEL_RESOURCE_ATTRIBUTES=aaa=bbb,ccc=ddd", "OTEL_INJECTOR_K8S_NAMESPACE_NAME=namespace", "OTEL_INJECTOR_K8S_POD_NAME=pod", "OTEL_INJECTOR_K8S_POD_UID=uid", }); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings( "aaa=bbb,ccc=ddd,k8s.namespace.name=namespace,k8s.pod.name=pod,k8s.pod.uid=uid", modified_value, ); } test "getModifiedOtelResourceAttributesValue: key-value pairs in original value have higher precedence than OTEL_INJECTOR_*" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[8][]const u8{ "OTEL_RESOURCE_ATTRIBUTES=aaa=bbb,ccc=ddd,k8s.namespace.name=namespace-original,k8s.pod.name=pod-original,service.name=service-original,service.version=version-original,service.namespace=servicenamespace-original", "OTEL_INJECTOR_K8S_NAMESPACE_NAME=namespace-otel-injector", "OTEL_INJECTOR_K8S_POD_NAME=pod-otel-injector", "OTEL_INJECTOR_K8S_POD_UID=uid-otel-injector", "OTEL_INJECTOR_K8S_CONTAINER_NAME=container-otel-injector", "OTEL_INJECTOR_SERVICE_NAME=service-otel-injector", "OTEL_INJECTOR_SERVICE_VERSION=version-otel-injector", "OTEL_INJECTOR_SERVICE_NAMESPACE=servicenamespace-otel-injector", }); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings( "aaa=bbb,ccc=ddd,k8s.namespace.name=namespace-original,k8s.pod.name=pod-original,service.name=service-original,service.version=version-original,service.namespace=servicenamespace-original,k8s.pod.uid=uid-otel-injector,k8s.container.name=container-otel-injector", modified_value, ); } test "getModifiedOtelResourceAttributesValue: key-value pairs in original value have higher precedence than OTEL_INJECTOR_RESOURCE_ATTRIBUTES" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[2][]const u8{ "OTEL_RESOURCE_ATTRIBUTES=aaa=bbb,ccc=ddd,k8s.namespace.name=namespace-original,k8s.pod.name=pod-original,service.name=service-original,service.version=version-original,service.namespace=servicenamespace-original", "OTEL_INJECTOR_RESOURCE_ATTRIBUTES=k8s.namespace.name=namespace-otel-injector,k8s.pod.name=pod-otel-injector,service.name=service-otel-injector,k8s.pod.uid=uid-otel-injector,k8s.container.name=container-otel-injector", }); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings( "aaa=bbb,ccc=ddd,k8s.namespace.name=namespace-original,k8s.pod.name=pod-original,service.name=service-original,service.version=version-original,service.namespace=servicenamespace-original,k8s.pod.uid=uid-otel-injector,k8s.container.name=container-otel-injector", modified_value, ); } test "getModifiedOtelResourceAttributesValue: OTEL_INJECTOR_RESOURCE_ATTRIBUTES key-value pairs have higher precedence than other OTEL_INJECTOR_* attributes" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[8][]const u8{ "OTEL_INJECTOR_K8S_NAMESPACE_NAME=namespace-from-otel-injector_*-env-var", "OTEL_INJECTOR_K8S_POD_NAME=pod-from-otel-injector_*-env-var", "OTEL_INJECTOR_K8S_POD_UID=uid-from-otel-injector_*-env-var", "OTEL_INJECTOR_K8S_CONTAINER_NAME=container-from-otel-injector_*-env-var", "OTEL_INJECTOR_RESOURCE_ATTRIBUTES=k8s.namespace.name=namespace-from-otel-injector-resource-attributes,k8s.pod.name=pod-from-otel-injector-resource-attributes,service.name=service-from-otel-injector-resource-attributes,k8s.pod.uid=uid-from-otel-injector-resource-attributes,k8s.container.name=container-from-otel-injector-resource-attributes", "OTEL_INJECTOR_SERVICE_NAME=service-from-otel-injector_*-env-var", "OTEL_INJECTOR_SERVICE_VERSION=version-from-otel-injector_*-env-var", "OTEL_INJECTOR_SERVICE_NAMESPACE=servicenamespace-from-otel-injector_*-env-var", }); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings( "k8s.namespace.name=namespace-from-otel-injector-resource-attributes,k8s.pod.name=pod-from-otel-injector-resource-attributes,service.name=service-from-otel-injector-resource-attributes,k8s.pod.uid=uid-from-otel-injector-resource-attributes,k8s.container.name=container-from-otel-injector-resource-attributes,service.version=version-from-otel-injector_*-env-var,service.namespace=servicenamespace-from-otel-injector_*-env-var", modified_value, ); } test "getModifiedOtelResourceAttributesValue: mixing key-value pairs from the original value, OTEL_INJECTOR_RESOURCE_ATTRIBUTES and OTEL_INJECTOR_*" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[9][]const u8{ "OTEL_RESOURCE_ATTRIBUTES=k8s.namespace.name=namespace-original,service.name=service-original", "OTEL_INJECTOR_K8S_NAMESPACE_NAME=namespace-from-otel-injector_*-env-var", "OTEL_INJECTOR_K8S_POD_NAME=pod-from-otel-injector_*-env-var", "OTEL_INJECTOR_K8S_POD_UID=uid-from-otel-injector_*-env-var", "OTEL_INJECTOR_K8S_CONTAINER_NAME=container-from-otel-injector_*-env-var", "OTEL_INJECTOR_RESOURCE_ATTRIBUTES=k8s.namespace.name=namespace-from-otel-injector-resource-attributes,service.name=service-from-otel-injector-resource-attributes,k8s.pod.name=pod-from-otel-injector-resource-attributes", "OTEL_INJECTOR_SERVICE_NAME=service-from-otel-injector_*-env-var", "OTEL_INJECTOR_SERVICE_VERSION=version-from-otel-injector_*-env-var", "OTEL_INJECTOR_SERVICE_NAMESPACE=servicenamespace-from-otel-injector_*-env-var", }); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings( "k8s.namespace.name=namespace-original,service.name=service-original,k8s.pod.name=pod-from-otel-injector-resource-attributes,k8s.pod.uid=uid-from-otel-injector_*-env-var,k8s.container.name=container-from-otel-injector_*-env-var,service.version=version-from-otel-injector_*-env-var,service.namespace=servicenamespace-from-otel-injector_*-env-var", modified_value, ); } test "getModifiedOtelResourceAttributesValue: trims keys and drops empty OTEL_INJECTOR_RESOURCE_ATTRIBUTES key-value pairs" { const allocator = testing.allocator; res_attrs.setLibcInfo(test_util.testLibcInfo(.GNU)); const original_environ = try test_util.setStdCEnviron(&[1][]const u8{"OTEL_INJECTOR_RESOURCE_ATTRIBUTES=aaa=bbb,, ccc=ddd, , eee = fff "}); defer test_util.resetStdCEnviron(original_environ); const modified_value = try res_attrs.getModifiedOtelResourceAttributesValue(allocator) orelse return error.Unexpected; defer allocator.free(modified_value); try testing.expectEqualStrings("aaa=bbb,ccc=ddd,eee= fff ", modified_value); } open-telemetry-opentelemetry-injector-ea9dca1/src/root.zig000066400000000000000000000377431524057645600242620ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const builtin = @import("builtin"); const std = @import("std"); const config = @import("config.zig"); const dotnet = @import("dotnet.zig"); const libc = @import("libc.zig"); const jvm = @import("jvm.zig"); const nodejs = @import("nodejs.zig"); const print = @import("print.zig"); const proc_self_environ_values = @import("proc_self_environ_values.zig"); const proc_self_environ_parser = @import("proc_self_environ_parser.zig"); const python = @import("python.zig"); const ruby = @import("ruby.zig"); const res_attrs = @import("resource_attributes.zig"); const types = @import("types.zig"); const pattern_matcher = @import("patterns_matcher.zig"); const args_parser = @import("args_parser.zig"); const init_section_name = switch (builtin.target.os.tag) { .linux => ".init_array", // Note: the injector does not support any OS besides Linux, this case is only here to support running Zig unit // tests directly on Darwin. .macos => "__DATA,__mod_init_func", else => { error.OsNotSupported; }, }; export const init_array: [1]*const fn () callconv(.c) void linksection(init_section_name) = .{&initEnviron}; fn initEnviron() callconv(.c) void { const allocator = std.heap.page_allocator; proc_self_environ_parser.initFromProcSelfEnviron() catch |err| { // If we fail to read the log level, we continue processing, using the default log level. print.printError("failed to read log level from environment: {}", .{err}); print.printError("using default log level {}", .{print.getLogLevel()}); }; if (proc_self_environ_values.getOtelInjectorDisabled()) { print.printInfo( "Injector has been explicitly disabled via {s}, no environment variables will be modified.", .{proc_self_environ_parser.otel_injector_disabled_env_var_name}, ); return; } // Read config and evaluate allow/deny before libc detection, so that filtered-out // processes never attempt libc detection and never emit libc-related warnings. // config.readConfiguration uses proc_self_environ_parser.getenv internally, which // reads /proc/self/environ directly without requiring libc. var configuration = config.readConfiguration(allocator, proc_self_environ_parser.getenv); defer configuration.deinit(allocator); if (!evaluateAllowDeny(allocator, configuration)) { return; } const libc_info = libc.getLibCInfo(allocator) catch |err| { if (err == error.UnknownLibCFlavor) { print.printError("no libc found: {}", .{err}); } else { print.printError("failed to identify libc: {}", .{err}); } return; }; dotnet.setLibcInfo(libc_info); python.setLibcInfo(libc_info); ruby.setLibcInfo(libc_info); res_attrs.setLibcInfo(libc_info); const maybe_modified_resource_attributes = res_attrs.getModifiedOtelResourceAttributesValue(allocator) catch |err| { print.printError("cannot calculate modified OTEL_RESOURCE_ATTRIBUTES: {}", .{err}); return; }; if (maybe_modified_resource_attributes) |modified_resource_attributes| { // Note: getModifiedOtelResourceAttributesValue returns a sentinel-terminated slices, which can be coerced // automatically into the sentinel-terminated many pointer which is required by setenv. const setenv_res = libc_info.setenv_fn_ptr( res_attrs.otel_resource_attributes_env_var_name, modified_resource_attributes, true, ); if (setenv_res == 0) { print.printDebug( "setting \"{s}\"=\"{s}\"", .{ res_attrs.otel_resource_attributes_env_var_name, modified_resource_attributes }, ); } else { print.printError( "failed to set \"{s}\"=\"{s}\", setenv returned: {d}", .{ res_attrs.otel_resource_attributes_env_var_name, modified_resource_attributes, setenv_res }, ); } } modifyEnvironmentVariable( allocator, libc_info, nodejs.node_options_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, jvm.java_tool_options_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, python.pythonpath_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, ruby.rubyopt_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, ruby.ruby_additional_gem_path_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, dotnet.coreclr_enable_profiling_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, dotnet.coreclr_profiler_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, dotnet.coreclr_profiler_path_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, dotnet.dotnet_additional_deps_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, dotnet.dotnet_shared_store_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, dotnet.dotnet_startup_hooks_env_var_name, configuration, ); modifyEnvironmentVariable( allocator, libc_info, dotnet.otel_dotnet_auto_home_env_var_name, configuration, ); setCustomEnvironmentVariables( allocator, libc_info, configuration.all_auto_instrumentation_agents_env_vars, ); print.printInfo("environment injection finished", .{}); } fn libcGetenv(getenv_fn: types.GetenvFnPtr, name: [:0]const u8) ?[:0]const u8 { return std.mem.span(getenv_fn(name) orelse return null); } fn evaluateAllowDeny(allocator: std.mem.Allocator, configuration: config.InjectorConfiguration) bool { const exe_path = getExecutablePath(allocator) catch { // Skip allow-deny evaluation if getting the executable path has failed. The error has already been logged in // getExecutablePath. return true; }; defer allocator.free(exe_path); const args = getCommandLineArgs(allocator) catch { // Skip allow-deny evaluation if getting the arguments has failed. The error has already been logged in // getCommandLineArgs. return true; }; defer { for (args) |arg| allocator.free(arg); allocator.free(args); } const allow = pattern_matcher.evaluateAllow(exe_path, args, configuration.include_paths, configuration.include_args); const deny = pattern_matcher.evaluateDeny(exe_path, args, configuration.exclude_paths, configuration.exclude_args); if (!allow or deny) { print.printDebug("executable with path {s} ignored. allow={any}, deny={any}", .{ exe_path, allow, deny }); if (print.isDebug()) { if (configuration.include_paths.len > 0) { print.printDebug(" include_paths:", .{}); for (configuration.include_paths) |pattern| { print.printDebug(" - {s}", .{pattern}); } } if (configuration.include_args.len > 0) { print.printDebug(" include_arguments:", .{}); for (configuration.include_args) |pattern| { print.printDebug(" - {s}", .{pattern}); } } if (configuration.exclude_paths.len > 0) { print.printDebug(" exclude_paths:", .{}); for (configuration.exclude_paths) |pattern| { print.printDebug(" - {s}", .{pattern}); } } if (configuration.exclude_args.len > 0) { print.printDebug(" exclude_arguments:", .{}); for (configuration.exclude_args) |pattern| { print.printDebug(" - {s}", .{pattern}); } } } return false; } return true; } fn getCommandLineArgs(allocator: std.mem.Allocator) ![]const []const u8 { // Get command line arguments. // Dynamically injected libraries don't get std.process.argsAlloc populated and // neither does std.os.argv. We read using the /proc/{pid}/cmdline. const cmdline_args = args_parser.cmdLineForPID(allocator) catch |err| { print.printDebug("failed to get executable arguments: {any}", .{err}); return err; }; if (print.isDebug()) { for (cmdline_args, 0..) |arg, i| { print.printDebug("arg[{d}]: {s}", .{ i, arg }); } } return cmdline_args; } fn getExecutablePath(allocator: std.mem.Allocator) ![]u8 { // Get the program full executable path const executable_path = std.fs.selfExePathAlloc(allocator) catch |err| { print.printDebug("failed to get executable path: {any}", .{err}); return err; }; print.printDebug("executable: {s}", .{executable_path}); return executable_path; } fn modifyEnvironmentVariable( allocator: std.mem.Allocator, lci: types.LibCInfo, name: [:0]const u8, configuration: config.InjectorConfiguration, ) void { if (getEnvValue(allocator, lci, name, configuration)) |value| { // Note: We must *not* free/deallocate the return value of getEnvValue after handing it over to setenv, or we // may cause a USE_AFTER_FREE memory corruption in the parent process. // Note: getEnvValue returns a sentinel-terminated slices, which can be coerced automatically into the // sentinel-terminated many pointer which is required by setenv. const setenv_res = lci.setenv_fn_ptr(name, value, true); if (setenv_res == 0) { print.printDebug( "setting \"{s}\"=\"{s}\"", .{ name, value }, ); } else { print.printError( "failed to set \"{s}\"=\"{s}\", setenv returned: {d}", .{ name, value, setenv_res }, ); } } } fn getEnvValue( allocator: std.mem.Allocator, lci: types.LibCInfo, name: [:0]const u8, configuration: config.InjectorConfiguration, ) ?[:0]const u8 { const original_value = libcGetenv(lci.getenv_fn_ptr, name); if (std.mem.eql(u8, name, jvm.java_tool_options_env_var_name)) { return jvm.checkOTelJavaAgentJarAndGetModifiedJavaToolOptionsValue( allocator, original_value, configuration, ); } else if (std.mem.eql(u8, name, nodejs.node_options_env_var_name)) { return nodejs.checkNodeJsAutoInstrumentationAgentAndGetModifiedNodeOptionsValue( allocator, original_value, configuration, ); } else if (std.mem.eql(u8, name, python.pythonpath_env_var_name)) { return python.checkPythonAutoInstrumentationAgentAndGetModifiedPythonpathValue( allocator, original_value, configuration, ); } else if (std.mem.eql(u8, name, ruby.rubyopt_env_var_name)) { // The two Ruby env vars are a coupled pair: our RUBYOPT entry file requires its bundled OpenTelemetry // dependencies from OTEL_RUBY_ADDITIONAL_GEM_PATH. If the user has pre-set the gem path, we respect // their value (see below) -- so we must also skip RUBYOPT here to avoid a hybrid boot where our entry // file loads but its deps resolve against the user's path (LoadError on every Ruby process). if (libcGetenv(lci.getenv_fn_ptr, ruby.ruby_additional_gem_path_env_var_name) != null) { print.printInfo("Skipping the injection of the Ruby OpenTelemetry auto-instrumentation because \"{s}\" is already set to a user-provided value.", .{ruby.ruby_additional_gem_path_env_var_name}); return null; } return ruby.checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue( allocator, original_value, configuration, ); } else if (std.mem.eql(u8, name, ruby.ruby_additional_gem_path_env_var_name)) { // Respect a user-provided value; only set it ourselves when Ruby injection is active. The RUBYOPT branch // above mirrors this decision by standing down when the user has already set this variable. if (original_value != null) return null; return ruby.getRubyAdditionalGemPath(allocator, configuration); } else if (std.mem.eql(u8, name, dotnet.coreclr_enable_profiling_env_var_name)) { if (dotnet.getDotnetValues(allocator, configuration)) |v| { return v.coreclr_enable_profiling; } } else if (std.mem.eql(u8, name, dotnet.coreclr_profiler_env_var_name)) { if (dotnet.getDotnetValues(allocator, configuration)) |v| { return v.coreclr_profiler; } } else if (std.mem.eql(u8, name, dotnet.coreclr_profiler_path_env_var_name)) { if (dotnet.getDotnetValues(allocator, configuration)) |v| { return v.coreclr_profiler_path; } } else if (std.mem.eql(u8, name, dotnet.dotnet_additional_deps_env_var_name)) { if (dotnet.getDotnetValues(allocator, configuration)) |v| { if (v.additional_deps) |ad| { return ad; } else { // No additional_deps available, return null to skip injection of DOTNET_ADDITIONAL_DEPS. return null; } } } else if (std.mem.eql(u8, name, dotnet.dotnet_shared_store_env_var_name)) { if (dotnet.getDotnetValues(allocator, configuration)) |v| { if (v.shared_store) |ss| { return ss; } else { // No shared_store available, return null to skip injection of DOTNET_SHARED_STORE. return null; } } } else if (std.mem.eql(u8, name, dotnet.dotnet_startup_hooks_env_var_name)) { if (dotnet.getDotnetValues(allocator, configuration)) |v| { return v.startup_hooks; } } else if (std.mem.eql(u8, name, dotnet.otel_dotnet_auto_home_env_var_name)) { if (dotnet.getDotnetValues(allocator, configuration)) |v| { return v.otel_auto_home; } } return null; } fn setCustomEnvironmentVariables( allocator: std.mem.Allocator, lci: types.LibCInfo, custom_env_vars: std.StringHashMap([]u8), ) void { if (custom_env_vars.count() == 0) { return; } var env_var_iterator = custom_env_vars.iterator(); while (env_var_iterator.next()) |env_var| { const name = allocator.dupeZ(u8, env_var.key_ptr.*) catch |err| { print.printError( "error allocating memory for name when setting custom environment variable \"{s}\"=\"{s}\" (remaining custom environment variables will be skipped): {}", .{ env_var.key_ptr.*, env_var.value_ptr.*, err, }, ); return; }; const value = allocator.dupeZ(u8, env_var.value_ptr.*) catch |err| { print.printError( "error allocating memory for value when setting custom environment variable \"{s}\"=\"{s}\" (remaining custom environment variables will be skipped): {}", .{ env_var.key_ptr.*, env_var.value_ptr.*, err, }, ); return; }; const setenv_res = lci.setenv_fn_ptr(name, value, true); if (setenv_res == 0) { print.printDebug("setting \"{s}\"=\"{s}\"", .{ name, value }); } else { print.printError( "failed to set \"{s}\"=\"{s}\", setenv returned: {d}", .{ name, value, setenv_res }, ); } } } open-telemetry-opentelemetry-injector-ea9dca1/src/ruby.zig000066400000000000000000000502111524057645600242410ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const config = @import("config.zig"); const print = @import("print.zig"); const types = @import("types.zig"); const test_util = @import("test_util.zig"); const testing = std.testing; pub const rubyopt_env_var_name = "RUBYOPT"; pub const ruby_additional_gem_path_env_var_name = "OTEL_RUBY_ADDITIONAL_GEM_PATH"; // Packaging contract: within the libc-specific directory (e.g. /glibc) the package must provide a // stable, version-independent entry file at this relative path (a real file or a symlink to the versioned gem). // The injector requires this absolute path via `RUBYOPT=-r ...`; the gem then loads the rest of the // OpenTelemetry gems from OTEL_RUBY_ADDITIONAL_GEM_PATH, which we point at the same directory. const ruby_entry_file_relative_path = "opentelemetry-auto-instrumentation.rb"; var libc_info: ?types.LibCInfo = null; pub fn setLibcInfo(info: types.LibCInfo) void { libc_info = info; } /// Returns the modified value for RUBYOPT, including the `-r ` flag, based on the original value of /// RUBYOPT. Returns null if Ruby auto-instrumentation is disabled, unconfigured, the libc flavor is unknown, or the /// entry file cannot be accessed. /// /// The caller is responsible for freeing the returned string (unless the result is passed on to setenv and needs to /// stay in memory). pub fn checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, configuration: config.InjectorConfiguration, ) ?[:0]u8 { const libc_dir = determineLibcDir(gpa, configuration) orelse return null; defer gpa.free(libc_dir); const entry_file = std.fmt.allocPrintSentinel(gpa, "{s}/{s}", .{ libc_dir, ruby_entry_file_relative_path }, 0) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\": {}", .{ rubyopt_env_var_name, err }); return null; }; defer gpa.free(entry_file); // Stand down if the entry file does not exist; requiring a missing file would crash the Ruby process at startup. std.fs.cwd().access(entry_file, .{}) catch |err| { print.printError("Skipping the injection of the Ruby OpenTelemetry auto-instrumentation in \"{s}\" because of an issue accessing the entry point at \"{s}\": {}", .{ rubyopt_env_var_name, entry_file, err }); return null; }; const require_flag = std.fmt.allocPrintSentinel(gpa, "-r {s}", .{entry_file}, 0) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\": {}", .{ rubyopt_env_var_name, err }); return null; }; return getModifiedRubyoptValue(gpa, original_value_optional, require_flag, entry_file); } /// Returns the value for OTEL_RUBY_ADDITIONAL_GEM_PATH (the libc-specific gem directory) so the auto-instrumentation /// gem can locate its bundled OpenTelemetry dependencies. Returns null under the same conditions as /// checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue -- in particular, the entry file must exist. The /// two env vars share a gate so we never inject one without the other. /// /// The caller is responsible for freeing the returned string (unless it is passed on to setenv). pub fn getRubyAdditionalGemPath( gpa: std.mem.Allocator, configuration: config.InjectorConfiguration, ) ?[:0]u8 { const libc_dir = determineLibcDir(gpa, configuration) orelse return null; const entry_file = std.fmt.allocPrintSentinel(gpa, "{s}/{s}", .{ libc_dir, ruby_entry_file_relative_path }, 0) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\": {}", .{ ruby_additional_gem_path_env_var_name, err }); gpa.free(libc_dir); return null; }; defer gpa.free(entry_file); std.fs.cwd().access(entry_file, .{}) catch |err| { print.printError("Skipping the injection of the Ruby OpenTelemetry auto-instrumentation in \"{s}\" because of an issue accessing the entry point at \"{s}\": {}", .{ ruby_additional_gem_path_env_var_name, entry_file, err }); gpa.free(libc_dir); return null; }; return libc_dir; } /// Resolves / and returns it, or null if disabled, unconfigured, the prefix /// contains whitespace, or the libc flavor is unknown. fn determineLibcDir(gpa: std.mem.Allocator, configuration: config.InjectorConfiguration) ?[:0]u8 { if (configuration.ruby_instrumentation_disabled) { print.printInfo("Skipping the injection of the Ruby OpenTelemetry auto-instrumentation because it has been explicitly disabled.", .{}); return null; } if (configuration.ruby_auto_instrumentation_agent_path_prefix.len == 0) { // The default state on hosts that have the injector but no ruby.conf drop-in. This runs for every // process on the host, so keep the log at debug to avoid flooding operator logs with an intentional // no-op. print.printDebug("Skipping the injection of the Ruby OpenTelemetry auto-instrumentation because it has not been configured (no path prefix set).", .{}); return null; } // Ruby tokenizes RUBYOPT on whitespace like a command line, so a prefix containing whitespace would // either crash every Ruby process with "invalid switch in RUBYOPT" (benign misconfig) or, worse, let // an attacker inject additional Ruby switches such as `-I` to hijack $LOAD_PATH (security). Reject // whitespace at composition time and stand down. for (configuration.ruby_auto_instrumentation_agent_path_prefix) |c| { if (std.ascii.isWhitespace(c)) { print.printError("Skipping the injection of the Ruby OpenTelemetry auto-instrumentation because the configured path prefix \"{s}\" contains a whitespace character; whitespace in the prefix would break Ruby's RUBYOPT tokenization.", .{configuration.ruby_auto_instrumentation_agent_path_prefix}); return null; } } if (libc_info == null) { print.printError("invariant violated: libc info has not been set prior to calling determineLibcDir().", .{}); return null; } // Fail-safe on any libc flavor we don't have a directory suffix for. This covers .UNKNOWN today, and // any variant added to LibCFlavor in the future -- an `else => unreachable` here would panic in every // process's .init_array on hosts running a newly-supported libc before ruby.zig learns about it. const libc_flavor_suffix = switch (libc_info.?.flavor) { .GNU => "glibc", .MUSL => "musl", else => { print.printError("Cannot determine libc flavor for Ruby auto-instrumentation: \"{s}\"", .{@tagName(libc_info.?.flavor)}); return null; }, }; return std.fmt.allocPrintSentinel(gpa, "{s}/{s}", .{ configuration.ruby_auto_instrumentation_agent_path_prefix, libc_flavor_suffix, }, 0) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\" for libc flavor \"{s}\": {}", .{ rubyopt_env_var_name, libc_flavor_suffix, err, }); return null; }; } fn getModifiedRubyoptValue( gpa: std.mem.Allocator, original_value_optional: ?[:0]const u8, require_flag: [:0]u8, entry_file: []const u8, ) ?[:0]u8 { if (original_value_optional) |original_value| { if (rubyoptAlreadyRequires(original_value, entry_file)) { // Our entry file is already required in RUBYOPT, do nothing. This avoids double injection, e.g. when we // inject into a shell entry point that then starts the Ruby process, which inherits the modified env. gpa.free(require_flag); return null; } // RUBYOPT is already set, prepend our `-r ...` flag to the original value. defer gpa.free(require_flag); return std.fmt.allocPrintSentinel(gpa, "{s} {s}", .{ require_flag, original_value }, 0) catch |err| { print.printError("Cannot allocate memory to manipulate the value of \"{s}\": {}", .{ rubyopt_env_var_name, err }); return null; }; } // RUBYOPT is not set, simply return the `-r ...` flag. return require_flag[0..]; } /// Returns true when `rubyopt_value` already requires `entry_file` via `-r`. Mirrors Ruby's RUBYOPT /// parsing: whitespace-tokenized, and each `-r` token can be followed by the path as the next token /// (`-r PATH`) or attached to the flag (`-rPATH`). Substring matching is not enough -- a coincidental /// `-r .disabled` would falsely suppress our injection, and Ruby's no-space attached form /// would slip past a `"-r " ++ path` substring check and cause double injection. fn rubyoptAlreadyRequires(rubyopt_value: []const u8, entry_file: []const u8) bool { var tokens = std.mem.tokenizeAny(u8, rubyopt_value, " \t\r\n"); while (tokens.next()) |token| { if (std.mem.eql(u8, token, "-r")) { if (tokens.next()) |next_token| { if (std.mem.eql(u8, next_token, entry_file)) return true; } } else if (std.mem.startsWith(u8, token, "-r") and std.mem.eql(u8, token[2..], entry_file)) { return true; } } return false; } test "checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue: returns null if ruby instrumentation is disabled" { const allocator = testing.allocator; _resetState(); defer _resetState(); libc_info = test_util.testLibcInfo(.GNU); const configuration = testConfiguration("/some/valid/path", true); const result = checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration); try test_util.expectWithMessage(result == null, "result == null"); } test "checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue: returns null if path prefix is empty" { const allocator = testing.allocator; _resetState(); defer _resetState(); libc_info = test_util.testLibcInfo(.GNU); const configuration = testConfiguration("", false); const result = checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration); try test_util.expectWithMessage(result == null, "result == null"); } test "checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue: returns null if libc flavor is unknown" { const allocator = testing.allocator; _resetState(); defer _resetState(); libc_info = test_util.testLibcInfo(.UNKNOWN); const configuration = testConfiguration("/some/valid/path", false); const result = checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration); try test_util.expectWithMessage(result == null, "result == null"); } test "checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue: returns null if entry file cannot be accessed" { const allocator = testing.allocator; _resetState(); defer _resetState(); libc_info = test_util.testLibcInfo(.GNU); const configuration = testConfiguration("/invalid/path", false); const result = checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration); try test_util.expectWithMessage(result == null, "result == null"); } test "checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue: returns null if prefix contains whitespace" { const allocator = testing.allocator; _resetState(); defer _resetState(); libc_info = test_util.testLibcInfo(.GNU); // Rejected because a space in the prefix would let Ruby's RUBYOPT tokenizer parse additional switches. const configuration_space = testConfiguration("/opt/OpenTelemetry Injector/ruby", false); try test_util.expectWithMessage( checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration_space) == null, "space in prefix -> null", ); // Tab, newline, and carriage return also break RUBYOPT tokenization. const configuration_tab = testConfiguration("/opt/otel\tinjector/ruby", false); try test_util.expectWithMessage( checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration_tab) == null, "tab in prefix -> null", ); const configuration_newline = testConfiguration("/opt/otel\ninjector/ruby", false); try test_util.expectWithMessage( checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration_newline) == null, "newline in prefix -> null", ); const configuration_cr = testConfiguration("/opt/otel\rinjector/ruby", false); try test_util.expectWithMessage( checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration_cr) == null, "cr in prefix -> null", ); // Vertical tab and form feed round out ASCII whitespace. const configuration_vt = testConfiguration("/opt/otel\x0binjector/ruby", false); try test_util.expectWithMessage( checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration_vt) == null, "vertical tab in prefix -> null", ); const configuration_ff = testConfiguration("/opt/otel\x0cinjector/ruby", false); try test_util.expectWithMessage( checkRubyAutoInstrumentationAgentAndGetModifiedRubyoptValue(allocator, null, configuration_ff) == null, "form feed in prefix -> null", ); } test "getRubyAdditionalGemPath: returns /glibc for GNU libc" { const allocator = testing.allocator; _resetState(); defer _resetState(); var tmp_dir = testing.tmpDir(.{}); defer tmp_dir.cleanup(); try tmp_dir.dir.makeDir("glibc"); (try tmp_dir.dir.createFile("glibc/" ++ ruby_entry_file_relative_path, .{})).close(); const prefix = try tmp_dir.dir.realpathAlloc(allocator, "."); defer allocator.free(prefix); libc_info = test_util.testLibcInfo(.GNU); const configuration = testConfiguration(prefix, false); const result = getRubyAdditionalGemPath(allocator, configuration); defer if (result) |v| allocator.free(v); const expected = try std.fmt.allocPrint(allocator, "{s}/glibc", .{prefix}); defer allocator.free(expected); try testing.expectEqualStrings(expected, result orelse "-"); } test "getRubyAdditionalGemPath: returns /musl for musl libc" { const allocator = testing.allocator; _resetState(); defer _resetState(); var tmp_dir = testing.tmpDir(.{}); defer tmp_dir.cleanup(); try tmp_dir.dir.makeDir("musl"); (try tmp_dir.dir.createFile("musl/" ++ ruby_entry_file_relative_path, .{})).close(); const prefix = try tmp_dir.dir.realpathAlloc(allocator, "."); defer allocator.free(prefix); libc_info = test_util.testLibcInfo(.MUSL); const configuration = testConfiguration(prefix, false); const result = getRubyAdditionalGemPath(allocator, configuration); defer if (result) |v| allocator.free(v); const expected = try std.fmt.allocPrint(allocator, "{s}/musl", .{prefix}); defer allocator.free(expected); try testing.expectEqualStrings(expected, result orelse "-"); } test "getRubyAdditionalGemPath: returns null if entry file cannot be accessed" { const allocator = testing.allocator; _resetState(); defer _resetState(); libc_info = test_util.testLibcInfo(.GNU); const configuration = testConfiguration("/invalid/path", false); const result = getRubyAdditionalGemPath(allocator, configuration); try test_util.expectWithMessage(result == null, "result == null"); } test "getModifiedRubyoptValue: returns -r flag if original value is unset" { const allocator = testing.allocator; const entry_file = "/usr/lib/opentelemetry/ruby/glibc/opentelemetry-auto-instrumentation.rb"; const require_flag = try std.fmt.allocPrintSentinel(allocator, "-r {s}", .{entry_file}, 0); const result = getModifiedRubyoptValue(allocator, null, require_flag, entry_file); defer if (result) |v| allocator.free(v); try testing.expectEqualStrings( "-r /usr/lib/opentelemetry/ruby/glibc/opentelemetry-auto-instrumentation.rb", result orelse "-", ); } test "getModifiedRubyoptValue: prepends -r flag if original value exists" { const allocator = testing.allocator; const original_value: [:0]const u8 = "--enable-frozen-string-literal"[0.. :0]; const entry_file = "/opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb"; const require_flag = try std.fmt.allocPrintSentinel(allocator, "-r {s}", .{entry_file}, 0); const result = getModifiedRubyoptValue(allocator, original_value, require_flag, entry_file); defer if (result) |v| allocator.free(v); try testing.expectEqualStrings( "-r /opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb --enable-frozen-string-literal", result orelse "-", ); } test "getModifiedRubyoptValue: does nothing if our -r flag is already present (space form)" { const allocator = testing.allocator; const original_value: [:0]const u8 = "--yjit -r /opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb --enable-frozen-string-literal"[0.. :0]; const entry_file = "/opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb"; const require_flag = try std.fmt.allocPrintSentinel(allocator, "-r {s}", .{entry_file}, 0); const result = getModifiedRubyoptValue(allocator, original_value, require_flag, entry_file); try test_util.expectWithMessage(result == null, "result == null"); } test "getModifiedRubyoptValue: does nothing if our -r flag is already present (attached -rPATH form)" { const allocator = testing.allocator; // Ruby accepts `-rPATH` (no space between flag and path) as an equivalent to `-r PATH`. const original_value: [:0]const u8 = "--yjit -r/opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb --enable-frozen-string-literal"[0.. :0]; const entry_file = "/opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb"; const require_flag = try std.fmt.allocPrintSentinel(allocator, "-r {s}", .{entry_file}, 0); const result = getModifiedRubyoptValue(allocator, original_value, require_flag, entry_file); try test_util.expectWithMessage(result == null, "result == null"); } test "getModifiedRubyoptValue: prepends when RUBYOPT contains our path as a substring of a different token" { const allocator = testing.allocator; // A coincidental `-r .disabled` must not suppress our injection. const entry_file = "/opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb"; const original_value: [:0]const u8 = "-r /opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb.disabled"[0.. :0]; const require_flag = try std.fmt.allocPrintSentinel(allocator, "-r {s}", .{entry_file}, 0); const result = getModifiedRubyoptValue(allocator, original_value, require_flag, entry_file); defer if (result) |v| allocator.free(v); try testing.expectEqualStrings( "-r /opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb -r /opt/otel/ruby/glibc/opentelemetry-auto-instrumentation.rb.disabled", result orelse "-", ); } /// Builds a minimal InjectorConfiguration for unit tests. Only the Ruby-relevant fields are meaningful. fn testConfiguration(path_prefix: []const u8, disabled: bool) config.InjectorConfiguration { return config.InjectorConfiguration{ .dotnet_auto_instrumentation_agent_path_prefix = @constCast(""), .jvm_auto_instrumentation_agent_path = @constCast(""), .nodejs_auto_instrumentation_agent_path = @constCast(""), .python_auto_instrumentation_agent_path_prefix = @constCast(""), .ruby_auto_instrumentation_agent_path_prefix = @constCast(path_prefix), .all_auto_instrumentation_agents_env_path = @constCast(""), .all_auto_instrumentation_agents_env_vars = std.StringHashMap([]u8).init(testing.allocator), .include_paths = &.{}, .exclude_paths = &.{}, .include_args = &.{}, .exclude_args = &.{}, .dotnet_auto_instrumentation_minimum_dotnet_major_version = config.default_dotnet_auto_instrumentation_minimum_dotnet_major_version, .dotnet_instrumentation_disabled = false, .jvm_instrumentation_disabled = false, .nodejs_instrumentation_disabled = false, .python_instrumentation_disabled = false, .ruby_instrumentation_disabled = disabled, }; } /// Only used for unit tests. fn _resetState() void { libc_info = null; } open-telemetry-opentelemetry-injector-ea9dca1/src/test.zig000066400000000000000000000015441524057645600242440ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // All files with unit tests need to be referenced here: pub const arg_parser = @import("args_parser.zig"); pub const auxv = @import("auxv.zig"); pub const config = @import("config.zig"); pub const dotnet = @import("dotnet.zig"); pub const libc = @import("libc.zig"); pub const jvm = @import("jvm.zig"); pub const nodejs = @import("nodejs.zig"); pub const patterns_matcher = @import("patterns_matcher.zig"); pub const patterns_util = @import("patterns_util.zig"); pub const proc_self_environ_parser = @import("proc_self_environ_parser.zig"); pub const print = @import("print.zig"); pub const python = @import("python.zig"); pub const ruby = @import("ruby.zig"); pub const res_attrs_test = @import("resource_attributes_test.zig"); test { @import("std").testing.refAllDecls(@This()); } open-telemetry-opentelemetry-injector-ea9dca1/src/test_util.zig000066400000000000000000000145271524057645600253060ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // **Note: This file must only be imported from *_test.zig files, never from actual production code zig files.** const std = @import("std"); const types = @import("types.zig"); const _print = @import("print.zig"); const testing = std.testing; // An allocator just for clearStdCEnviron/setStdCEnviron. Since this is never part of production code but only used in // tests, it is not a concern whether we leak memory in these two helper functions, so there is no need to use // std.test.allocator here. const environ_allocator = std.heap.page_allocator; /// Clears all entries from std.c.environ, i.e. all environment variables are discarded. The original content before /// making any changes is returned. The caller is expected to reset std.c.environ to the return value of this function /// when the test is done, for example by calling `defer resetStdCEnviron(original_environ);` directly after calling /// this function (where original_environ is the return value of this function). pub fn clearStdCEnviron() anyerror![*:null]?[*:0]u8 { const original_environ = std.c.environ; const new_environ = try environ_allocator.allocSentinel(?[*:0]u8, 0, null); std.c.environ = new_environ; return original_environ; } /// Sets the given key-value pairs as the only content of std.c.environ. Everything else in std.c.environ is discarded. /// The original content before making any changes is returned. The caller is expected to reset std.c.environ to /// the return value of this function when the test is done, for example by calling /// `defer resetStdCEnviron(original_environ);` directly after calling this function (where original_environ is the /// return value of this function). pub fn setStdCEnviron(env_vars: []const []const u8) anyerror![*:null]?[*:0]u8 { const original_environ = std.c.environ; // For some reason, the tests run with builtin.link_libc=true although test_mod in build.zig is configured with // .link_libc = false. This in turn makes makes std.posix.getenv use std.c.environ instead of std.os.environ. // Hence, for tests that require certain environment variables to be set, we mess around with std.c.environ. // Note: To manipulate std.os.environ instead of std.c.environ, use allocator.alloc([*:0]u8, n); instead of // allocator.allocSentinel(?[*:0]u8, n, null). const new_environ = try environ_allocator.allocSentinel(?[*:0]u8, env_vars.len, null); for (env_vars, 0..) |env_var, i| { new_environ[i] = try std.fmt.allocPrintSentinel( environ_allocator, "{s}", .{env_var}, 0, ); } std.c.environ = new_environ; return original_environ; } /// Resets std.c.environ to the given value. This function is meant to be used after doing clearStdCEnviron or /// setStdCEnviron earlier, to restore the original environment after the test is done. pub fn resetStdCEnviron(original_environ: [*:null]?[*:0]u8) void { std.c.environ = original_environ; } /// A GetenvFnPtr-compatible wrapper around std.posix.getenv for use in tests. /// Tests manipulate std.c.environ via setStdCEnviron/clearStdCEnviron, and std.posix.getenv /// reads from std.c.environ when link_libc is true (which is the case at test runtime). pub fn testGetenvFnPtr(name: [*:0]const u8) ?[*:0]const u8 { const val = std.posix.getenv(std.mem.span(name)) orelse return null; // val.ptr points into a null-terminated string in std.c.environ, safe to cast. return @ptrCast(val.ptr); } fn testNoopSetenvFnPtr(name: [*:0]const u8, value: [*:0]const u8, overwrite: bool) c_int { _ = name; _ = value; _ = overwrite; return 0; } /// Returns a LibCInfo suitable for unit tests. The setenv function pointer is a no-op /// since tests that use this helper do not exercise the actual setenv path. pub fn testLibcInfo(flavor: types.LibCFlavor) types.LibCInfo { return .{ .flavor = flavor, .getenv_fn_ptr = testGetenvFnPtr, .setenv_fn_ptr = testNoopSetenvFnPtr, }; } /// An extended version of std.testing.expect that prints a message in case of failure. Useful for tests that have /// multiple expects; this version should generally be used in favor of the original std.testing.expect function. pub fn expectWithMessage( ok: bool, comptime message: []const u8, ) !void { if (!ok) { print("\n====== assertion failed: =========\n", .{}); print(message, .{}); print("\n==================================\n", .{}); return error.TestUnexpectedResult; } } pub fn expectStringStartAndEnd(actual: []const u8, prefix: []const u8, suffix: []const u8) !void { try testing.expectStringStartsWith(actual, prefix); try testing.expectStringEndsWith(actual, suffix); } pub fn expectStringContains(actual: []const u8, expected_needle: []const u8) !void { if (std.mem.indexOf(u8, actual, expected_needle) != null) { return; } print("\n====== expected to contain: =========\n", .{}); printWithVisibleNewlines(expected_needle); print("\n========= full output: ==============\n", .{}); printWithVisibleNewlines(actual); print("\n======================================\n", .{}); return error.TestExpectedContains; } pub inline fn expectMemoryRangeLimit(expected: comptime_int, actual: anytype) !void { testing.expectEqual(expected, @intFromPtr(actual)) catch |err| { std.debug.print("expected memory range limit {x:0>8}, found {x:0>8}\n", .{ expected, @intFromPtr(actual) }); return err; }; } // Copied from Zig's std.testing module. fn print(comptime fmt: []const u8, args: anytype) void { if (@inComptime()) { @compileError(std.fmt.comptimePrint(fmt, args)); } else if (testing.backend_can_print) { std.debug.print(fmt, args); } } // Copied from Zig's std.testing module. fn printWithVisibleNewlines(source: []const u8) void { var i: usize = 0; while (std.mem.indexOfScalar(u8, source[i..], '\n')) |nl| : (i += nl + 1) { printLine(source[i..][0..nl]); } print("{s}â�ƒ\n", .{source[i..]}); // End of Text symbol (ETX) } // Copied from Zig's std.testing module. fn printLine(line: []const u8) void { if (line.len != 0) switch (line[line.len - 1]) { ' ', '\t' => return print("{s}â�Ž\n", .{line}), // Return symbol else => {}, }; print("{s}\n", .{line}); } open-telemetry-opentelemetry-injector-ea9dca1/src/types.zig000066400000000000000000000010061524057645600244220ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 pub const DlSymFn = *const fn (handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque; pub const GetenvFnPtr = *const fn (name: [*:0]const u8) ?[*:0]const u8; pub const SetenvFnPtr = *const fn (name: [*:0]const u8, value: [*:0]const u8, overwrite: bool) c_int; pub const LibCFlavor = enum { UNKNOWN, GNU, MUSL }; pub const LibCInfo = struct { flavor: LibCFlavor, getenv_fn_ptr: GetenvFnPtr, setenv_fn_ptr: SetenvFnPtr, }; open-telemetry-opentelemetry-injector-ea9dca1/start-injector-dev-container.sh000077500000000000000000000017041524057645600300240ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" # See https://ziglang.org/download/ for the correct strings for zig_architecture. (Needs to match the architecture part # of the download URL.) ARCH="${ARCH:-amd64}" if [[ "$ARCH" = arm64 ]]; then docker_platform=linux/arm64 zig_architecture=aarch64 elif [[ "$ARCH" = amd64 ]]; then docker_platform=linux/amd64 zig_architecture=x86_64 else echo "The architecture $ARCH is not supported." exit 1 fi image_name="otel-injector-dev-$ARCH" docker rmi -f "$image_name" 2> /dev/null || true docker build \ --platform "$docker_platform" \ --build-arg "zig_architecture=${zig_architecture}" \ -f devel.Dockerfile \ -t "$image_name" \ . container_name="$image_name" docker rm -f "$container_name" 2> /dev/null || true docker run \ --platform "$docker_platform" \ --rm \ -it \ --name "$container_name" \ --volume "$(pwd):/injector" \ "$image_name" \ /bin/bash open-telemetry-opentelemetry-injector-ea9dca1/tooling/000077500000000000000000000000001524057645600234325ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/tooling/nextversion/000077500000000000000000000000001524057645600260165ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/tooling/nextversion/go.mod000066400000000000000000000001661524057645600271270ustar00rootroot00000000000000module github.com/open-telemetry/opentelemetry-injector/tooling/nextversion go 1.23 require gopkg.in/yaml.v3 v3.0.1 open-telemetry-opentelemetry-injector-ea9dca1/tooling/nextversion/go.sum000066400000000000000000000005501524057645600271510ustar00rootroot00000000000000gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= open-telemetry-opentelemetry-injector-ea9dca1/tooling/nextversion/main.go000066400000000000000000000101311524057645600272650ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "errors" "flag" "fmt" "os" "os/exec" "path/filepath" "strings" "gopkg.in/yaml.v3" ) func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) } } func run() error { var ( override string prerelease bool chloggenDir string gitTagOutput string ) flag.StringVar(&override, "override", "auto", "override the auto-derived bump: auto, major, minor, or patch") flag.BoolVar(&prerelease, "prerelease", false, "when true, produce the next v-rc. instead of the stable version") flag.StringVar(&chloggenDir, "chloggen-dir", ".chloggen", "directory containing chloggen entry files") flag.StringVar(&gitTagOutput, "git-tag-output", "", "(testing) read the tag list from this file instead of running git") flag.Parse() tags, err := loadTags(gitTagOutput) if err != nil { return fmt.Errorf("listing git tags: %w", err) } latest, hasLatest := LatestStable(tags) if !hasLatest { fmt.Fprintln(os.Stderr, "No prior stable release tag found; starting from v0.0.0.") } else { fmt.Fprintf(os.Stderr, "Latest stable release tag: %s\n", latest) } kind, err := resolveBump(override, chloggenDir) if err != nil { return err } next := latest.Apply(kind) if prerelease { idx := NextRCIndex(tags, next) next = next.WithPreRelease(fmt.Sprintf("rc.%d", idx)) } fmt.Fprintf(os.Stderr, "Next release version (%s bump): %s\n", kind, next) fmt.Println(next) return writeGitHubOutput(next, kind) } func loadTags(fromFile string) ([]string, error) { if fromFile != "" { data, err := os.ReadFile(fromFile) if err != nil { return nil, err } return splitLines(string(data)), nil } cmd := exec.Command("git", "tag") out, err := cmd.Output() if err != nil { return nil, err } return splitLines(string(out)), nil } func splitLines(s string) []string { s = strings.TrimSpace(s) if s == "" { return nil } return strings.Split(s, "\n") } func resolveBump(override, chloggenDir string) (BumpKind, error) { if override != "" && override != "auto" { kind, err := ParseBumpKind(override) if err != nil { return BumpNone, err } fmt.Fprintf(os.Stderr, "Bump overridden to: %s\n", kind) return kind, nil } changeTypes, err := readChloggenChangeTypes(chloggenDir) if err != nil { return BumpNone, fmt.Errorf("reading chloggen entries from %s: %w", chloggenDir, err) } kind, err := DeriveBump(changeTypes) if err != nil { if errors.Is(err, ErrNoEntries) { return BumpNone, fmt.Errorf("no chloggen entries in %s -- either add one or pass -override=patch", chloggenDir) } return BumpNone, err } fmt.Fprintf(os.Stderr, "Bump derived from %d chloggen entries: %s\n", len(changeTypes), kind) return kind, nil } // readChloggenChangeTypes loads *.yaml files from dir, skipping the template // and config files that live alongside the entries, and returns the // change_type of each entry in file-order. func readChloggenChangeTypes(dir string) ([]ChangeType, error) { matches, err := filepath.Glob(filepath.Join(dir, "*.yaml")) if err != nil { return nil, err } var types []ChangeType for _, p := range matches { base := filepath.Base(p) if base == "TEMPLATE.yaml" || base == "config.yaml" { continue } data, err := os.ReadFile(p) if err != nil { return nil, err } var entry struct { ChangeType string `yaml:"change_type"` } if err := yaml.Unmarshal(data, &entry); err != nil { return nil, fmt.Errorf("%s: %w", p, err) } if entry.ChangeType == "" { return nil, fmt.Errorf("%s: empty change_type", p) } types = append(types, ChangeType(entry.ChangeType)) } return types, nil } func writeGitHubOutput(v Semver, kind BumpKind) error { path := os.Getenv("GITHUB_OUTPUT") if path == "" { return nil } f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return fmt.Errorf("open GITHUB_OUTPUT: %w", err) } defer f.Close() if _, err := fmt.Fprintf(f, "version=%s\nbump=%s\n", v, kind); err != nil { return fmt.Errorf("write GITHUB_OUTPUT: %w", err) } return nil } open-telemetry-opentelemetry-injector-ea9dca1/tooling/nextversion/main_test.go000066400000000000000000000120111524057645600303230ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "os" "path/filepath" "reflect" "strings" "testing" ) func TestLoadTagsFromFile(t *testing.T) { dir := t.TempDir() tags := "v0.9.2\nv0.9.1\nv1.0.0-rc1\nrandom-tag\n" path := filepath.Join(dir, "tags") if err := os.WriteFile(path, []byte(tags), 0600); err != nil { t.Fatal(err) } got, err := loadTags(path) if err != nil { t.Fatal(err) } want := []string{"v0.9.2", "v0.9.1", "v1.0.0-rc1", "random-tag"} if !reflect.DeepEqual(got, want) { t.Errorf("loadTags = %v; want %v", got, want) } } func TestLoadTagsFromFileEmpty(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "tags") if err := os.WriteFile(path, []byte("\n\n"), 0600); err != nil { t.Fatal(err) } got, err := loadTags(path) if err != nil { t.Fatal(err) } if len(got) != 0 { t.Errorf("loadTags on empty file = %v; want nil", got) } } func TestReadChloggenChangeTypes(t *testing.T) { dir := t.TempDir() // Files that should be skipped by name. writeFile(t, filepath.Join(dir, "TEMPLATE.yaml"), "change_type: enhancement\n") writeFile(t, filepath.Join(dir, "config.yaml"), "entries_dir: .\n") // A markdown file that must not be picked up. writeFile(t, filepath.Join(dir, "notes.md"), "not yaml\n") // Actual entries. writeFile(t, filepath.Join(dir, "a-fix.yaml"), "change_type: bug_fix\ncomponent: injector\n") writeFile(t, filepath.Join(dir, "b-feature.yaml"), "change_type: enhancement\ncomponent: injector\n") got, err := readChloggenChangeTypes(dir) if err != nil { t.Fatal(err) } want := []ChangeType{ChangeBugFix, ChangeEnhancement} if !reflect.DeepEqual(got, want) { t.Errorf("readChloggenChangeTypes = %v; want %v", got, want) } } func TestReadChloggenChangeTypesMissingChangeType(t *testing.T) { dir := t.TempDir() writeFile(t, filepath.Join(dir, "bad.yaml"), "component: injector\n") _, err := readChloggenChangeTypes(dir) if err == nil { t.Fatal("expected error for missing change_type; got nil") } if !strings.Contains(err.Error(), "empty change_type") { t.Errorf("unexpected error: %v", err) } } func TestReadChloggenChangeTypesMalformedYAML(t *testing.T) { dir := t.TempDir() writeFile(t, filepath.Join(dir, "bad.yaml"), ": : : not-yaml\n") _, err := readChloggenChangeTypes(dir) if err == nil { t.Fatal("expected error for malformed yaml; got nil") } } func TestReadChloggenChangeTypesEmptyDirIsNoEntries(t *testing.T) { dir := t.TempDir() got, err := readChloggenChangeTypes(dir) if err != nil { t.Fatal(err) } if len(got) != 0 { t.Errorf("expected no entries; got %v", got) } // And DeriveBump on an empty slice yields ErrNoEntries -- covered in // nextversion_test.go, but reconfirm the composed behavior here. if _, err := DeriveBump(got); err == nil { t.Fatal("DeriveBump on empty entries should error") } } func TestResolveBumpOverride(t *testing.T) { // override wins even if chloggen dir does not exist. got, err := resolveBump("patch", filepath.Join(t.TempDir(), "does-not-exist")) if err != nil { t.Fatal(err) } if got != BumpPatch { t.Errorf("got %v; want BumpPatch", got) } } func TestResolveBumpAutoDerives(t *testing.T) { dir := t.TempDir() writeFile(t, filepath.Join(dir, "a.yaml"), "change_type: breaking\ncomponent: x\n") got, err := resolveBump("auto", dir) if err != nil { t.Fatal(err) } if got != BumpMajor { t.Errorf("got %v; want BumpMajor", got) } } func TestResolveBumpAutoNoEntriesWrapsError(t *testing.T) { dir := t.TempDir() _, err := resolveBump("auto", dir) if err == nil { t.Fatal("expected error; got nil") } if !strings.Contains(err.Error(), "no chloggen entries") { t.Errorf("expected 'no chloggen entries' hint, got: %v", err) } } func TestWriteGitHubOutput(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "out") t.Setenv("GITHUB_OUTPUT", path) if err := writeGitHubOutput(Semver{Major: 0, Minor: 10, Patch: 0}, BumpMinor); err != nil { t.Fatal(err) } data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } want := "version=v0.10.0\nbump=minor\n" if string(data) != want { t.Errorf("got %q; want %q", string(data), want) } } func TestWriteGitHubOutputAppends(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "out") if err := os.WriteFile(path, []byte("previous=value\n"), 0600); err != nil { t.Fatal(err) } t.Setenv("GITHUB_OUTPUT", path) if err := writeGitHubOutput(Semver{Major: 1, Minor: 0, Patch: 0}, BumpMajor); err != nil { t.Fatal(err) } data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } want := "previous=value\nversion=v1.0.0\nbump=major\n" if string(data) != want { t.Errorf("got %q; want %q", string(data), want) } } func TestWriteGitHubOutputEmptyEnvIsNoop(t *testing.T) { t.Setenv("GITHUB_OUTPUT", "") if err := writeGitHubOutput(Semver{Major: 0, Minor: 1, Patch: 0}, BumpMinor); err != nil { t.Fatal(err) } } func writeFile(t *testing.T, path, contents string) { t.Helper() if err := os.WriteFile(path, []byte(contents), 0600); err != nil { t.Fatal(err) } } open-telemetry-opentelemetry-injector-ea9dca1/tooling/nextversion/nextversion.go000066400000000000000000000147671524057645600307500ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 // Package main implements a small tool that computes the next release version // by combining the highest existing stable git tag with a bump kind. The bump // kind is derived from the change_type field of chloggen entries in .chloggen/ // unless overridden on the command line. // // This file contains only pure functions and types; all I/O (git, filesystem, // GitHub Actions outputs) lives in main.go. package main import ( "errors" "fmt" "regexp" "strconv" "strings" ) // Semver is a bare-bones vMAJOR.MINOR.PATCH version, optionally carrying a // SemVer 2.0.0 pre-release suffix (e.g. "rc.1"). Build metadata is not // modeled. The pre-release field is only ever populated on the *output* // version; parsed tags always have an empty PreRelease -- LatestStable // deliberately skips pre-release tags so the next release is computed from // the last stable base. type Semver struct { Major int Minor int Patch int PreRelease string } var stableSemverRe = regexp.MustCompile(`^v(\d+)\.(\d+)\.(\d+)$`) // ParseStableSemver parses "vX.Y.Z" (no pre-release suffix). Anything else // returns ok=false so callers can filter tag lists in one pass. func ParseStableSemver(s string) (Semver, bool) { m := stableSemverRe.FindStringSubmatch(strings.TrimSpace(s)) if m == nil { return Semver{}, false } maj, _ := strconv.Atoi(m[1]) min, _ := strconv.Atoi(m[2]) pat, _ := strconv.Atoi(m[3]) return Semver{Major: maj, Minor: min, Patch: pat}, true } func (v Semver) String() string { base := fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch) if v.PreRelease == "" { return base } return base + "-" + v.PreRelease } // WithPreRelease returns a copy of v with the given SemVer 2.0.0 pre-release // identifier attached. An empty argument clears any existing suffix. func (v Semver) WithPreRelease(pre string) Semver { v.PreRelease = pre return v } // rcTagRe extracts the rc index from a "vX.Y.Z-rc.N" tag matching the given // base version. Built dynamically by NextRCIndex so the base X.Y.Z is // literal, and only rc.N is captured. func rcTagRe(base Semver) *regexp.Regexp { pattern := fmt.Sprintf(`^v%d\.%d\.%d-rc\.(\d+)$`, base.Major, base.Minor, base.Patch) return regexp.MustCompile(pattern) } // NextRCIndex returns the next rc index for base by scanning tags for // existing "v-rc.N" entries and taking max(N)+1. Returns 1 when no // prior rc exists. Ignores rc tags whose numeric part has a leading zero // (e.g. "-rc.01"), since we never emit those and treating them as valid // would produce a duplicate tag on the next run. func NextRCIndex(tags []string, base Semver) int { re := rcTagRe(base) best := 0 for _, t := range tags { m := re.FindStringSubmatch(strings.TrimSpace(t)) if m == nil { continue } if len(m[1]) > 1 && m[1][0] == '0' { continue } n, err := strconv.Atoi(m[1]) if err != nil { continue } if n > best { best = n } } return best + 1 } // Less reports whether v is strictly older than o. func (v Semver) Less(o Semver) bool { if v.Major != o.Major { return v.Major < o.Major } if v.Minor != o.Minor { return v.Minor < o.Minor } return v.Patch < o.Patch } // LatestStable picks the highest vX.Y.Z tag from the given list, skipping // anything that isn't a stable semver tag. Returns ok=false when the input // contains no stable tags. func LatestStable(tags []string) (Semver, bool) { var best Semver found := false for _, t := range tags { v, ok := ParseStableSemver(t) if !ok { continue } if !found || best.Less(v) { best = v found = true } } return best, found } // BumpKind enumerates the semver components in ascending priority order, so // numeric comparison picks the strongest bump when combining classifications. type BumpKind int const ( BumpNone BumpKind = iota BumpPatch BumpMinor BumpMajor ) func (b BumpKind) String() string { switch b { case BumpPatch: return "patch" case BumpMinor: return "minor" case BumpMajor: return "major" default: return "none" } } // ParseBumpKind parses the CLI-facing spelling of a bump kind. func ParseBumpKind(s string) (BumpKind, error) { switch s { case "patch": return BumpPatch, nil case "minor": return BumpMinor, nil case "major": return BumpMajor, nil } return BumpNone, fmt.Errorf("invalid bump kind %q (want major, minor, or patch)", s) } // Apply returns the version resulting from bumping v by kind. // // While v is pre-1.0.0, a "major" bump is downgraded to a minor bump per the // common 0.y.z convention: breaking changes may happen in a minor release // until the project commits to a stable v1. func (v Semver) Apply(kind BumpKind) Semver { switch kind { case BumpMajor: if v.Major == 0 { return Semver{Major: 0, Minor: v.Minor + 1, Patch: 0} } return Semver{Major: v.Major + 1, Minor: 0, Patch: 0} case BumpMinor: return Semver{Major: v.Major, Minor: v.Minor + 1, Patch: 0} case BumpPatch: return Semver{Major: v.Major, Minor: v.Minor, Patch: v.Patch + 1} } return v } // ChangeType is the string chloggen writes into an entry file's change_type // field. The known values are enumerated below. type ChangeType string const ( ChangeBreaking ChangeType = "breaking" ChangeDeprecation ChangeType = "deprecation" ChangeNewComponent ChangeType = "new_component" ChangeEnhancement ChangeType = "enhancement" ChangeBugFix ChangeType = "bug_fix" ) // ClassifyChangeType maps a chloggen change_type to the bump kind it // warrants. Returns BumpNone for unknown values so callers can flag them. func ClassifyChangeType(ct ChangeType) BumpKind { switch ct { case ChangeBreaking: return BumpMajor case ChangeDeprecation, ChangeNewComponent, ChangeEnhancement: return BumpMinor case ChangeBugFix: return BumpPatch } return BumpNone } // ErrNoEntries is returned when auto-derivation is requested but no chloggen // entries were provided. var ErrNoEntries = errors.New("no chloggen entries found") // DeriveBump picks the strongest bump warranted by the given change types. // An unknown change type is treated as a fatal error rather than silently // ignored -- releases should never be classified from partial information. func DeriveBump(changeTypes []ChangeType) (BumpKind, error) { if len(changeTypes) == 0 { return BumpNone, ErrNoEntries } best := BumpNone for _, ct := range changeTypes { k := ClassifyChangeType(ct) if k == BumpNone { return BumpNone, fmt.Errorf("unknown change_type %q", string(ct)) } if k > best { best = k } } return best, nil } open-telemetry-opentelemetry-injector-ea9dca1/tooling/nextversion/nextversion_test.go000066400000000000000000000210371524057645600317730ustar00rootroot00000000000000// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package main import ( "errors" "testing" ) func TestParseStableSemver(t *testing.T) { cases := []struct { in string want Semver wantOK bool }{ {"v0.9.2", Semver{Major: 0, Minor: 9, Patch: 2}, true}, {"v1.0.0", Semver{Major: 1, Minor: 0, Patch: 0}, true}, {"v10.20.30", Semver{Major: 10, Minor: 20, Patch: 30}, true}, {" v1.2.3 ", Semver{Major: 1, Minor: 2, Patch: 3}, true}, {"v0.9.2-rc1", Semver{}, false}, {"v0.9.2-20260101", Semver{}, false}, {"0.9.2", Semver{}, false}, {"random-tag", Semver{}, false}, {"", Semver{}, false}, } for _, c := range cases { got, ok := ParseStableSemver(c.in) if ok != c.wantOK || got != c.want { t.Errorf("ParseStableSemver(%q) = (%v, %v); want (%v, %v)", c.in, got, ok, c.want, c.wantOK) } } } func TestSemverLess(t *testing.T) { cases := []struct { a, b Semver want bool }{ {Semver{Major: 0, Minor: 9, Patch: 2}, Semver{Major: 0, Minor: 10, Patch: 0}, true}, {Semver{Major: 0, Minor: 10, Patch: 0}, Semver{Major: 0, Minor: 9, Patch: 2}, false}, {Semver{Major: 1, Minor: 0, Patch: 0}, Semver{Major: 0, Minor: 99, Patch: 99}, false}, {Semver{Major: 0, Minor: 9, Patch: 2}, Semver{Major: 0, Minor: 9, Patch: 2}, false}, {Semver{Major: 0, Minor: 9, Patch: 2}, Semver{Major: 0, Minor: 9, Patch: 3}, true}, } for _, c := range cases { if got := c.a.Less(c.b); got != c.want { t.Errorf("(%s).Less(%s) = %v; want %v", c.a, c.b, got, c.want) } } } func TestLatestStable(t *testing.T) { cases := []struct { name string tags []string want Semver wantOK bool }{ { name: "picks highest stable", tags: []string{"v0.9.0", "v0.9.2", "v0.9.1", "v0.8.0"}, want: Semver{Major: 0, Minor: 9, Patch: 2}, wantOK: true, }, { name: "ignores pre-release tags", tags: []string{"v0.9.2", "v1.0.0-rc1", "v0.9.3-20260101", "v2.0.0-beta"}, want: Semver{Major: 0, Minor: 9, Patch: 2}, wantOK: true, }, { name: "ignores non-semver tags", tags: []string{"random-tag", "v0.2.0", "not-a-tag", "v0.3.0"}, want: Semver{Major: 0, Minor: 3, Patch: 0}, wantOK: true, }, { name: "picks highest not most-recent", tags: []string{"v2.0.0", "v0.9.2", "v1.9.9"}, want: Semver{Major: 2, Minor: 0, Patch: 0}, wantOK: true, }, { name: "empty input", tags: nil, wantOK: false, }, { name: "only pre-release tags", tags: []string{"v1.0.0-rc1", "v0.5.0-beta"}, wantOK: false, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got, ok := LatestStable(c.tags) if ok != c.wantOK { t.Fatalf("ok = %v; want %v", ok, c.wantOK) } if ok && got != c.want { t.Errorf("got %s; want %s", got, c.want) } }) } } func TestApply(t *testing.T) { cases := []struct { name string base Semver kind BumpKind want Semver }{ {"patch bump", Semver{Major: 0, Minor: 9, Patch: 2}, BumpPatch, Semver{Major: 0, Minor: 9, Patch: 3}}, {"minor bump resets patch", Semver{Major: 0, Minor: 9, Patch: 2}, BumpMinor, Semver{Major: 0, Minor: 10, Patch: 0}}, {"minor bump double-digit", Semver{Major: 1, Minor: 9, Patch: 9}, BumpMinor, Semver{Major: 1, Minor: 10, Patch: 0}}, {"major on 0.x demotes to minor", Semver{Major: 0, Minor: 9, Patch: 2}, BumpMajor, Semver{Major: 0, Minor: 10, Patch: 0}}, {"major on 1.x resets minor/patch", Semver{Major: 1, Minor: 4, Patch: 5}, BumpMajor, Semver{Major: 2, Minor: 0, Patch: 0}}, {"major on 1.99.99 wraps to 2.0.0", Semver{Major: 1, Minor: 99, Patch: 99}, BumpMajor, Semver{Major: 2, Minor: 0, Patch: 0}}, {"patch double-digit", Semver{Major: 1, Minor: 9, Patch: 9}, BumpPatch, Semver{Major: 1, Minor: 9, Patch: 10}}, {"none returns identity", Semver{Major: 0, Minor: 9, Patch: 2}, BumpNone, Semver{Major: 0, Minor: 9, Patch: 2}}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if got := c.base.Apply(c.kind); got != c.want { t.Errorf("(%s).Apply(%s) = %s; want %s", c.base, c.kind, got, c.want) } }) } } func TestParseBumpKind(t *testing.T) { cases := []struct { in string want BumpKind wantErr bool }{ {"major", BumpMajor, false}, {"minor", BumpMinor, false}, {"patch", BumpPatch, false}, {"MAJOR", BumpNone, true}, {"", BumpNone, true}, {"bogus", BumpNone, true}, } for _, c := range cases { got, err := ParseBumpKind(c.in) if (err != nil) != c.wantErr { t.Errorf("ParseBumpKind(%q) err = %v; wantErr = %v", c.in, err, c.wantErr) } if got != c.want { t.Errorf("ParseBumpKind(%q) = %v; want %v", c.in, got, c.want) } } } func TestClassifyChangeType(t *testing.T) { cases := []struct { in ChangeType want BumpKind }{ {ChangeBreaking, BumpMajor}, {ChangeDeprecation, BumpMinor}, {ChangeNewComponent, BumpMinor}, {ChangeEnhancement, BumpMinor}, {ChangeBugFix, BumpPatch}, {ChangeType("unknown"), BumpNone}, {ChangeType(""), BumpNone}, } for _, c := range cases { if got := ClassifyChangeType(c.in); got != c.want { t.Errorf("ClassifyChangeType(%q) = %v; want %v", c.in, got, c.want) } } } func TestDeriveBump(t *testing.T) { cases := []struct { name string in []ChangeType want BumpKind wantErr error }{ { name: "single bug_fix -> patch", in: []ChangeType{ChangeBugFix}, want: BumpPatch, }, { name: "single enhancement -> minor", in: []ChangeType{ChangeEnhancement}, want: BumpMinor, }, { name: "single breaking -> major", in: []ChangeType{ChangeBreaking}, want: BumpMajor, }, { name: "picks strongest across many", in: []ChangeType{ChangeBugFix, ChangeEnhancement, ChangeBugFix}, want: BumpMinor, }, { name: "breaking dominates enhancement and bug_fix", in: []ChangeType{ChangeBugFix, ChangeBreaking, ChangeEnhancement}, want: BumpMajor, }, { name: "deprecation and new_component are minor", in: []ChangeType{ChangeDeprecation, ChangeNewComponent, ChangeBugFix}, want: BumpMinor, }, { name: "empty input errors with ErrNoEntries", in: nil, wantErr: ErrNoEntries, }, { name: "unknown change_type errors", in: []ChangeType{ChangeBugFix, ChangeType("wild")}, wantErr: errUnknown{}, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got, err := DeriveBump(c.in) switch want := c.wantErr.(type) { case nil: if err != nil { t.Fatalf("unexpected error: %v", err) } if got != c.want { t.Errorf("got %v; want %v", got, c.want) } case errUnknown: _ = want if err == nil || !containsSubstring(err.Error(), "unknown change_type") { t.Errorf("want unknown change_type error, got: %v", err) } default: if !errors.Is(err, c.wantErr) { t.Errorf("want error %v, got %v", c.wantErr, err) } } }) } } func TestWithPreReleaseAppendsSuffix(t *testing.T) { got := Semver{Major: 0, Minor: 10, Patch: 0}.WithPreRelease("rc.1").String() if got != "v0.10.0-rc.1" { t.Errorf("got %q; want v0.10.0-rc.1", got) } } func TestWithPreReleaseEmptyIsStable(t *testing.T) { got := Semver{Major: 0, Minor: 10, Patch: 0}.WithPreRelease("").String() if got != "v0.10.0" { t.Errorf("got %q; want v0.10.0", got) } } func TestNextRCIndex(t *testing.T) { base := Semver{Major: 0, Minor: 10, Patch: 0} cases := []struct { name string tags []string want int }{ { name: "no rc tags -> 1", tags: []string{"v0.9.0", "v0.9.1", "v0.9.2"}, want: 1, }, { name: "picks max+1", tags: []string{"v0.10.0-rc.1", "v0.10.0-rc.3", "v0.10.0-rc.2"}, want: 4, }, { name: "ignores rc tags for other versions", tags: []string{"v0.9.0-rc.7", "v0.11.0-rc.5", "v0.10.0-rc.1"}, want: 2, }, { name: "ignores rc tags with leading zeros", tags: []string{"v0.10.0-rc.01", "v0.10.0-rc.02"}, want: 1, }, { name: "ignores non-rc pre-release tags", tags: []string{"v0.10.0-beta.1", "v0.10.0-alpha", "v0.10.0-rc"}, want: 1, }, { name: "handles surrounding whitespace", tags: []string{" v0.10.0-rc.2 ", "v0.10.0-rc.5\n"}, want: 6, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { if got := NextRCIndex(c.tags, base); got != c.want { t.Errorf("NextRCIndex = %d; want %d", got, c.want) } }) } } // errUnknown is a marker type used by TestDeriveBump to assert on // unknown-change-type error text without pinning the exact string. type errUnknown struct{} func (errUnknown) Error() string { return "" } func containsSubstring(s, sub string) bool { for i := 0; i+len(sub) <= len(s); i++ { if s[i:i+len(sub)] == sub { return true } } return false } open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/000077500000000000000000000000001524057645600252135ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/config/000077500000000000000000000000001524057645600264605ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/config/all_values.conf000066400000000000000000000016051524057645600314600ustar00rootroot00000000000000dotnet_auto_instrumentation_agent_path_prefix=/custom/path/to/dotnet/instrumentation dotnet_auto_instrumentation_minimum_dotnet_major_version=6 jvm_auto_instrumentation_agent_path=/custom/path/to/jvm/javaagent.jar nodejs_auto_instrumentation_agent_path=/custom/path/to/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js python_auto_instrumentation_agent_path_prefix=/custom/path/to/python ruby_auto_instrumentation_agent_path_prefix=/custom/path/to/ruby all_auto_instrumentation_agents_env_path=/custom/path/to/auto_instrumentation_env.conf auto_instrumentation_disabled=nodejs include_paths=/app/*,/home/user/test/* include_paths=/another_dir/* exclude_paths=/usr/*,/opt/* exclude_paths=/another_excluded_dir/* include_with_arguments=-jar,*my-app*,*.js include_with_arguments=*.dll exclude_with_arguments=-javaagent*,*@opentelemetry-js* exclude_with_arguments=-debug auto_instrumentation_disabled_list.conf000066400000000000000000000000671524057645600364300ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/configauto_instrumentation_disabled=dotnet,jvm,nodejs,python auto_instrumentation_disabled_multiple_times.conf000066400000000000000000000001071524057645600405040ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/configauto_instrumentation_disabled=dotnet auto_instrumentation_disabled=jvm auto_instrumentation_disabled_star.conf000066400000000000000000000000401524057645600364150ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/configauto_instrumentation_disabled=* open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/config/conf.d/000077500000000000000000000000001524057645600276275ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/config/conf.d/01-java.conf000066400000000000000000000001051524057645600316310ustar00rootroot00000000000000jvm_auto_instrumentation_agent_path=/conf.d/path/to/jvm/javaagent.jaropen-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/config/conf.d/02-nodejs.conf000066400000000000000000000001111524057645600321700ustar00rootroot00000000000000nodejs_auto_instrumentation_agent_path=/conf.d/path/to/nodejs/register.jsopen-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/config/conf.d/not-a-conf-file.txt000066400000000000000000000002111524057645600332400ustar00rootroot00000000000000this file should be ignored because it does not have a .conf extension dotnet_auto_instrumentation_agent_path_prefix=/should/not/be/read open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/config/empty.conf000066400000000000000000000000001524057645600304530ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/config/very_long_lines.conf000066400000000000000000000406051524057645600325320ustar00rootroot00000000000000# a short line dotnet_auto_instrumentation_agent_path_prefix=/this/line/should/not/be/parsed/since/it/is/too/long # a comment that deliberately makes this line very long, i.e. over 8192 characters ..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... # another short line # make sure overly lone lines do not break the parsing of other lines jvm_auto_instrumentation_agent_path=/this/line/should/be/parsed nodejs_auto_instrumentation_agent_path=/this/line/should/not/be/parsed/since/it/is/too/long # a comment that deliberately makes this line very long, i.e. over 8192 characters ............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ with_comments_and_whitespace.conf000066400000000000000000000011741524057645600351710ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/config# Full line comment # end-of-line comment # some lines with only whitespace all_auto_instrumentation_agents_env_path=/custom/path/to/auto_instrumentation_env.conf auto_instrumentation_disabled =jvm dotnet_auto_instrumentation_agent_path_prefix = /custom/path/to/dotnet/instrumentation # end-of-line comment jvm_auto_instrumentation_agent_path = /custom/path/to/jvm/javaagent.jar # end-of-line comment nodejs_auto_instrumentation_agent_path = /custom/path/to/nodejs/node_modules/@opentelemetry/auto-instrumentations-node/build/src/register.js # end-of-line comment an invalid line that will be ignored open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/libc/000077500000000000000000000000001524057645600261245ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/libc/dotnet-app-arm64-glibc000077500000000000000000002220101524057645600321270ustar00rootroot00000000000000ELF·¸f@ˆ@8 @" @@@  ààà´f´f¸f¸f¸fإإ� � � €p¸Û� � � 0 0 0   Råtd� � � €pPåtdLFLFLF„„QåtdüüüDD/lib/ld-linux-aarch64.so.1GNUGNUpIÎhl•ÌøÒ¹ÿ€Ì\Û0× ( D ^"m �™±¾Ð×"g°ð÷=N]—­Â#h…£"¯¶¾ÆÏÕßåìòùGO[bm°ð;^ ë;LTv”œÜãëù N�”›¤«±¼ÃÊÏØßåíôüPW^els|‚ˆ¶ú6 W à õ  N a ¨ À É Ó Û å í ó        `$ `4 `‡  �  §  —‘–  —‘–  t)’C aø— O pø—^ qø—m Ó¯k| P&y • —‘– —‘– qabort__libc_start_main__gmon_start___ITM_deregisterTMCloneTable_ITM_registerTMCloneTable__cxa_finalize_Jv_RegisterClasses_ZNSt8ios_base4InitC1Ev_ZNSt8ios_base4InitD1Ev__cxa_atexit__stack_chk_guardstrlen_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_createERmmmemcpy_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEmmPKc_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_replaceEmmPKcm_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4swapERS4__ZdlPv_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4___stack_chk_fail_Unwind_Resume_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED2Ev__cxa_call_unexpected__gxx_personality_v0_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4findEcm__cxa_begin_catch_ZSt9terminatev_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm_ZSt19__throw_logic_errorPKc_ZSt20__throw_length_errorPKcsched_yieldstderrfopen64setvbufvfprintffputcvsnprintf_Znwmmemsetfputsstdoutfflush_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_mutateEmmPKcmstrncmpstrncasecmpstrcmpstrcasecmp_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5rfindEPKcmm_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5rfindEcm_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE12find_last_ofEPKcmm_ZSt24__throw_out_of_range_fmtPKcz_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE8_M_eraseEmm_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE14_M_replace_auxEmmmc_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE17find_first_not_ofEPKcmm__errno_locationstrtoul_ZSt24__throw_invalid_argumentPKc_ZSt20__throw_out_of_rangePKctoupper_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7reserveEmstrchrtolower__cxa_rethrow__cxa_end_catch_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareERKS4__ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6substrEmmtimegmtimestrftimeopen64close__fxstat64mmap64getcwdfreestrerrordlopendlsymdlerrordladdrdlclose__getdelimsscanf_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4findEPKcmmfclosestrtolgetenvaccessgetuidgetpwuidmkdirfgets_ZNSt13basic_fstreamIcSt11char_traitsIcEEC1Ev_ZNSt13basic_filebufIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmode_ZNSt9basic_iosIcSt11char_traitsIcEE5clearESt12_Ios_Iostate_ZNKSt5ctypeIcE13_M_widen_initEv_ZSt7getlineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7__cxx1112basic_stringIS4_S5_T1_EES4__ZNSt13basic_filebufIcSt11char_traitsIcEE5closeEv_ZTTSt13basic_fstreamIcSt11char_traitsIcEE_ZNSt13basic_fstreamIcSt11char_traitsIcEED2Ev_ZNSt8ios_baseD2Ev_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEmmRKS4__ZSt16__throw_bad_castvrealpathgetauxvalopendirreaddir64fnmatchdirfd__fxstatat64closedirbcmplibdl.so.2GLIBC_2.17libpthread.so.0libstdc++.so.6GLIBCXX_3.4GLIBCXX_3.4.11GLIBCXX_3.4.20GLIBCXX_3.4.21CXXABI_1.3libgcc_s.so.1GCC_3.0libc.so.6ld-linux-aarch64.so.1$ORIGIN/netcoredepslibm.so.6˜ €g  Èg¨  h° xm¸ ÜwÀ ”†È „‘Ð X¾Ø ¸Ôà  è u5ð V1ø &< H- Ê6 l+ t+ ô-( 2PØX``°vhpx€ˆ� ˜ H  ¨%°)¸cØàèðø    (08@HPX`hpx€ˆ�˜ !¨"°#¸$À%È&Ð'Ø(à*è+ð,ø-./01 2(30485@6H7P8X9`:h;p<x=€>ˆ?�@˜A B¨C°D¸EÀFÈGÐHØIàJèKðLøMNOPQ R(S0T8U@VHWPXXY`Zh[p\x]€^ˆ_�`˜a b¨d°e¸fÀgÈhÐiØjàkèlðmønop app-local: [%s]false0123456789The managed DLL bound to this executable is: '%s'}You must install .NET to run this application.linux-arm64 %shttps://go.microsoft.com/fwlink/?linkid=2063370Considering fxr version=[%s]... coreservicing/proc/self/mapsUsing environment variable %s=[%s] as runtime location.Not found ]aRuntime config is cfg=%s dev=%s%s: __pos (which is %zu) > this->size() (which is %zu)/proc/self/exeThe managed DLL bound to this executable is longer than the max allowed length (%d)fxrError: [%s] does not existFound registered install location '%s'./usr/share/dotnet global install location [%s] self-registered config location [%s] environment_variableriscv64s390xFailed to resolve full path of the current executable [%s]Invoking fx resolver [%s] hostfxr_main_startupinfoResolved fxr [%s]...Checking if CoreCLR path exists=[%s]Failed to unload library, error: %sLooking for architecture-specific install_location file in '%s'.The library %s was found, but loading it from %s failedThe required library %s does not support single-file apps.Reading fx resolver directory=[%s]COREHOST_TRACECOREHOST_TRACE_VERBOSITY.loongarch64linux_DOTNET_TEST_GLOBALLY_REGISTERED_PATHError: [%s] does not contain any version-numbered child foldersCOREHOST_TRACEFILEDOTNET_ROOT_&framework_version=x64Using core servicing at [%s]%*p-%*p %*[-rwxsp] %*p %*[:0-9a-f] %*d %s This executable is not bound to a managed DLL to execute. The binding value is: '%s'.runtimeconfig.dev.jsonThe required library %s could not be found. Search location options [0x%x].runtimeconfig.json_DOTNET_TEST_INSTALL_LOCATION_PATH%s App: %s Architecture: %s App host version: %s .NET location: %s Learn more: https://aka.ms/dotnet/app-launch-failed Download the .NET runtime: %s&apphost_version=%sThe application to execute does not exist: '%s'. app_local.netDOTNET_RUNTIME_IDmissing_runtime=trueFailed to load %s, error: %sThe app-relative .NET path is longer than the max allowed length (%d)%s - search options: [basic_string::substrDirectory core servicing at [%s] was not ACL-ed properlyFailed to map file. mmap(%s) failed with error %dhostfxr_set_error_writerarm64true.., app-relative path: Tracing enabled @ %sDOTNET_ROOTtouch_fileVERSION_ID=The app-relative .NET path is not embedded.Unable to open COREHOST_TRACEFILE=%s for writingx869.0.5libcoreclr.soThe install_location file ['%s'] does not exist - skipping.App path: [%s]Using global install location [%s] as runtime location./\%c %Zrealpath(%s) failed: %salpine.apphostDid not find [%s] directory [%s]Directory core breadcrumbs [%s] was not specified or foundcorebreadcrumbsinstall_location_Failed to map file. fstat(%s) failed with error %dlibhostfxr.soInvoking fx resolver [%s] v1Failed to map file. open(%s) failed with error %d - Installing .NET prerequisites might help resolve this problem.The required library %s does not support relative app dll paths.Using app-relative location [%s] as runtime location.Found previously loaded library %s [%s].&arch=%dbasic_string::eraseThe required library %s does not contain the expected entry point.Using app-local location [%s] as runtime location.basic_string::appendarmCORE_SERVICINGr.NET root search location options: %dHost path: [%s]Dotnet path: [%s]HOMEBundle Header Offset: [%ld]hostDOTNET_MULTILEVEL_LOOKUPgetcwd() failed: %sFallback directory core breadcrumbs at [%s] was not foundError: the required library %s could not be found in [%s]DOTNET_MULTILEVEL_LOOKUP is set to %s&rid=&os=ppc64leFallback directory core servicing at [%s] was not foundFailed to determine default extraction location. Environment variable '$HOME' is not defined and getpwuid() returned NULL.rocky. globalDetected latest fxr version=[%s]...basic_string::_M_construct null not validrhel.Embedded app-relative .NET path: '%s'stoulCORE_BREADCRUMBSFailed to create default extraction directory [%s]. %shostfxr_main_bundle_startupinfohostfxr_mainDetected Single-File app bundleError: the default install location cannot be obtained..deps.jsonRealpath CORE_BREADCRUMBS [%s]Breadcrumb store [%s] is not ACL-ed with rw-Default extraction directory [%s] either doesn't exist or is not accessible for read/write./etc/dotnetinstall_location_DOTNET_TEST_DEFAULT_INSTALL_PATHID=hostfxr_main_startupinfoInvoking fx resolver [%s] hostfxr_main_bundle_startupinfo--- Invoked %s [version: %s] main = {Multilevel lookup is %shttps://aka.ms/dotnet-core-applaunch?Probed for and did not find library symbol %s, error: %sopt/etc/os-release0123456789."'You must install or update .NET to run this application. app-relative: [%s] app_relativeframework=Realpath CORE_SERVICING [%s]Directory core servicing at [%s] was not specified or foundThe install_location file ['%s'] failed to open: %s.Looking for install_location file in '%s'. environment variable: [%s]armv6open(%s) failed in %sFailed to determine default extraction location. Environment variable '$HOME' is not defined and directory reported by getpwuid() [%s] either doesn't exist or is not accessible for read/write.Did not find any install location in '%s'.19ff3e9c3602ae8e841925bb461a0adb064a1f1903667a5e0d87e8f608f425acc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2ÿÿ0ÜüìTÀÐà4̼$ø” ̸¬üäüÿœ ltÿÿÈÈÀØtÌlĸ(ÿÿ PP,À|hÿœ¼¬HÔ ”à ¼Ü ðä ” Ô � ä  Ø Ð Ô  ¼ ¨À ØXÄ ° Œ ¼ˆ Ää ôØ „ ä ¤� Ø<¸ ”à œÜ ¬@” üÐ ŒÀ Ð(� øHÔ À 8ø „ ¨ ´ ° ÀÿÿLLø`¼ÿÿfààÌì ØÀ è@¨Ô€ü ¬ � ˜ Ä� ü´Ä ¸ ü Ì ,° Ð ¤ Ü ì è € À Œ Ä ˜ ÄÿÿJTT Ü` ÈÀ¨È ¬ÔÈÜdÐôÄü È œÜ ˜ìdðдÿÿ¤¤dÔ¨ ˜´¼ÿÿ0ÐHÌ\ Èh„ÿÿ Ä„ÌÄ ¼Ð°ÿÿÀÀ”Èèÿÿ¨¨4°Ütÿÿ¨¨Ì°¼ÿÿœœ(˜ÄtÿœQCÈÈ ìÔа�À@€””xŒˆ Pð Ðü �ˆhÿœ 0ÿÿ 4äDÀÿÿ4àT܈à˜dÿœaTxx ôœ ìØ@ô˜<Ô𬀼<ø€ŒøÌÄ „Ð üÜà ôìTÿÿ HPà˜hÿÿ<<àX¬ÿÿ Œ�¨ˆÿÿ8 üP¨�ødÿÿ¨¨äÀ È¸ÿÿÀÀœÐ<Œ œ˜$ÿÿ¨¨äÀ È¸ÿÿ,,¬d ¨p ô|�ŒÄÿÿB¤¤´ØèTì̈àüøèØ ”À ìÌ ØØ ”ä¤ÿÿ H\¤¤ ÿœ9-ÀÀ¬ÐÌœ ¬¨¸¼ÀÌÄ ÿÿèè�øˆ€ �Œ$ÿÿ0t8\ÿÿ @ ˜L�ÿÿ @¤X�ÿÿ  ð´ ìÈTŒœÈÿÿ3pp¤¤ ÄÜ„¤¸ØØÔ„¤  ÿÿcÜÜ œ èðØ � äpÔ ü àhÈÄ ì˜ ¤” ¸¬ È œ Ô ô Œ €€€ è Œ ¸ÿÿ˜˜ Ф€¤ İÀÿÿˆˆ è”tÿÿK\ ˜ŒØ�è”øè༤œà˜œ ˆì  øü ”ˆlÿÿ `è�Èhÿÿ `ø Øhÿÿ¼¼œÌpÿÿh üˆ�˜Ðü세ÿœ)ØØ ää”øèˆ€ÿÿ 4l¸ 8ÿÿ!HH äh¤€” Œ 8�ØÜÿÿ 8TèŒ|ÿœÄ¶˜ ì¨8ø 0Ìð м ŒØ �„ÐàèÐäà`ÀøÈ@ˆ@ôè|Ì� à  @à ÔÔ ”ä ¸œl¬�8ðà ¬ÀÌÄ ”ÐÔ(ˆü(„¤¬¼ àÈ ÌÔ¬ÿÿÀÀôÐDÿÿ @ ˆL\ÿÿ @ ˆL\ÿœ1"¨¨ˆ¸Ü”˜œ¨  ;€�´!˜À!¬ü!Ô "è"ü,"<"€œ&Ô,'$h'H)\,*œ0Ld0ð€1x�1”Ì1¼à10<9„@=Ø0?ÐŒ?üØ?0H@p„@pAPpC˜”DœÄFÔØFèüG$I` ItDJ°ÜJàK8K0tKìÄL,°NpèOX8P€@P”HP¨œPРPä¤Pø€Q, ÔR` 0T˜ ŒU¬üXð0YÌ �Yü 4[8 <[L L[` X[ \\t l\Xh]”(aà(bˆ p_ãÿÿÿÿÿÿD�T @“”•–— ž� ° �TÓÔÕÖ×ÞÝD ,”B4oãÿÿÿÿÿÿDD ž� ` DÞÝD 4Ä0DSãÿÿÿÿÿÿDPL  “”ž� À PLÓÔÞÝD 8ü Eü+ãÿÿÿÿÿÿD`P 0“”•ž � ° `PÓÔÕÞÝD H8ÌEÀ ãÿÿÿÿÿÿDÀX P“”•–— ˜ ™šž� à ÀXÓÔÕÖרÙÚÞÝD 8„@I#ãÿÿÿÿÿÿD`P 0“”•ž � ´ `PÓÔÕÞÝD 4À K÷âÿÿÿÿÿÿDPL  “”ž� ¼ PLÓÔÞÝD <øðL°ÓâÿÿÿÿÿÿD€P 0“”•–ž � d €PÓÔÕÖÞÝD <8`NÜ£âÿÿÿÿÿÿD`P 0“”•–ž � D `PÓÔÕÖÞÝD <xüO€wâÿÿÿÿÿÿDpT @“”•–— ž� à pTÓÔÕÖ×ÞÝD <¸'”¨[ø©]ø¨ƒø?i(8¨ƒ]øùñÃT`ÿÿ°|‘¡?€RC”4´ñãT ÕšöP ÃÑ᪀R-'”5cÿÿ°c<5‘ ÃÑ€R€R&'”4¸]ø¨CÑ¿8A‘àªèù¨;©'”䪠CÑáªâªãª'”¡[ø`ÿÿ° ‘Ö”à‘¡CÑ'” [øè@ùë@T'”8€R ]øë!T `ÿÿ°È‘”ø* ]øë@T '”˜6`ÿÿ°(‘á*¼”è'@ùè´á@ùàƒ‘'”á#@ùàƒ‘ ”à@ù⃑á*ãªäªJ”à6àªáª�”à6ö*ÖÖN¢@ù`ÿÿ�2‘aÿÿ°!0‘æ”`ÿÿ°¤‘ã”`ÿÿ�ø$‘aÿÿ�!%‘Þ”à@ùè@ùvn¹ë@TØ&”à#@ùè @ùë@TÓ&”ÈÐÍGù©_ø@ù ë!TôOP©öWO©ø_N©úgM©üoL©ý{K©ÿC‘À_ÖÌ&”öª ]øëàT¾&”à@ùè@ùëÀT'¨[øé@ùöª ëáT ]øëaþÿTà@ùè@ùëaTà#@ùè @ùë�T @ùë¡T€@ùëÁT`@ùëáTપ&”જ&” ]øë ýÿTÚÿÿöªà@ùè@ùëàüÿT’&”à#@ùè @ùëÀüÿT�&” @ùë üÿT‰&”€@ùë€üÿT…&”`@ùë`üÿT�&”ઋ&”ý{¾©ôO©ý‘óª@ù@´A”`&@ùhb‘ë@Ts&”`@ùhâ‘ë@Tn&”`@ùhb‘ë€TôOA©ý{¨g&ôOA©ý{¨À_Öôª`"‘q&”`¢‘o&”`"‘m&”àªo&”ý{¾©ó ùý‘ ÕbPàª4&”ÀÐ Õâ+ÈGùáªó @ùý{¨0&ÿƒÑý{©öW©ôO©ýÑÈÐ Õ<ÍGùóª@ù¨ƒø@ùàª%&”äªàªáªâªãª/&”è#‘àªA‘öù&”ôªàªŸBñôùTà#‘á‘âª&”è@ùàùèùÔ´ŸñáTHðAU99è'@©è ù?i(8è @ùñT Õßõp€€R”ó*à@ùë@T&”ÈÐÍGù©ƒ_ø@ù ëáTà*ôOE©öWD©ý{C©ÿƒ‘À_Ö Õá3âªñ%”è'@©è ù?i(8è @ùñBüÿTñãT Õcsöà#‘᪀Rç%”5cÿÿ°cD6‘à#‘€R€Rà%”@4a@ù`ÿÿ�<#‘ž”3€Rà@ùëAúÿTÒÿÿa@ù`ÿÿ�9‘Þ”Èÿÿä%”óªà@ùë@TÖ%”àªà%”ÿƒÑý{©öW©ôO©ýÑÈÐôªÍGù@ù¨ƒø3@ùv ””@ùõªè#‘àªáªö#‘‹”æ@ùdÿÿ°„L‘`ÿÿ�¼<‘aÿÿ°!¤+‘âªãªåªçªµ”à@ùÈB‘ë@T°%”ȰÍGù©ƒ_ø@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_Ö¬%”è@ùÉB‘óª ë`Tàª�%”ધ%”ÿÑý{©üo©úg©ø_©öW©ôO©ýƒ‘Ȱ©ÃÑóªÍGùô*:A‘@ù¨øº=©¿8 Ãѹ"”6 ÃÑá**#”€6¨CÑ¿8A‘¨ÃÑ¿8A‘èC‘»;©A‘¼9©÷ ©ÿƒ9àC‘)ÿÿ—˜�R°r 6àC‘á€R⪅%”öªèƒ‘ ÃÑõƒ‘¦ ” CÑბa%”à@ù¨B‘ë@T`%”áK@ù CÑi”¤ýÿ— ´`ÿÿ�$"‘”%¡]ø@ÿÿðè-‘S”ˆ�R°r2 ]øë@TL%”ȰÍGù©_ø@ù ëáTà*ôOW©öWV©ø_U©úgT©üoS©ý{R©ÿ‘À_Ö(€R*àK@ùë�T CÑá*–” 6胑 CÑõƒ‘m ” ÃÑბ(%”à@ù¨B‘ë@T'%”àƒ‘¡ÃÑŽýÿ—õ�@¹4àƒ‘¢þÿ—àK@ùë@T%” Yøë@T%” [øëÀøÿT%”ÄÿÿZýÿ—´àƒ‘iýÿ—õª ´é£D©¶]øñ¨+{©é‰šéù_ñøˆšLýÿ—á7@ùùª`ÿÿ�À'‘¶”¡]ø`ÿÿ�À‘²”á'@ù`ÿÿ�‘®”¡[ø`ÿÿ�Œ ‘ª”`ÿÿ�\‘᪦”àƒ‘Kýÿ—áªàC‘Ì”ã@ùà*áªâªäªåª ?Öèc@9õ*è÷ÿ4è @ù¨÷ÿ´àª?Öºÿÿ¡[ø@ÿÿðh?‘×”[àK@ùëáöÿT·ÿÿàƒ‘2ýÿ—õª ´é£D©«]øá7@ùñ¨+{©é‰šé/©_ñöˆš@ÿÿðÔ.‘y”¡]ø`ÿÿ�À‘u”á'@ù`ÿÿ�‘q”¡[ø`ÿÿ�Œ ‘m”àƒ‘ýÿ—ùªàC‘᪒”ã @©à*áªäª ?Öõ*H”9µK¿kÁT ´èƒ‘¡‘¡ÃÑÍþÿ—èc@9¨ïÿ4è @ùhïÿ´àª?Öxÿÿá7@ù@ÿÿðü2‘•”胑¡‘¡ÃѾþÿ—Kàƒ‘þÿ—àK@ùëáíÿToÿÿá7@ùß±àT`ÿÿ�°‘„”•�R°r`ÿÿ`ÿÿ�h‘5”é”àƒ‘áüÿ—èª`´à*áª?Öõ*àƒ‘õýÿ—àK@ùë�êÿTTÿÿá7@ù`ÿÿ�¨‘k”•�R°rGÿÿo$”¡”Ÿ”èc@9óªè4è @ù¨´àª?Ö”” èc@9óªh4è @ù(´àª?Öˆ”óªàƒ‘Éýÿ—óªàK@ùë�T Yøë¡T [øëÁT ]øë�Tàª?$”2$” Yøë þÿT.$” [øë€þÿT*$” ]øë`þÿTóª ]øëÀýÿT!$”àª+$”ý{¾©ôO©ý‘ôªóª…”tù"9µ”À´h@ùˆ´?Ö(€Rh"9ôOA©ý{¨À_ÖÿÃÑý{©÷#ùöW©ôO©ýÑȰóªô*ÍGù@ù¨ƒøü”‹” 6è#‘õ#‘Ç”â@ù`ÿÿ�¨(‘aÿÿ�!L ‘¬”à@ù¨B‘ë@Tð#”ŸqKTö*uÿÿ�µ–‘÷ªá†@øàªž”Öñ�ÿÿT@ÿÿð$‘™”à*áªEþÿ—ó*I”ȰÍGù©ƒ_ø@ù ëTà*ôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö×#”è@ù©B‘óª ë`TàªÈ#”àªÒ#”ý{¿©ý‘ß#”â#”ý{¾©ó ùý‘ Õópછ#”À° ÕÂØÈGùáªó @ùý{¨—#äªãªá*âªÿCÑý{©üo©úg©ø_©öW©ôO©ýÑȰ)rª€RÍGùõª÷ªóªöªT‰@ù¨øÔ6È@ùˆ´ Õ!ØõàªâªK”À6Á@ù`ÿÿ�´‘E”¡@ù@ÿÿð /‘A”àªáª‹#”3€R@ü*³´”6h@ùñüŸ¨ÃÑ¿8A‘¨CÑ¿8A‘¹=©º;©|4àªá*!”à6a@ù`ÿÿ�´‘%”àªáªo#” Õ�Òõàªâª”`6¡@ù@ÿÿð /‘”3€R [øëa"Tt6 CÑ᪆ ”à6¡[øâ@ù@ÿÿðØ&‘ ”Ô6 ÃÑ.”€7 ÃÑ"6¡]ø`ÿÿ�È ‘ý”¡ÃÑàªG#”ø^@©¨ÃÑA‘»øxµ‹è!µÿBñàª÷#ùT ÃÑá‘⪠#”è#@ù ø¨ø7´ÿñ�T@99áªâª#”è#@ù©Yø¨ƒø?i(8aÿÿ�!Ì‘ ÃÑ,”Aÿÿð!Ü*‘ ÃÑ(” ÃÑC ”`6¨Yøéƒ‘4A‘ëô3ù€T©Zøè3ùé;ù²š” 6@ÿÿð´:‘ Õ�Æõâ*˜”´6Á@ù@ÿÿð°"‘“”¼4a@ù`ÿÿ�ˆ,‘Ž”´6¡[ø`ÿÿ�$0‘‰”ô6¨ƒ]øèµ ÃÑÓ”¨ƒ]øhµ ÃÑ’”– ”è‘›”¡]øâ#@ù@ÿÿð@,‘x”à#@ùè‘A‘ë@Tá"”è‘ÿC9A‘ö©à‘ ”Iÿÿð)¹'‘ˆ €R)@ùꃑèsyWA‘(€RŸ6q÷ùè§©€ Taÿÿ�! ‘àƒ‘‚€Rî"”µ€’õÿïòT6è@ù©.‘ ë,Taÿÿ�!,‘àƒ‘B€Râ"”\4è@ù©"‘ ëìTaÿÿ�!Ü,‘àƒ‘¢€RØ"”46è@ùëÌTAÿÿð!X-‘àƒ‘¢€RÏ"”T6è@ùéï}² ëŒTaÿÿ�!0‘àƒ‘â€RÅ"”è@ù ø’?Hë`TAÿÿð!à'‘àƒ‘B€R¼"”œ4è@ùëL Taÿÿ�!�‘àƒ‘¢€R³"”s@ùàªt"”è@ùù@Òëà Tâªàƒ‘᪨"”ó#@ù0 ”õ@ùôªè‘àªáªF”æ@ùdÿÿ�„L‘@ÿÿð¼<‘Aÿÿð! $‘âªãªåªçªp”à@ùè‘A‘ë@Tj"”à@ùë@Tf"”à#@ùë@Tb"”ó*`À=€€=¨ƒYø»9©¿8è7ùàƒ‘áª�”ó*à3@ùë@TR"” Yøë@TN"” [øë@TJ"” ]øë@TF"”ȰÍGù©_ø@ù ëAT`ôOT©öWS©ø_R©úgQ©üoP©ý{O©ÿC‘À_Ö`ÿÿ�¤"‘4”ó* [øëÁüÿTæÿÿ6"”`ÿÿ�à‘S"”`ÿÿ�€‘T"”è#@ùé‘)A‘ ë!5è@ùé‘óª)A‘ ëTà@ùë!Tà#@ùëAT)àª"”à@ùëÿÿTóªà@ùë`þÿTóªà#@ùëATè3@ùëóª Tàªÿ!” óªà@ùë üÿTø!”à#@ùë Tô!”óª [øë Tóª Yøë!T [øëAT ]øëaTàªî!”á!” [øëÿÿTÝ!” ]øëàþÿTÙ!”àªã!”ÿƒÑý{©üo©úg©ø_©öW©ôO©ý‘ȰóªÍGù@ù¨ø@ùáù@ÿÿðè3‘áª{”ÿÿ©ÿùác‘઄ ” ƒÑóùq”õïA©¿ëT¨ƒÑéÑXÿÿÐÛ%‘A‘Á‘è‘*A‘<Á‘4‘3�‘A‘ê ùµ‚‘¿ëàT¡@ùàª\”è‘ઉ”àÑU”à‘áÑâ*” 6 ƒÑáÑ.”r¨ƒÑéÑ(ˆšù* @¹@ù¨øè @ù©ƒ¸–šàª�!”?r�—šàª‹!”à3@ùë@Tƒ!”à#@ùë@T!”àC@ùë@úÿT{!”ÐÿÿàÑ-” ƒÑáÑóÑc”õ*à3@ùh‘ó@ùë@Tm!”à#@ùèÑ�‘ë@Tg!”õ6a@ù@ÿÿÐ6‘b”õ*$èÑ ƒÑ”á@ùàªf”a@ù@ÿÿðP‘ ”â@ù ÕÁ�õઠ”õ*à6è@ù@ù@ÿÿР/‘”b@ù@ÿÿð|‘ Õá�õC”à@ùèÑA‘ë@T=!” ]ø³ƒÑh‘ë@T7!” [øh‚‘ë@T2!”óÓA©ëÁTs´àª,!”È�ÍGù©_ø@ù ë¡T ôOU©öWT©ø_S©úgR©üoQ©ý{P©ÿƒ‘À_ÖsB‘ëÀT`Aøë`ÿÿT!”ùÿÿó@ùÓüÿµçÿÿ!”%$óªàÑʔ ƒÑÈ”àc‘Ù”àª!”óªàc‘Ӕઠ!”óªàc‘Δàª!”è@ùéÑóª)A‘ ë@Tàªó ” ƒѯ”àc‘À”àªù ”óª ƒѧ”àc‘¸”àªñ ”óªóªàÑž”àC@ùë@TÜ ” ƒј”àc‘©”àªâ ”ÿÑý{©öW©ôO©ýC‘È�óªÍGù@ù¨ƒøè£‘T@©A‘öùtµˆ‹H µ¿BñàªõùT࣑á‘⪩ ”è@ùàùèù5´¿ñ�Tˆ@99áªâª¡ ”è@ùé@ùèù?i(8Aÿÿð!Ì‘࣑µ”AÿÿÐ!Ü*‘࣑±”࣑Ì”`6è@ùé#‘4A‘ëôù`Té@ùèùéù á@ù@ÿÿÐì*‘””ó*ÀÀ=€€=è@ùöÿ©ÿã9è ùà#‘᪳þÿ—ó*à@ùë@T„ ”à@ùë@T€ ”È�ÍGù©ƒ_ø@ù ëAT`ôOG©öWF©ý{E©ÿ‘À_Ö@ÿÿðà‘™ ”x ”è@ùóªëÁTà@ùë¡Tàªs ”àªe ”à@ùë@ÿÿTóªà@ùë þÿT\ ”àªf ”ý{¾©ôO©ý‘óªâª ÕÀoõAÿÿð!ð!‘ãªã”ô*à6b@ù@ÿÿðŒ‘ ÕanõØ”€ôOA©ý{¨À_Öý{¾©ó ùý‘óª@ùh‘ë@T: ”` @ùh‚‘ë€Tó @ùý{¨3 ó @ùý{¨À_Öý{½©õ ùôO©ý‘T@©ë€TôªsB‘ëÀT`Aøë`ÿÿT ”ùÿÿ“@ùÓ´àªôOB©õ @ùý{è ôOB©õ @ùý{èÀ_Öý{¾©ó ùý‘ ÕS™àªí”À� ÕcÈGùáªó @ùý{¨éÿÃÑý{©ôO©ýC‘È�飑ÍGù4A‘@ù¨ƒøôÿ©ÿã9 ÕÀ0õ0ᣑ=”`6à@ùÇ”qëT&” 6è#‘ó#‘p”á@ù@ÿÿð葞”à@ùhB‘ë@Tâ”à@ùë@TÞ”È�ÍGù©ƒ_ø@ù ë¡TôOF©ý{E©ÿÑÀ_ÖÛ”óªà@ùë@TÌ”àªÖ”ÿƒÑý{©ù3ùø_©öW©ôO ©ýC‘È�W°é£‘ÍGù4A‘@ù¨ƒøõÞI¹ôÿ©ÿã9 5V°Öb'‘(€RÉþ_È~ Êÿÿ5i4ó*8€R&rATÜ”sÈþ_Ø~ Éÿÿ5(ÿÿ5È�Y°ÑGù@ù(óù@ÿÿÐ7‘ᣑæ”À6à@ùAÿÿÐ!ì'‘Í”`´áªB€RãªóªË”ø*3óùø*8€Rè#‘ÿc9A‘óÿ©@ÿÿа4‘á#‘Í”`6à@ùW”è@ùàÞ ¹ë`Tàª|”ßþŸá@ù¸6@ÿÿðx‘v”á@ù?ë`Tàªq”È�¿qÍGù©ƒ_øàŸ@ù ëÁTôOI©ù3@ùöWH©ø_G©ý{E©ÿƒ‘À_Ö€€Rè@ùàÞ ¹ëüÿTáÿÿc”óª óªè@ùëóª`TàªQ”ßþŸà@ùë@TL”àªV”ÿCÑý{©ü“ùöW©ôO©ýC‘á‹©È�I°ã“ ©å› ©ç_ùà­â­ä­ÍGùæ­@ù¨ƒø(ÝI¹ qëT耒é‘ê#‘ðßò)‘óª©#<©¨‘Iᑨ';©U°µb'‘(€R©þ_¨~ Êÿÿ5i4ô*6€RŸ&rATN””¨þ_¶~ Éÿÿ5(ÿÿ5 ‡}­¢ÃÑ´@ùáªàª ‡>­N”@€RáªO”¿þŸÈ�ÍGù©ƒ_ø@ù ëáTôOT©ü“@ùöWS©ý{Q©ÿC‘À_Ö ”ÿƒÑý{©ü³ùø_©öW©ôO©ýC‘á‹©È�é#‘ã“ ©ê‘óªå› ©£ÃÑàªç_ùáªâªà­â­ä­ÍGùæ­@ù¨ƒø(á‘©C‘©#;©è€’I‘ðßò©#<© ‡}­ ‡;­ ‡<­ ‡>­”1ôª}@“âTઔá*âªôª” ‡{­£ÃÑàªáªâª ‡>­”W°÷b'‘(€Réþ_è~ Êÿÿ5i4õ*6€R¿&rATèþ_ö~ Éÿÿ5(ÿÿ5 Ò€ò Õ ÕXÐ;Õõªk`ø(´àª?ÖH°ÝI¹h4È�I°ÑGù6ñDù@ùßëaTkuøH´ ‡|­¢ÃÑàªáª ‡>­Ù”@€RáªÚ”ÿþŸt´àª–”È�ÍGù©ƒ_ø@ù ë�TôOY©ü³@ùöWX©ø_W©ý{U©ÿƒ‘À_ÖÖ�àªÖÒGùÁ@ùÔ”Á@ù@€RÁ”H°ÝI¹húÿ5äÿÿ„”óªÿþŸt´àªw”àª�”H°ÝI¹qàŸÀ_ÖÿCÑý{©ü“ùöW©ôO©ýC‘á‹©È�I°ã“ ©å› ©ç_ùà­â­ä­ÍGùæ­@ù¨ƒø(ÝI¹qëT耒é‘ê#‘ðßò)‘óª©#<©¨‘Iᑨ';©U°µb'‘(€R©þ_¨~ Êÿÿ5i4ô*6€RŸ&rATt””¨þ_¶~ Éÿÿ5(ÿÿ5 ‡}­¢ÃÑ´@ùáªàª ‡>­t”@€Ráªu”¿þŸÈ�ÍGù©ƒ_ø@ù ëáTôOT©ü“@ùöWS©ý{Q©ÿC‘À_Ö/”ÿCÑý{©ü“ùöW©ôO©ýC‘á‹©È�é‘ã“ ©)‘ê#‘å› ©óªT°”b'‘ç_ùà­â­ä­ÍGùæ­@ù¨ƒøè€’ðßò©#<©Há‘©‘©#;©(€R‰þ_ˆ~ Êÿÿ5i4õ*6€R¿&rAT/”µˆþ_–~ Éÿÿ5(ÿÿ5Õ�¢ÃÑ᪵ÚGù ‡}­ @ù ‡>­.”¡@ù@€R/”ŸþŸÈ�ÍGù©ƒ_ø@ù ëáTôOT©ü“@ùöWS©ý{Q©ÿC‘À_Öé”@ÿÿÐ5‘¸ÿÿÿCÑý{©ü“ùöW©ôO©ýC‘á‹©È�I°ã“ ©å› ©ç_ùà­â­ä­ÍGùæ­@ù¨ƒø(ÝI¹ qëT耒é‘ê#‘ðßò)‘óª©#<©¨‘Iᑨ';©U°µb'‘(€R©þ_¨~ Êÿÿ5i4ô*6€RŸ&rATâ””¨þ_¶~ Éÿÿ5(ÿÿ5 ‡}­¢ÃÑ´@ùáªàª ‡>­â”@€Ráªã”¿þŸ¨ðÍGù©ƒ_ø@ù ëáTôOT©ü“@ùöWS©ý{Q©ÿC‘À_Ö�”ý{½©õ ùôO©ý‘H�ñDù¨´S�sb'‘(€Riþ_h~ Êÿÿ5i4ô*5€RŸ&rAT²””hþ_u~ Éÿÿ5(ÿÿ5`@ùÏ”þŸ¨ðÑGù@ùÊ”¨ðÙGù@ùôOB©õ @ùý{èÃý{¿©ý‘誠Ҁò Õ ÕJÐ;Õéª@i`øHi)øý{Á¨À_Öý{¿©ý‘ Ò€ò Õ ÕHÐ;Õi`øý{Á¨À_Öý{¾©ó ùý‘ ÕóAàª1” ð Õ‚ ÈGùáªó @ùý{¨-ÿÃÑý{©÷#ùöW©ôO©ýѨðóªôªÍGù@ù¨ƒøè#‘X@©A‘÷ùuµ¨‹ÈµßBñàªöùTà#‘á‘⪔è@ùàùèù6´ßñ�T¨@99áªâª”è'@©è ù?i(8à#‘áª(”à#‘C”ñô*èŸ ˆ6á#‘ઔà@ùë@T”¨ðÍGù©ƒ_ø@ù ëaT€ôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö@ÿÿÐà‘&””è@ùóªë`Tàª÷”ઔÿÃÑý{©÷#ùöW©ôO©ýѨðé#‘ÍGù7A‘@ù¨ƒø÷ùá ´ôªàªóªË”õªàª¿>ñõù Tà#‘á‘âªÆ”è@ùàùèù5´¿ñ�Th@99áªâª¾”è'@©è ù?i(8à#‘Z”ö*à@ùë@TÄ”•@ùV6ન”äªàªáªâªãª²”%u´ˆ@ù ‹)ñ_8?½qÀT骶‘* Aø ëé€R)Ššß ë Tàªáªâªãª$€Rý”ˆ@ùé€R i58ˆ@ù–ùi68•@ù઄”¨ú@Òë£Tâªàªáª¹”¨ðÍGù©ƒ_ø@ù ëATôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö@ÿÿÐà‘®”�”@ÿÿЀ‘®”è@ùóªë`Tàª|”આ”ÿƒÑý{©õ#ùôO©ýѨðÍGù@ù¨ƒøè#‘P@©A‘õùsµh‹hµŸBñàªôùTà#‘á‘âªN”è@ùàùèù4´Ÿñ�Th@99áªâªF”è'@©è ù?i(8 Õáãô0à#‘[ÿÿ—á@ù@ÿÿ°ô/‘Üýÿ—à#‘r”ó*à@ùë@TD”¨ðÍGù©ƒ_ø@ù ëAT`ôOE©õ#@ùý{C©ÿƒ‘À_Ö@ÿÿÐà‘]”<”è@ùóªë`Tàª.”àª8”@ùIÑ?ëâTý{¿©ý‘ Õèr@ù Õ)(ˆš?ÖqàŸý{Á¨À_Öà*À_ÖB|@“rB|@“t@ùëbTà*À_Öý{¿©ý‘ @ù Õ© ÕKrH‹i‰šË ?ÖqàŸý{Á¨À_ÖbeÿÑý{©õùôO©ýC‘óª¨ðéªÍGù`B‘@ùèù4U@©`ùtµˆ‹hµ¿BñõùTá‘àªâªÑ”è@ù`ùh ù5´¿ñ�Tˆ@99áªâªÉ”è@ùi@ùhù?i(8¨ðÍGùé@ù@ù ë!TôOC©õ@ùý{A©ÿ‘À_Ö@ÿÿÐà‘ï”ΔÿÑý{©õùôO©ýC‘©ðóª)ÍGù)@ùéù @ù ´AÿÿÐ!¨ ‘€’C€Rõª ”ôªàªÁ€R€’”Ÿ±èªÀTŸë‰T´V@©`B‘`ùµˆ‹È´0èª 8h~© ´&@©`B‘`ù?ë51ˆštµˆ‹ˆµ¿BñõùTá‘àªâª|”è@ù`ùh ù5´¿ñ�Tˆ@99áªâªt”è@ùi@ùhù?i(8¨ðÍGùé@ù@ù ëÁTôOC©õ@ùý{A©ÿ‘À_Ö|”@ÿÿÐà‘™”ÿÑý{©öW©ôO©ýC‘©ðóª)ÍGù)@ùéù @ù©´AÿÿÐ!¨ ‘€’C€Rôª€’Ò”õªàªÁ€R€’É”ƒ@ù¢‘hëƒT ëŠ@ù Vú`B‘)ŸÚT‹`ù ë1‰štµˆ‹¨µ¿BñõùTá‘àªâª.”è@ù`ùh ùµ´¿ñTˆ@99èª 8h~©áªâª"”è@ùi@ùhù?i(8¨ðÍGùé@ù@ù ëÁTôOC©öWB©ý{A©ÿ‘À_Ö*”@ÿÿ°t(‘AÿÿÐ!ô‘•”@ÿÿÐà‘B”ÿÑý{©õùôO©ýC‘©ðóª)ÍGù)@ùéù @ùi´á€R€’õªz”± T£@ù‘ë©T¨@ù`B‘`ù‹tµ‹hµuË¿BñõùTá‘àªâªá”è@ù`ùh ùõ´¿ñATˆ@99èª 8h~©´V@©`B‘`ùtµˆ‹µ¿BñõùÃýÿTæÿÿáªâªË”è@ùi@ùhù?i(8¨ðÍGùé@ù@ù ëÁTôOC©õ@ùý{A©ÿ‘À_ÖÓ”@ÿÿÐà‘ð”@ÿÿ°t(‘AÿÿÐ!ô‘;”ÿCÑý{©÷3ùöW©ôO©ýC‘óª¨ðÍGù@ù¨ƒøè£‘T@©A‘öùtµˆ‹èµ¿BñàªõùT࣑á#‘⪕”è@ùàùèù5´¿ñ�Tˆ@99áªâª�”è@ùé@ùèù?i(8è@ùh´é@ù)‹)ñ_8?½qÁTÑ࣑"€R ”õÿÿ࣑á€R€’ù”±àTô@ù ø7ˆJ`8½qÁTQqà*LÿÿT€|@“é@ùë#‘wA‘ ‘÷ù? ë55ˆštµˆ‹ˆ µ¿BñàªõùTà#‘á‘âªV”è@ùàùèù´¿ñaTˆ@99ô×B©wB‘wùtµˆ‹ µ¿BñàªõùTá#‘àªâª?”è@ù`ùh ùu´¿ñÁTˆ@99&áªâª7”è'@©è ù?i(8á @ùà#‘âª#€Rä€R¾”iB‘èªiù Aø_ë Tjù @ùi ù@À= €= @ùiù|©è@ù@9ë Tàª)”áªâª”è@ùi@ùhù?i(8a@ùàªâª#€Rä€R›”à@ùë@T”¨ðÍGù©ƒ_ø@ù ëATôOH©÷3@ùöWG©ý{E©ÿC‘À_Ö@ÿÿÐà‘0””@ÿÿÐà‘,”@ÿÿÐà‘)”h@ùè@ùëóªÁTà@ùë¡Tઔàªó”à@ùë@ÿÿTóªà@ùë þÿTê”àªô”"öÿ—ý{¿©ý‘ @©)‹)ñ_8?½q�TÑ"€RY”ý{Á¨À_Ööÿ—ý{½©õ ùôO©ý‘ó*âªô*õªé”±@T⪨@ùàªá*i"8á”⪱!ÿÿTôOB©õ @ùý{èÀ_ÖÿCÑý{©ø_©öW©ôO©ýC‘óª¨ðô*ÍGùâªõ*÷ª@ùèùÊ”ö^@©øª`B‘`ùÈ‹ßñ @ú蟱@Tˆ 6ÿBñ÷ùTá‘àªâªŒ”è@ù`ùh ùW´ÿñ¡TÈ@99h6ÿBñ÷ùTá‘àªâª{”è@ù`ùh ù·´ÿñTÈ@99 áªâªs”è@ùi@ùhù?i(8h@ùàªá*âªi88‘”øª±ÿÿT¨ÐÍGùé@ù@ù ë�TôOD©öWC©ø_B©ý{A©ÿC‘À_ÖáªâªW”è@ùi@ùhù?i(8¨ÐÍGùé@ù@ù ëÀýÿTd”@ÿÿ°à‘�” €RÀ_Ö ÕèZ Ù`øÀ_Ö@ÿÿ°X‘À_ÖÿCÑý{©ôO©ýÑóª¨Ðé#‘ÍGù4A‘@ù¨ƒøôÿ©ÿc9@ÿÿ°l‘á#‘‡”hB‘hù6é@ù?ë€Tè@ùiùh ùI®�RJÿÿ�JÉ$‘à@ùɆ¦rJ@ù q¸i€Rë ùiùn9àT'”€À=€=è @ùhù¨ÐÍGù©ƒ_ø@ù ë¡TôOD©ý{C©ÿC‘À_Ö”è@ùóªë`Tઔઔáª@ÿÿ°l‘SÿƒÑý{©õ#ùôO©ýѨÐé#‘ÍGù5A‘@ù¨ƒøõÿ©ÿc9@ÿÿ°à‘á#‘B”Sÿÿ°sr‘À6à@ùÊ”á@ùô*@ÿÿ°d‘�úÿ—ŸqHÿÿ�ù"‘ôŸsˆš4€R@ÿÿ°@)‘᪜ùÿ—à@ùë@TᔨÐÍGù©ƒ_ø@ù ëáTà*ôOE©õ#@ùý{C©ÿƒ‘À_ÖÜ”óªà@ùë@TÎ”àªØ”ÿÑý{©úg©ø_ ©öW ©ôO ©ýѨÐóªõªÍGù@ù¨ƒø!6ô*¨£Ñ¿ƒ8A‘¨@ù¸ÿ=©Hµ1«ÿÿ—ô*¨£Ñ¿ƒ8A‘¨@ù¸ÿ=©(´ £Ñ᪯”©£}©)‹)ñ_8?½q¡TÑ £Ñ"€R”u¢@©¿ëàT B‘ ù¶ß}©vµÈ‹ÈµÿBñ÷ùTᣑàªâª~” ùè@ù¨ ù×´ÿñ!TÈ@99 ¡£ÑàªÐ”´7náªâªq”è@ù©@ù¨ù?i(8h@ù�‘hùt 6ÿÿ©ÿùà#‘"”ô@ùà 6ú @ùŸë€ T裑A‘”‚‘Ÿë  Tùù•Z@©uµ¨‹h µßBñàªöùT࣑ბâªI”è@ùàùèù6´ßñ�T¨@99áªâªA”è'B©èù?i(8࣑¡£Ñ>”7u¢@©¿ëàT B‘ ùößB©vµÈ‹H µÿBñ÷ùTბàªâª&” ùè@ù¨ ù·´ÿñTÈ@99ᣑàªx” áªâª”è@ù©@ù¨ù?i(8h@ù�‘hùà@ùë€öÿT”²ÿÿô@ùó @ùŸëTt´àª” ƒ]øë@T”¨ÐÍGù©ƒ_ø@ù ë!TôOK©öWJ©ø_I©úgH©ý{G©ÿ‘À_Ö”B‘ŸëÀT€Aøë`ÿÿTÿ”ùÿÿô@ù”üÿµåÿÿ@ÿÿ°à‘ ”@ÿÿ°à‘”ü”@ÿÿ°à‘”+ôÿ—óª óªóªà@ùë@Tä”à#‘³÷ÿ— ƒ]øë@TÞ”àªè”ÿƒÑý{©õ#ùôO©ýѨÐé#‘óªÍGùôª5A‘@ù¨ƒø(@ù?ù9õÿ©ÿc9á#‘”@6à#‘á*&” 6á#‘àªÄ”3€Rà@ùëaT â@ù@ÿÿ°l ‘áªGùÿ—ó*à@ùë@T±”¨ÐÍGù©ƒ_ø@ù ëáTà*ôOE©õ#@ùý{C©ÿƒ‘À_Ö¬”è@ùóªë`Tઞ”ન”âªAÿÿ�!#‘C€RÿÑý{©õùôO©ýC‘¨ÐÍGù@ùèù@ùH´óªAÿÿ�!#‘âªC€Rôª ”± Tà*¨ÐÍGùé@ù@ù ë�TôOC©õ@ùý{A©ÿ‘À_Ö•@ùý”ôª¹á‘àªB€Rû”è@ùë Tˆ@¹‰q T`¹ €R¨ÐÍGùé@ù@ù ëÀüÿTh”@ÿÿ°8 ‘í”@ÿÿ°8 ‘î”ÿCÑý{©ôO©ýÑ©Ðóªè#‘)ÍGùô#‘)@ù©ƒø Õé  Ù`ø3”Cÿÿ�cL7‘à#‘áªâª„€R<”iB‘èªiù Aø_ë Tjù @ùi ù@À= €= @ùiùé#‘|©)A‘è@ù@9 ë`Tàª-”¨ÐÍGù©ƒ_ø@ù ë¡TôOD©ý{C©ÿC‘À_Ö*”è@ù‰B‘óª ë`Tઔàª%”ÿÑý{©öW©ôO©ýC‘óª¨ÐÍGùuB‘@ùèùuùÀ´ôªò”öª<ñàù)Tá‘àªâªî”è@ùõª`ùh ù¶´ßñATˆ@9¨9è@ùi@ùhù?i(8t@ù”µàªáªâªÞ”è@ùi@ùhù?i(8t@ùô´s@ù`À9}””ñ`8�ÿÿT¨ÐÍGùé@ù@ù ë!TôOC©öWB©ý{A©ÿ‘À_Ö@ÿÿ°à‘ü”Û”ÿÑý{©öW©ôO©ýC‘¨Ð)LŽRôªÍGù©Í¦rŠ€R €Róª@ù¨ƒøè£‘A‘¨€Ré;¹ê{yõ£©U”àã9@€RR”àç9  €RO”àë9À€RL”àï9€€RI”àó9Cÿÿ�cL7‘࣑áªâª„€R¡”è#‘A‘èªöù Aø?ë T @ùéùêù À=À€= @ù@9é ù|©è@ùë`Tઓ”á#‘ઌ”à@ùë@TŒ”€@ù᪮þÿ—`6 €R ‚@ùCÿÿ°c<‘àªáªd€Rw”€@ù᪡þÿ—¨ÐÍGù©ƒ_ø@ù ëáTôOG©öWF©ý{E©ÿ‘À_Öw”è@ùóªë`Tàªi”àªs”ÿƒÑý{©öW©ôO©ýÑóª¨ÐôªÍGùõª@ù¨ƒø¿8u~©è#‘àª=ûÿ—ˆ@ùé @ù ‹‘àªì”� @©àªq”ˆ@ùH´‰@ù(‹ñ_8½q ThR@©ê€Ri @ùë–‘I‰šß ë Tàªáªâªãª$€R�”h@ùé€R i48h@ùvùi68Aÿÿ�!5‘à#‘€’#€R ”á£@©ë1€šàªK”h@ù)€’éÿïò ëêTAÿÿ°!„#‘àªB€RA”à@ùè#‘A‘ë@T”¨ÐÍGù©ƒ_ø@ù ë!TôOE©öWD©ý{C©ÿƒ‘À_Ö@ÿÿ°€‘5””ôª`@ùë`Tè@ùé#‘ôª)A‘ ëÁT`@ùëTઔàªö”`@ùë@ÿÿTò”àªü”ÿÃÑý{©÷#ùöW©ôO©ýÑóª¨ÐôªÍGùwB‘@ù¨ƒøX@©wùuµ¨‹hµßBñàªöùTá#‘àªâªÂ”è@ù`ùh ù6´ßñ�T¨@99áªâªº”è@ùi@ùhù?i(8Aÿÿ�!à;‘è#‘àª2”á@ùàªÊøÿ—à@ùè#‘A‘ë@T¸”¨°ÍGù©ƒ_ø@ù ëATôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö@ÿÿ�à‘Ñ”°”è@ùé#‘ôª)A‘ ëÁT`@ùë¡T઩”છ”`@ùë@ÿÿTôª`@ùë þÿT’”જ”ÿCÑý{©÷ùöW©ôO©ýC‘󪨰ôªÍGùwB‘@ùèùX@©wùuµ¨‹èµßBñàªöùTá‘àªâªb”è@ù`ùh ù6´ßñ�T¨@99áªâªZ”è@ùi@ùàªhù?i(8L”h@ùù@ÒëƒTâªàªáª€”¨°ÍGùé@ù@ù ë¡TôOD©÷@ùöWC©ý{A©ÿC‘À_Ö@ÿÿ�à‘u”@ÿÿ�€‘v”Q”h@ùóªë`TàªC”àªM”ÿÃÑý{©÷#ùöW©ôO©ýÑ󪨰ôªÍGùwB‘@ù¨ƒøX@©wùuµ¨‹hµßBñàªöùTá#‘àªâª”è@ù`ùh ù6´ßñ�T¨@99áªâª ”è@ùi@ùhù?i(8!ÿÿð!T:‘è#‘ઃÿÿ—á@ùàªøÿ—à@ùè#‘A‘ë@T ”¨°ÍGù©ƒ_ø@ù ëATôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö@ÿÿ�à‘"””è@ùé#‘ôª)A‘ ëÁT`@ùë¡Tàªú”àªì”`@ùë@ÿÿTôª`@ùë þÿTã”àªí”ÿÃÑý{©÷#ùöW©ôO©ýѨ°óªôªÍGùõªöª÷#‘@ù¨ƒøè#‘áþÿ—á#‘àªÉ”à@ù÷B‘ë@TÈ”è#‘àªáª„ÿÿ—á#‘ઽ”à@ùë@T½”�@ùb@ù ÿÿðô'‘Iöÿ—¨°ÍGù©ƒ_ø@ù ëáTôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö³”ÿCÑý{©óCùýÑ󪨰ÍGù@ù¨ƒø¨£ÑÞùÿ— £Ñ%øÿ—€6¨ƒ]ø©£Ñ*A‘iB‘ ëiù T©ƒ^øhùi ù裑 £ÑÍùÿ—è#‘࣑Êùÿ—à#‘èªó#‘Æùÿ—à@ùhB‘ë@Tƒ”à@ù裑A‘ë@T}” ƒ]ø¨£ÑA‘ëàTw”@À= €=¨^øhù¨°ÍGù©ƒ_ø@ù ë¡Tý{G©óC@ùÿC‘À_Öo”è@ùiB‘óª ë¡Tà@ù裑A‘ëÁT ƒ]ø¨£ÑA‘ëáTàªb”àªT”à@ù裑A‘ë`þÿTóªà@ù裑A‘ë€ýÿTG” ƒ]ø¨£ÑA‘ë`ýÿTóª ƒ]ø¨£ÑA‘ë`üÿT9”àªC”ÿCÑý{©÷3ùöW©ôO©ýC‘󪨰ôªÍGùõªvB‘·€’ᣑàª@ùâª÷ÿïò¨ƒø¨€Rvùèù ”Iÿÿ�)¡)‘è@ù!@­)ÑAø`ùh ù­ Ðøhùh(8U´¨@94h@ùéÑ ëlTAÿÿ�!-‘àªB€R-”àªï”h@ùù@ÒëTâªàªáª#”Ô´ˆ@9ˆ4h@ùé:Ñ ë¬T!ÿÿð!€7‘àªb€R”àªÙ”h@ùâªù@Òë"Tˆh@ùé>Ñ ëŒTTÿÿ�”¶‘‚€Ràªáª”h@ùéÑ ëìTAÿÿ�!0‘àªÂ€Rü”h@ùëì TAÿÿ�!X‘ઢ€Rô”h@ùëì TAÿÿ�!ü‘ઢ€Rì”裑ÿã9A‘õÿ©@ÿÿ�l‘ᣑ ”è#‘A‘ôù6á@ù?ëÀTè@ùáùèù)ÿÿð)É$‘à@ùH®�R)@ùb€RȆ¦rë᪈r¸‰ùâ ùÿ�9 T¦”á‹@© À=᪀€=â@ùâ ùàªÁ”à@ùë@T™”è£‘à ”è@ù(µˆ-�Ré#‘ €RÈ­®r4A‘࣑蹨€Rá#‘ê;yô£©…”à@ùë@T…”h@ù ü’? Hë TAÿÿ�!‘ં€R ”á‹B©àª�”à@ù裑A‘ë@Ts”¨°ÍGù©ƒ_ø@ù ëTôOH©÷3@ùöWG©ý{E©ÿC‘À_Ö@ÿÿ�€‘�”@ÿÿ�€‘�”@ÿÿ�€‘Š”e”ôª`@ùë T!è@ùëè@ùëè@ù飑)A‘ ëôªÁT`@ùëATàªS”àªE”`@ùë@ÿÿT ôª`@ùë þÿTôª`@ùëþÿT7”àªA”ý{½©õ ùôO©ý‘󪨀R)Ç…R Æ¥rõªh*y¨€R Ѐ%‘€R©¸u"©Ä” ´ôª!ÿÿð!X&‘àª"€RA”ઔh@ùù@Òë#Tâªàªáª7”ôOB©õ @ùý{èÀ_Ö@ÿÿ�€‘8”h@ùóªë`TઔઔÿÑý{©öW©ôO©ýC‘󪨰ÍGùuB‘@ùèùuùÀ´ôªÝ”öª<ñàù)Tá‘àªâªÙ”è@ùõª`ùh ù¶´ßñATˆ@9¨9è@ùi@ùhù?i(8t@ù”µàªáªâªÉ”è@ùi@ùhù?i(8t@ùô´s@ù`À9t””ñ`8�ÿÿT¨°ÍGùé@ù@ù ë!TôOC©öWB©ý{A©ÿ‘À_Ö@ÿÿ�à‘ç”Æ”(Ð�f9•qAT à*À_ÖÿƒÑý{©ùùø_©öW©ôO©ýC‘¨° ÿ’óªÍGùøª@ùèù @© ËýE“ñ …Ÿš*«_ ëJ1‹š?«y!Šš¹´ ë{Óä”i"@©ôª ËýE“‹c@©·B‘·ù´ ôªõ‹c@©·B‘·ùvµÈ‹h µCñøù#Tá‘àªâªo”è@ù ùèù÷ªX´ñ�TÈ@9è9àªáªâªe”è@ù©@ù¨ù?i(8u&@©¿ ë€TèªmùL@ùl ù«‹�‹�‘l@ù¬ù¬‹Ÿ ëj}©_9`T‹‹ª‹lA‘lùMAø_ ë¡ýÿT À=€€=íÿÿwV@©–‹ÿë¡T ÷B‘ÿëÀTàAøë`ÿÿTI”ùÿÿu@ùu´àªD”‰‹È‚‘t"©¨°i ùÍGùé@ù@ù ëÁTôOE©ù@ùöWD©ø_C©ý{A©ÿƒ‘À_Ööª•ýÿµíÿÿ@ÿÿ�à‘U”4”G”àª)”ДóªÒ”àª0”^îÿ—ý{¾©ó ùý‘ ÕÜàªü” ° Õâ¤ÈGùáªó @ùý{¨øÿƒÑý{©ùùø_©öW©ôO©ýC‘¨°€‘ôªÍGùóªõª@ùèù)¹™ø–\@©vµÈ‹ µÿBñ÷ùCTá‘àªâªã”è@ù€ù(ù—µ àª7´ÿñ�TÈ@99áªâªØ”è@ùi @ùv‘øªhù?i(8ø·V@©wµè‹Hµ¿Bñõù#Tá‘àªâªÂ”è@ùùÈùöªU´¿ñ�Tè@9È9àªáªâª¸”è@ùi@ùhù?i(8¨°ÍGùé@ù@ù ëÁTôOE©ù@ùöWD©ø_C©ý{A©ÿƒ‘À_Ö ÿÿðà‘Ü” ÿÿðà‘Ù”¸”ˆ@ùóªë`Tપ”઴”ÿCÑý{©ó#ùýѨ�é#‘ÍGù3A‘@ù¨ƒøóÿ©ÿc9å#‘‡ÿÿ—à@ùë@T–”¨�ÍGù©ƒ_ø@ù ë¡Tý{C©ó#@ùÿC‘À_Ö“”è@ùëóª`Tઅ”àª�”ÿÃÑý{©ôO©ýC‘¨�飑ÍGù4A‘@ù¨ƒøè#‘A‘ôÿ©ÿã9óÿ©ÿc9䣑å#‘]ÿÿ—à@ùë@Tl”à@ùë@Th”¨�ÍGù©ƒ_ø@ù ë¡TôOF©ý{E©ÿÑÀ_Öe”è@ùëóªÁTà@ùëTàª`”àªR”à@ùë@ÿÿTN”àªX”ÿÃÑý{©ôO©ýC‘¨�飑ÍGù4A‘@ù¨ƒøè#‘A‘ôÿ©ÿã9óÿ©ÿc9䣑å#‘€€€#ÿÿ—à@ùë@T2”à@ùë@T.”¨�ÍGù©ƒ_ø@ù ë¡TôOF©ý{E©ÿÑÀ_Ö+”è@ùëóªÁTà@ùëTàª&”ઔà@ùë@ÿÿT”ઔý{¿©ý‘”qàŸý{Á¨À_ÖÿÃÑý{©ôO©ýC‘¨�ÍGù@ù¨ƒø@¹)@¹ kaT@¹)@¹ káT@¹)@¹ kaT @ù¨´( @ù(´ Aø+Aø3€RH@9i@9 kATL€Rˆ4¹qHil8iil8““šŒ‘ k ÿÿTÈ4¹qTé5 €R(€RňZ¨�ÍGù©ƒ_ø@ù ë¡TôOF©ý{E©ÿÑÀ_Ö( @ùñàŸóÿÿ?¹qaT€ïÿÿèªà£‘ôªA‘⪔à#‘�B‘⪔ÿ¹à£‘á‘%ùÿ—ó*ÿ¹à#‘á‘ ùÿ—h 6é#@) k(€R…ˆZ à*Óÿÿh*¨6r(€RˆZ࣑á#‘T”è@ùé#‘)A‘ ë Tó*જ”à*è@ù飑)A‘ ë€÷ÿTó*ઓ”à*·ÿÿ˜”óªà@ù裑A‘ë Tóªà@ùè#‘A‘ëTà@ù裑A‘ë!Tઇ”z”à@ù裑A‘ë ÿÿTt”àª~”ý{¿©ý‘eÿÿ—qàŸý{Á¨À_Öý{¿©ý‘^ÿÿ—|Sý{Á¨À_Öý{¿©ý‘Xÿÿ—qàןý{Á¨À_Öý{¿©ý‘Qÿÿ—qà§Ÿý{Á¨À_Öý{¿©ý‘Jÿÿ—è *}Sý{Á¨À_ÖÿÃÑý{©÷#ùöW©ôO©ýÑ©�ôª ÕBxóp)ÍGù€Róª)@ù©ƒø@¹ �ÔGùd”hV@©öªÉAøê€Rë·‘I‰šÿ ë Tàªáªâªãª$€R†”h@ùÉ€R i58h@ùwùi78ƒ@¹ � ÕtópÔGùè#‘€RG”á‹@©àªA”à@ùè#‘A‘ë@T”hV@©ê€Ri @ùë·‘I‰šÿ ë Tàªáªâªãª$€Ra”h@ùÉ€R i58h@ùwùi78ƒ @¹ � ÕboópÔGùè#‘€R"”á‹@©àª”à@ùè#‘A‘ë@Tò”‚@ù‚´� @ùઔ‚@ù‚´�@ùઠ”¨�ÍGù©ƒ_ø@ù ëáTôOF©÷#@ùöWE©ý{C©ÿÑÀ_Öã”è@ùé#‘ôª)A‘ ëÁT`@ùëÁTàªÚ”àªÌ”`@ùë@ÿÿTôª`@ùë€þÿT”àªÌ”ÿÑý{©öW©ôO©ýC‘¨�öªóªÍGùàªÁ€Rõª@ùèùÉ”±ÀTÃ@ùhëC TÉ@ù Ë`B‘ ë`ù4‹1Šštµˆ‹Èµ¿BñõùTá‘àªâª‡”è@ù`ùh ùÕ´¿ñÁTˆ@99!Ã@ùtë£TÈ@ù`B‘`ù‹uµ‹ˆµŸBñôùTá‘àªâªm”è@ù`ùh ù”´ŸñáT¨@99áªâªáªâªb”è@ùi@ùhù?i(8¨�ÍGùé@ù@ù ëáTôOC©öWB©ý{A©ÿ‘À_Ö ÿÿÐt(‘!ÿÿð!ô‘âªÕ” ÿÿðà‘‚”a”ÿCÑý{ ©üo ©úg ©ø_©öW©ôO©ýѨ�õ*óªÍGùÁ€Râªôª@ù¨ƒø_”±@T–"@©éc‘øª9A‘ÿ¹ù/ùë1€švµÈ‹È#µÿBñàª÷ùTàc‘áã‘⪔è@ùà/ùè7ù7´ÿñ�TÈ@99áªâª”è@ùé/@ùè3ù?i(8àc‘áS‘‰÷ÿ—ö*à/@ùë@T”Ö6 ñ£Tˆ@ù@9ÁqT‘àªÁ€Râª&”± Tƒ@ùÿ¹ëéTˆ@ùËjËéc‘_ëöª;A‘‹Y1ššû/ùxµ‹èµ?CñàªùùTàc‘áã‘âªß”è@ùà/ùè7ù9´?ñ�T@99áªâª×”è@ùé/@ùè3ù?i(8àc‘áC‘J÷ÿ—ø*à/@ùë@TÛ”ø6_ ñ£Tˆ@ùiw8Áq Tבàªÿ¹áª4÷ÿ—±À T57Ëøªèc‘àªáªâªùc‘x”àc‘á3‘+÷ÿ—ö*à/@ù(C‘ë@T»”ö 6¿ ñ£Tˆ@ùiw8Áq Tàªa€RâªöªÇ”˱õªŸÚèã‘àªáª[”àã‘Ï”`6èc‘¿±ÿ£9A‘öÿ©@Tèc‘àªáª€’ôc‘L”àc‘ác‘‘”à/@ùˆB‘ë@T�”àc‘¸”À 6âB)ã@¹àc‘äã‘åc‘tüÿ—ác‘àª�”àc‘@îÿ— €RRƒ@ùë‰ Tˆ@ùéc‘9A‘‹ù/ùuµ‹( µxËàªCñøùTàc‘áã‘âªX”è@ùà/ùè7ù8´ñ�T¨@99áªâªP”è@ùé/@ùè3ù?i(8àc‘á3‘Ãöÿ—õ*à/@ùë@TT”6ˆ@ù ‹) @9i4iw8ÁqTà*¨�ÍGù©ƒ_ø@ù ëTôOP©öWO©ø_N©úgM©üoL©ý{K©ÿC‘À_ÖâB)ã@¹àc‘·üÿ—ác‘àªO”àc‘òíÿ— €Rèÿÿà* à*è@ùë Tó*àª)”à*è@ùéã‘)A‘ ë ûÿTó*ઠ”à*Ôÿÿ%” ÿÿðà‘B” ÿÿÐt(‘!ÿÿð!ô‘⪌”óªàc‘Ííÿ—óªà@ùë€T ”óªà@ùèã‘A‘ë T”ઠ”óªàc‘ºíÿ—ઔè/@ù)C‘ ëè/@ùëè/@ùëóªaTàªø ”àªê ”àªô ”ý{¾©ôO©ý‘óª(@¹ôª)Aø¹ øÚ ”€Â‘aÂ‘× ”àªôOA©ý{¨À_ÖÿÃÑý{©ø_©öW©ôO©ýѨ�ÍGù@ù¨ƒø@ùˆ´@ùóª@9ÿ¶q`Tÿ®qáTàªÁ€R"€R5€R× ”±  Tè#‘ôª5€RA‘c@ùhëãTi@ùŠËøù ë5‹1Ššuµ¨‹ˆµßBñàªöùTà#‘á‘⪑ ”è@ùàùèù¶´ßñ!T¨@99è'@©è ù?i(8è @ùHµ1áªâª„ ”è'@©è ù?i(8è @ù(´è@ù @9)4 ‘ë *åq(TK@8ë4lŸqëTmqCÿÿTkíQi1âþÿTŸ½qLþÿTµq@þÿTÿ®q@T?ÁqT@9È4à#‘!€RÓõÿ—±ÀT5€Rà@ùëÁTõ*à@ùë@Tb ”•6•‘àªÁ€Râªt ”ôª±!ôÿTc@ùtëã Th@ùé#‘6A‘‹öùsµ‹¨ µŸBñàªôùTà#‘á‘âª2 ”è@ùàùèù´´Ÿñ¡Th@99è'@©è ù?i(8è @ùȵ5 €R;à*9áªâª! ”è'@©è ù?i(8è @ù(´è@ù @9)4 ‘ë *åq(TK@8ë4lŸqëTmqCÿÿTkíQi1âþÿTŸ½qLþÿTµq@þÿTÿ®q@T?ÁqT@9È4à#‘!€Rpõÿ—±ÀT €Rè@ùëÁT à*è@ùë Tó*àªý ”à*ˆðÍGù©ƒ_ø@ù ëTôOF©öWE©ø_D©ý{C©ÿÑÀ_Ö ÿÿ°t(‘!ÿÿÐ!ô‘âªb ” ÿÿÐà‘ ”î ”è@ùëè@ùëóª`TàªÝ ”àªç ”‚ýÿý{¼©ü ùöW©ôO©ý‘ÿÑó‘ôª£“8©¥›9©ˆð*<‘§ƒøJí|’éª`­b­d­ÍGùf­@ù¨ƒøè‘ Ë¿‘耒j‘«ãÑðßòJ‘£CѪ#>©h¡‘ª‘ª#=©àª¡ƒ~­¡ƒ=­ ?Öè*€B‘}@“Aq€ù¶øT¡CÑàªâª” ”¨_ø€ùˆ ù64ßq�T¨@99áªâªŒ ”¨_ø‰@ùˆù?i(8ˆðÍGù©ƒ_ø@ù ëáT¿‘ôOC©ü @ùöWB©ý{ĨÀ_Ö“ ”ý{¾©ó ùý‘ Õ)0àªd ”€ð ÕâñÈGùáªó @ùý{¨` ÿÃÑý{©õKùôO ©ý‘óªˆðàªÍGù@ù¨ƒø( ”àùà‘) ”㪠բëò0àS‘� €R' ”uB‘àS‘uùK ”ôª@ñàù#Tá#‘àªâªG ”è@ùõª`ùh ùT´Ÿñ�TèS@9¨9áS‘àªâª= ”è@ùi@ùhù?i(8ˆðÍGù©ƒ_ø@ù ëÁTôOJ©õK@ùý{H©ÿÑÀ_ÖE ”ý{¾©ôO©ý‘ôª@ù€R‚$€Rù ”ó*1Tà*ø ”1àŸôOA©ý{¨À_Ö�@ù ÿÿа0‘"ÿÿÐBl‘Hîÿ—1àŸôOA©ý{¨À_Ö"€R#€RÿCÑý{ ©÷SùöW ©ôO ©ýC‘ˆðôª÷ªÍGùá*õ*ö*@ù¨ƒø@ùÐ ”1 Tó*â#‘à*á*Ñ ”€45ÿÿеf ‘”@ùˆ ”@¹àªáªüìÿ—õª“@ù€ ”@¹ ÿÿÐÜ‘áªóìÿ—õªá@ùW´áùàªâ*ã*ä*媸 ”õª±Tà*« ”ˆðÍGù©ƒ_ø@ù ëaTàªôOL©÷S@ùöWK©ý{I©ÿC‘À_Ö5ÿÿе.‘ÒÿÿÚ ”b€RC€R³ÿÿÿƒÑý{©öW©ôO©ýшðôªáªÍGù@ù¨ƒø@ùùàª9“ ”óª ´•@ùઢ ”äªàªáªâªãª¬ ”ઊ ”,8 ”@¹qTˆ ”è#‘A‘öù ´ôªŽ ”õªàª¿>ñõù Tà#‘á‘⪉ ”è@ùàùèù5´¿ñ�Tˆ@99áªâª� ”è'@©è ù?i(8á@ù ÿÿÐD‘Šìÿ—à@ùë@T† ”ˆðñÍGù©ƒ_øàŸ@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_Ö€ ” ÿÿÐà‘� ”è@ùóªë`Tàªo ”àªy ”ý{º©ü ùúg©ø_©öW©ôO©ý‘ÿ@Ñÿƒшðéc‘óªÍGù9A‘ôªöªõª@ù¨ƒøùÿ©ÿ£9? ” ø·âªàc‘áªv ”à@ù¡€R/ ” ´áª÷ª/ ” µõª0 ”⪠ÿÿÐ8*‘áªúëÿ—àªáã‘, ”qõŸ4—ùô@ùv@ùઠ”äªàªáªâªãª* ”à@ùë@T. ”ˆðÍGù©ƒ_ø@ù ëATà*ÿ@‘ÿƒ‘ôOE©ü @ùöWD©ø_C©úgB©ý{ƨÀ_Öèã‘A‘÷ùµ´àª ”öªàªß>ñ¶ƒø Tàã‘¡£Ñâªû ”¨ƒ]øàùè'ùö´ßñAT¨@99 àªô ”à@ùëaúÿTÓÿÿáªâªí ”¨ƒ]øé@ùè#ù?i(8à@ù@9ë@Tô ”ß¾q@ T(ÿÿ°™&‘êã‘ @ùq@øVA‘!ÿÿÐ! ‘àªé'ùèóøè€Rÿÿ©ö£©ÿ_9 ”è@ùëöª`TàªÜ ”v´¨£Ñ7ÿÿ°÷V8‘A‘¿ƒ8ºÿ=©àC‘á#‘B€RãªÄ ”±`Tà @ùâã‘áªÂ ”q�þÿT¸^øàã‘­ ”䪠£Ñãã‘áªâª· ” £Ñá€R€’+ ”± üÿTøªàªž ”㪠£Ñáªâª­ ”±`ûÿTભ ”à @ùƒ ” ƒ]ø¡€Rˆ ”´€ù¡£Ñધ ”5€R õ*à@ùëáíÿToÿÿચ ”à @ùp ”õ* ƒ]øë`ìÿT“ ”aÿÿ ÿÿЀ‘» ”– ” ÿÿÐà‘³ ”  óªà@ùë`T€ ”ઊ ”óª ƒ]øëÁTà@ùëáþÿTàª� ”t ”à@ùë`ÿÿTðÿÿ@ù@9½qàŸÀ_Öý{¾©ôO©ý‘óªJ ”€´ôOA©ý{¨À_ÖôªH ”⪠ÿÿÐ8*‘áªëÿ—àªôOA©ý{¨À_Öý{½©õ ùôO©ý‘ôª@ùõª!€R. ”óª ùà´ñàŸôOB©õ @ùý{èÀ_Ö”@ù+ ”⪠ÿÿБáª>ëÿ—ñàŸôOB©õ @ùý{èÀ_Öý{¿©ý‘% ”à4 ”᪠ÿÿ°ˆ0‘ý{Á¨Qìÿý{Á¨À_Öý{¿©ý‘áªB€R+ ”ý{Á¨À_ÖÿƒÑý{©õ#ùôO©ýшðé#‘óªÍGù5A‘@ù¨ƒø@ùù ÿÿÐP ‘9õÿ©ÿc9 ”`´ôªö ”äªà#‘áªâªãª ”è @ù(´à#‘á*ª” 6á@ù ÿÿа#‘µêÿ—à@ùá* ” 4á@ù ÿÿÐð ‘­êÿ—è@ùÿ ù9!ÿÿÐ!+‘à#‘ûìÿ—!ÿÿÐ!Ü ‘à#‘÷ìÿ—à@ùá*ô ”@4á@ù ÿÿД‘šêÿ—ó*à@ùë!Tà@ùÁ€Rç ” 4á@ù ÿÿÐ,$‘�êÿ—á#‘àª× ”3€Rà@ùë@TÎ ”ˆðÍGù©ƒ_ø@ù ëáTà*ôOE©õ#@ùý{C©ÿƒ‘À_ÖÉ ”è@ùóªë`T઻ ”àªÅ ”ý{½©õ ùôO©ý‘(@ùóª?ù9¸ ”@´t@ùõª” ”äªàªáªâªãªž ”h@ùñàŸôOB©õ @ùý{èÀ_ÖEÿƒÑý{©õ#ùôO©ýшÐé#‘óªÍGù5A‘@ù¨ƒø@ùù ÿÿ°ä‘9õÿ©ÿc9’ ”`´ôªo ”äªà#‘áªâªãªy ”è @ù(´à#‘á*#” 6á@ù ÿÿ°@-‘.êÿ—à@ùá*€ ” 4á@ù ÿÿ°´-‘&êÿ—è@ùÿ ù9!ÿÿ°!+‘à#‘tìÿ—!ÿÿ�!`&‘à#‘pìÿ—à@ùá*m ”@4á@ù ÿÿ°H‘êÿ—ó*à@ùë¡Tà@ù�€R` ” 4á@ù ÿÿ°H‘êÿ—á#‘àªP ”á@ù ÿÿ�à7‘ÿéÿ—3€Rà@ùë@TC ”ˆÐÍGù©ƒ_ø@ù ëáTà*ôOE©õ#@ùý{C©ÿƒ‘À_Ö> ”è@ùóªë`Tàª0 ”àª: ”ý{¾©ó ùý‘á*óªÏ”è*à*È6`@ùá€R. ”qàŸó @ùý{¨À_Öý{½©öW©ôO©ý‘@ùóªù ÿÿ°H‘9 ”@´t@ùõªö”äªàªáªâªãª ”h@ùH´àªá*ª” 6`@ùá€R ”à4a@ù ÿÿ°à$‘úéÿ—à*ôOB©öWA©ý{èÀ_Ö| ”ôª¹ ” ”€µˆ@¹q`ÿÿT ÿÿ°(‘ééÿ—à*ôOB©öWA©ý{èÀ_Ö@ùöªu@ùàªÇ”äªàªáªâªãªÑ”àªá*}” 6`@ùá€RÞ ”@4Á@ù ÿÿ°1‘Íéÿ—à*ôOB©öWA©ý{èÀ_Ö €RôOB©öWA©ý{èÀ_ÖÿƒÑý{©öW©ôO©ýшÐóªÍGù@ù¨ƒøœÿÿ—€ 6!ÿÿ°!X‘િëÿ—àªá*W” 6`@ùá€R¸ ” 4`@ù8€RÀ ” 4. ”@¹DqáTàªá*G”è*à*h6`@ùá€R¦ ”qàŸ- €R+s@ùo ”è#‘A‘öù ´ôªu”õªàª¿>ñõù Tà#‘á‘âªp”è@ùàùèù5´¿ñ�Tˆ@99áªâªh”è'@©è ù?i(8â@ù ÿÿ°” ‘áªpéÿ—à@ùë@Tl”à*ˆÐÍGù©ƒ_ø@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_Ög” ÿÿ°à‘„”è@ùóªë`TàªV”àª`”à*À_ÖÿƒÑý{©öW©ôO ©ýÑóªˆÐ)ÿÿ°)Q&‘ÍGùöª)@ùô*@ù¨ƒøˆÎ�R¨Œ®rÉøhr¸h€Rv"©¨£ÑA‘n9µÿ=©¿ƒ8 ÿÿ�0<‘¡£Ñwõÿ—€6¡£Ñàª4”à*Øîÿ—è#‘)õÿ—#ÿÿ°c ‘à#‘áªâª$€R”裑A‘èªôùAø?ë T @ùáùéù À=᪀€= @ù@9éù|©àªëÿ—à@ùë@T ”à@ùè#‘A‘ë@T” ƒ]øë@T”ˆÐÍGù©ƒ_ø@ù ëÁTôOI©öWH©ý{G©ÿƒ‘À_Öÿ”è@ùëôªÁTà@ùè#‘A‘ëáT ƒ]øëAT`@ùëaTàªò”àªä”à@ùè#‘A‘ë@þÿTôªà@ùè#‘A‘ë`ýÿT×” ƒ]øë@ýÿT ôª ƒ]øë üÿTôª ƒ]øëüÿTÉ”`@ùëàûÿTÅ”àªÏ”ÿCÑý{©ü“ùöW©ôO©ýC‘ˆÐ)€RóªÍGùöªõª@ù¨ƒø)9!ÿÿ°! ‘@ùä”@´è#‘ôªá#‘A‘àªÿc9öÿ©£”à@ùë@T£”à#‘ €R⪻” ´à#‘„”¨@ùù@Òë# Tâªá#‘સ”¨@ùþÿ´©@ùÑ(ia8)qaýÿTàª"€R”¨@ù¨´àª†” €RˆÐÍGù©ƒ_ø@ù ë@ T†””@¹s@ùq¡T ÿÿ°œ‘áªéÿ—à*ß9ˆÐÍGù©ƒ_ø@ù ëTîÿÿa@ù ÿÿ° 4‘‹éÿ—àªf”)@”è#‘A‘öù€´ôªF”õªàª¿>ñõù Tà#‘á‘âªA”è@ùàùèù5´¿ñ�Tˆ@99áªâª9”è'@©è ù?i(8â@ù ÿÿ°¤.‘áªAèÿ—à@ùë@T=”à*ˆÐÍGù©ƒ_ø@ù ë÷ÿTôOT©ü“@ùöWS©ý{Q©ÿC‘À_Ö ÿÿ°€‘Y” ÿÿ°à‘R”dãÿ—è@ùóªë`Tàª#”àª-”ÿCÑý{©ôO©ýшÐé#‘óªÍGù4A‘@ù¨ƒøôÿ©ÿc9 ÿÿ�h5‘á#‘Sôÿ—€6á#‘ઔ3€Rà@ùë@T”ˆÐÍGù©ƒ_ø@ù ëÁT`ôOD©ý{C©ÿC‘À_Ö£íÿ—᪔ó*à@ùëÁýÿTîÿÿû”è@ùóªë`Tàªí”àª÷”ÿÃÑý{©÷CùöW ©ôO ©ýшÐóªô*ÍGù@ù¨ƒø(@ù?ù9¨£Ñ‰þÿ—¡ƒ]ø ÿÿ�1‘gèÿ—裑ÿã9A‘ÿ9õÿ© £Ñá‘⣑ ÿÿ—@6ᣑàªÍ”a@ù ÿÿ�X+‘Vèÿ—è@9qóŸà@ùë@T¾” ƒ]ø¨£ÑA‘ë@T¸”ˆÐÍGù©ƒ_ø@ù ëATà*ôOJ©÷C@ùöWI©ý{G©ÿÑÀ_Öè@9ˆ5Píÿ—k!Tè#‘ £Ñáëÿ—!ÿÿ°!€&‘à#‘ö#‘ªéÿ—á@ù ÿÿ°x/‘+èÿ—à#‘á‘⣑÷#‘Óþÿ—ô*à@ùèB‘ë@T�”tø7ó*à@ùë�ùÿTÌÿÿ�”è@ùÉB‘óª ëaTà@ùë!T ƒ]ø¨£ÑA‘ëAT઄”àªv”à@ùë þÿT óª ƒ]ø¨£ÑA‘ë`þÿT óªà@ùë ýÿTf” ƒ]ø¨£ÑA‘ëýÿT`”àªj”ÿƒÑý{©õ#ùôO©ýшÐé#‘óªÍGù5A‘@ù¨ƒøõÿ©ÿc9 ÿÿ°Ä&‘á#‘�óÿ— 6á#‘àªL” îìÿ—ô*ììÿ—k!Tb@ù#ÿÿ�cø+‘àªáª$€R3”3€Rà@ùë@T6”ˆÐÍGù©ƒ_ø@ù ë�Tà*ôOE©õ#@ùý{C©ÿƒ‘À_Öó*à@ùëþÿTðÿÿ,”è@ùóªë`Tઔàª(”ý{½©õ ùôO©ý‘õªó*½ìÿ—ô*kT¢@ù#ÿÿ�cø+‘àªáª$€R”ŸkàŸôOB©õ @ùý{èÀ_Öý{¾©ôO©ý‘A€Râªóªôª”±`Tèªc@ùëCTâŸàªáªo”àªáªô”àªA€R⪔誱þÿTàªá€Râªý”±`Tèªc@ùëCTâŸàªáªW”àªáªÜ”àªá€Râªì”誱þÿT誊B‘ AøŠù?ë T @ù‰ùŠ ù À=@€=i@ùh~©B9‰ùôOA©ý{¨À_Ö ÿÿ�t(‘!ÿÿ°!X‘âª0”ý{º©üo©úg©ø_©öW©ôO©ý‘ÿƒÑôªˆ°©ÃÑÍGù;A‘¸CÑC‘á*@ùનø(ÿÿ�-+‘ @ùq@ø»9©¿8©øsøè€R³#7©¿ó8¥”à4µ[y©èã‘A‘÷ùu-´làã‘õ㑯”¡Wø b‘€R¯”é@ùêã‘èª)�^ø@ ‹h´á* @¹2¨”è@ùõã‘ �^ø© ‹)!@¹é(5©ÃÑ*‰ˆR¿8)A‘ª rÿ£9éo©©=©©CÑ+A‘ª¸i€Rêc‘«';©IA‘*ÿÿ�J™‘é/©J@ùéÿ ©é+‰R‰¨§rÿO9éó ¸éã‘+A‘i€Rÿ£9é+ ©éc‘)A‘ë_ùéÿ ©�^øé/©¨‹y@ùµ9´¨â@9h4¢A9 àªz”¨@ù@ùàªA€R?Öâ*àã‘¡ÃÑõã‘t”èã‘ù*ü*º‚‘»Â‘A‘ÂA9àã‘¡ÃÑi”è@ù�^øHkh87y69€R6<€R( 7è@ù�^øvkhøvµ£¡{© ÃÑâª"”´ù*\þ7á�K© ÃÑ⪔ ´ü*è@ù�^øvkhøV2´Èâ@9hûÿ5àªA”È@ù@ùàªA€R?Öâ*Óÿÿ¶ƒ]øß ñ‰1T¨]øõ?ù ‘wµ‹h0µØÑàªCñøùTàã‘áÑâªè”è@ùà?ùèGùßñ`TßñÁTè@99&¶ƒ]øß*ñI/T¨]øõ?ù-‘wµ‹(.µØ.ÑàªCñøùTàã‘áÑâªÌ”è@ùà?ùèGùß.ñ Tß2ñaTè@99è@ùé?@ùèCù?i(8èC@ùˆµ#áªâª½”è@ùé?@ùèCù?i(8á‹G©àc‘ê”à?@ùëòÿT”9€Rüñ7�ÿÿáªâª¬”è@ùé?@ùèCù?i(8èC@ù(´àã‘!ÿÿ�!l+‘⪣€R4”±ÀTü*à?@ùë!Tyÿÿá‹G©àc‘Ê”<€Rà?@ùë@îÿT¡”pÿÿ9€R<€Rèã‘a‘Ï”µè@ùéã‘�^ø ‹ @¹2»”ù6á‹M© Ãѳ”œ7ù7=œ6¨ƒYø ð’ ë@$Tÿÿð!5‘ ÃÑ"€R¦”á‹I© ÃÑ£”µ[y©èc‘A‘÷/ùuµ¨‹"µßBñàªöùTàc‘áÑâª\”è@ùà/ùè7ù6´ßñ�T¨@99áªâªT”è@ùé/@ùè3ù?i(8èã‘àc‘õã‘Xþÿ— ÃÑáã‘U”à?@ù¨B‘ë@TT”à/@ùë@TP”àO@ùè@ùë@TK”à_@ùè@ù¸CÑû@ùõã‘ë@TC”ào@ùè @ùë@T>” [øè@ùë@T9” ]øè@ùë@T4”�°àã‘!ÜGùh” B‘j”µ[y©èã‘A‘÷ùuµ¨‹(µßBñàªöùTàã‘áã‘⪠”è@ùàùè'ù6´ßñ�T¨@99áªâª”è@ùé@ùúã‘UC‘J�R¹CÑè#ùªŒ­r?i(8È€R©ÃÑèy¨€R6A‘õ£©(Œ�R.­rê ¹¨¸(Í�R¨Ì¥r¿s83¸è€R8C‘¶#=©Hî�Rhl­r¿c8¨¸(Ï…R¨CxÈ€R¸#;©àã‘ãã‘᪢€R,”à4¢ƒ]øàã‘£ÃÑáª&” 4¢ƒ[øàã‘£CÑ᪠”`5(#‘H#‘@ùÿÿð!5‘àã‘#€RÒ”¢ƒ]øÿÿð!5‘àã‘#€R˔誱Tÿÿð!5‘àã‘‘#€R”誱àTã#@ùbëC Tàã‘áª9”è@ù‰B‘‰ùë Té'@ùˆù‰ ùàÀ= €= [øè#@ù÷ÿ©ÿ#9ëˆù@Tª” ]øë@T¦”à@ùë@T¢”à@ùë@Tž” Wøë@Tš” Yøë@T–”ˆ°ÍGù©_ø@ù ë¡Tÿƒ‘ôOE©öWD©ø_C©úgB©üoA©ý{ƨÀ_ÖË” ÿÿ�à‘¬”‹” ÿÿ�à‘¨”ÿÿðt(‘!ÿÿ�!ô‘b€Rãªñ” ÿÿ�à‘ž”ÿÿðt(‘!ÿÿ�!ô‘b€Rãªç”ÿÿðt(‘!ÿÿ�!X‘âªá” ÿÿ�à‘Ž” ÿÿ�€‘�”è/@ùë \[Z*/è?@ùëôªÁTàO@ùè@ùë� Tà_@ùè@ùë¡ Tào@ùè @ùëÁ T [øè@ùëá T ]øè@ùë TXàª?”àO@ùè@ùë@ýÿT<ôªûùN32oßÿ—nßÿ—ûùôª Wøë`TNôª [øûùëaT ]øë�Tà@ùë¡Tà@ùëÁT Wøë¡T Yøè@ùëÁTàª"”” ]øëÀýÿT”à@ùë ýÿT ”à@ùë€ýÿT ” Wøë`ýÿT&ôªàO@ùè@ùëÀõÿTþ”à_@ùè@ùë õÿTù”ào@ùè @ùë€õÿTô” [øè@ùë`õÿTï” ]øè@ùë@Tê”�°àã‘õã‘!ÜGù” B‘” Wøë øÿTß” Yøè@ùë€øÿTÚ”àªä”ý{¿©ý‘@ùá*ß”qàŸý{Á¨À_Öý{½©õ ùôO©ý‘óªÿÿðP)‘᪔ ´t@ùõªª”äªàªáªâªãª´”઒” €RôOB©õ @ùý{èÀ_Öà€Rÿ”@ÿÿ´áªø”`ýÿµöÿÿÿƒÑý{©öW©ôO©ýш°ôª ÕÿÿÍGùá#‘@ù¨ƒøŒ”ó*`4õ@ù–@ùં”äªàªáªâªãªŒ”ˆ°qÍGù©ƒ_øàŸ@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_ÖŽ”ÿƒÑý{©öW©ôO©ýщ°èªôª)ÍGùá#‘àª)@ù©ƒød”ó*`4õ@ù–@ùàªZ”äªàªáªâªãªd”ˆ°qÍGù©ƒ_øàŸ@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_Öf”à*À_Öà*À_ÖÿÃÑý{©÷#ùöW©ôO©ýш°ôªõ*ÍGùáª@ù¨ƒø@ù“”óª@´•@ùàª.”äªàªáªâªãª8”ઔˆ°ñÍGù©ƒ_øàŸ@ù ëÁTôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö·”@¹qàýÿTÕý7”@ù”è#‘A‘÷ùÀ´õª ”öªàªß>ñöù Tà#‘á‘⪔è@ùàùèù6´ßñ�T¨@99áªâªþ”è'@©è ù?i(8â@ù ÿÿ�Ì ‘áªãÿ—à@ùëÀøÿT”Äÿÿ” ÿÿ�à‘%”è@ùóªë`Tàª÷”ઔ@ùãªâ*ÿÃÑý{ ©üo ©úg ©ø_ ©öW ©ôO©ýC‘ˆ°óªô*ÍGùõª@ù¨ƒø1” ´öª2”` ´ùªÿÿð÷5‘ûÿ‰8ÿÿ�‡‘(3A8ˆ9è@ùI@ùûÿ‰Hù?i(8h@ù�‘hùઔùªÀ ´ @ù!O‘"€R”ÿÿ5(K@9ˆ q¬TH4 q`Tðÿÿq Tq�ýÿTtý7ઔá*"O‘ã#‘à*ä* ”1üÿTè@¹ ”6@qaûÿT@QjáúÿT O‘᪔`úÿ4 O‘áªÿ”àùÿ4z¢@©_ë T\C‘ O‘\ù{”ûª@ñàù#Tá#‘àªâªw”@ùüªè@ùH ù{öÿ´ñàõÿT!O‘àªâªp”¬ÿÿ!O‘àª�”°ÿÿàªÝ”ˆ�ÍGù©ƒ_ø@ù ë!TôON©öWM©ø_L©úgK©üoJ©ý{I©ÿÑÀ_Ör”ÿCÑý{©ó#ùýш�é#‘J€RÍGù3A‘ãª@ù¨ƒø(€R@ùê3yó£©á#‘â*gÿÿ—à@ùë@TS”ˆ�ÍGù©ƒ_ø@ù ë¡Tý{C©ó#@ùÿC‘À_ÖP”è@ùëóª`TàªB”àªL”@ùãª"€RLÿÿÿCÑý{©ó#ùýш�é#‘J€RÍGù3A‘ãª@ù¨ƒø(€R@ùê3yó£©á#‘"€R9ÿÿ—à@ùë@T%”ˆ�ÍGù©ƒ_ø@ù ë¡Tý{C©ó#@ùÿC‘À_Ö"”è@ùëóª`Tઔઔà*À_Öà*À_Ö@ù(@ù_ëaT‚´ý{¿©ý‘!@ù@ùl”qàŸý{Á¨À_Öà*À_Ö €RÀ_ÖÿƒÑý{©ùùø_©öW©ôO©ýC‘ˆ� ÿ’óªÍGùõª@ùèù @© ËýE“ñ …Ÿš*«_ ëJ1‹š?«y!Šš´ ë{Ó)”i"@©ôª ËýE“ôª–‹àª×B‘×ù¾”øª@ñàù#Tá‘àªâªº”è@ùÀùèù÷ªX´ñ�T¨@9è9àªáªâª°”è@ùÉ@ùÈù?i(8u&@©¿ ë€TèªmùL@ùl ù«‹�‹�‘l@ù¬ù¬‹Ÿ ëj}©_9`T‹‹ª‹lA‘lùMAø_ ë¡ýÿT À=€€=íÿÿwV@©–‹ÿë¡T ÷B‘ÿëÀTàAøë`ÿÿT””ùÿÿu@ùu´àª�”‰‹È‚‘t"©ˆ�i ùÍGùé@ù@ù ëaTôOE©ù@ùöWD©ø_C©ý{A©ÿƒ‘À_Ööª•ýÿµíÿÿ‚”•”àªw””óª ”àª~”¬Üÿ—ý{¾©ó ùý‘ ÕÓ¥PàªJ”€� Õ¢nÈGùáªó @ùý{¨Fý{¼©ý‘õ[©•�óS©”�µ‚2‘”¢3‘”Ë÷c©öªø*÷ª””þC“T´€Ò£zsøâªáªà*s‘`?Öë!ÿÿTóSA©õ[B©÷cC©ý{ĨÀ_ÖÀ_Öý{¿©ý‘CØÿ—ý{Á¨À_Öý{¿©ý‘ý{Á¨À_Öð{¿©��êGùB?‘ Ö Õ Õ Õ��îGùb?‘ Ö��òGù‚?‘ Ö��öGù¢?‘ Ö��úGùÂ?‘ Ö��þGùâ?‘ Ö�°@ù‘ Ö�°@ù"‘ Ö�° @ùB‘ Ö�°@ùb‘ Ö�°@ù‚‘ Ö�°@ù¢‘ Ö�°@ù‘ Ö�°@ùâ‘ Ö�°"@ù‘ Ö�°&@ù"‘ Ö�°*@ùB‘ Ö�°.@ùb‘ Ö�°2@ù‚‘ Ö�°6@ù¢‘ Ö�°:@ù‘ Ö�°>@ùâ‘ Ö�°B@ù‘ Ö�°F@ù"‘ Ö�°J@ùB‘ Ö�°N@ùb‘ Ö�°R@ù‚‘ Ö�°V@ù¢‘ Ö�°Z@ù‘ Ö�°^@ùâ‘ Ö�°b@ù‘ Ö�°f@ù"‘ Ö�°j@ùB‘ Ö�°n@ùb‘ Ö�°r@ù‚‘ Ö�°v@ù¢‘ Ö�°z@ù‘ Ö�°~@ùâ‘ Ö�°‚@ù‘ Ö�°†@ù"‘ Ö�°Š@ùB‘ Ö�°Ž@ùb‘ Ö�°’@ù‚‘ Ö�°–@ù¢‘ Ö�°š@ù‘ Ö�°ž@ùâ‘ Ö�°¢@ù‘ Ö�°¦@ù"‘ Ö�°ª@ùB‘ Ö�°®@ùb‘ Ö�°²@ù‚‘ Ö�°¶@ù¢‘ Ö�°º@ù‘ Ö�°¾@ùâ‘ Ö�°Â@ù‘ Ö�°Æ@ù"‘ Ö�°Ê@ùB‘ Ö�°Î@ùb‘ Ö�°Ò@ù‚‘ Ö�°Ö@ù¢‘ Ö�°Ú@ù‘ Ö�°Þ@ùâ‘ Ö�°â@ù‘ Ö�°æ@ù"‘ Ö�°ê@ùB‘ Ö�°î@ùb‘ Ö�°ò@ù‚‘ Ö�°ö@ù¢‘ Ö�°ú@ù‘ Ö�°þ@ùâ‘ Ö�°Aù‘ Ö�°Aù"‘ Ö�° AùB‘ Ö�°Aùb‘ Ö�°Aù‚‘ Ö�°Aù¢‘ Ö�°Aù‘ Ö�°Aùâ‘ Ö�°"Aù ‘ Ö�°&Aù" ‘ Ö�°*AùB ‘ Ö�°.Aùb ‘ Ö�°2Aù‚ ‘ Ö�°6Aù¢ ‘ Ö�°:Aù ‘ Ö�°>Aùâ ‘ Ö�°BAù ‘ Ö�°FAù" ‘ Ö�°JAùB ‘ Ö�°NAùb ‘ Ö�°RAù‚ ‘ Ö�°VAù¢ ‘ Ö�°ZAù ‘ Ö�°^Aùâ ‘ Ö�°bAù ‘ Ö�°fAù" ‘ Ö�°jAùB ‘ Ö�°nAùb ‘ Ö�°rAù‚ ‘ Ö�°vAù¢ ‘ Ö�°zAù ‘ Ö�°~Aùâ ‘ Ö�°‚Aù ‘ Ö�°†Aù" ‘ Ö½  $ 4 Ñ ‡ � §  ûÿÿo¸H ùÿÿo¨ À@ Ü Û õþÿoÀ  H˜  Ü ððÿÿoØ þÿÿo¼ÿÿÿo‹¹ja 8r{“× 2õ¹æï®3î;-Î$³j®19ff3e9c3602ae8e841925bb461a0adb064a1f1903667a5e0d87e8f608f425acapp.dll@(#)Version 9.0.525.21509 @Commit: e36e4d1a8f8dfb08d7e3a6041459c9791d732c01d38cc827-e34f-4453-9df4-1e796e9f1d07clang version 18.1.8GCC: (Ubuntu/Linaro 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609Linker: LLD 18.1.8.init_array.fini_array.text.got.comment.got.plt.rela.plt.init.tbss.bss.dynstr.eh_frame_hdr.jcr.gnu.version_r.interp.data.rel.ro.rela.dyn.gnu.version.dynsym.gnu_debuglink.fini.gnu.hash.relro_padding.note.ABI-tag.eh_frame.gcc_except_table.tm_clone_table.note.gnu.build-id.dynamic.shstrtab.rodata.dataapphost.dbg¾Ê†{ààÝüü $§ @@˜ šÿÿÿoØ Ø âlþÿÿo¼¼ÄöÿÿoÀÀQÜÜÛ �¸¸H6B¨ =2¨(¨( õ´=´=˜YLFLF„ëÐJÐJä¸f¸f$Ÿ@Üܾðð;�F� � g� �  ˜ ˜     Hƒè è H*0 0  PPp-ÀÀPÎð EµÈÈLÈÈ#$0Èe3-K¯xopen-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/libc/dotnet-app-arm64-musl000077500000000000000000002264001524057645600320360ustar00rootroot00000000000000ELF·Ðg@%@8 @ @@@  àààÐgÐgÐgÐgÐg°®°®€€€0€ °°°° €€€ààRåtd€€€0€ Påtd˜F˜F˜F¤¤Qåtdüüü$$/lib/ld-musl-aarch64.so.1GNUz)¨“sRU-jзÑFsT>Û¼  ; U"d"|"’ªÂÏáè,3xÁÈ.@Pe¤é$07=ENT^dkqxÆÎÚáì/oºÝjºËÓñ[bjxˆÍUZajou{€‡Œ•œ¢ª±¹ÁÈ $+4:@n²î { ­ Ø   ` x ¦ ¯ ¹ Á É Ñ × ß è ìÜí ii û P&y  ooêÓï¹�ñ_fini_init__libc_start_main_ITM_deregisterTMCloneTable_ITM_registerTMCloneTable__cxa_finalize__deregister_frame_info__register_frame_info_ZNSt8ios_base4InitC1Ev_ZNSt8ios_base4InitD1Ev__cxa_atexit__stack_chk_guardstrlen_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_createERmmmemcpy_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEmmPKc_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_replaceEmmPKcm_ZdlPv_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4___stack_chk_fail_Unwind_Resume__cxa_begin_catch_ZSt9terminatev__gxx_personality_v0_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4findEcm_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm_ZSt19__throw_logic_errorPKc_ZSt20__throw_length_errorPKcsched_yieldstderrfopensetvbufvfprintffputcvsnprintf_Znwmmemsetfputsstdoutfflush_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_mutateEmmPKcmstrncmpstrncasecmpstrcmpstrcasecmp_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5rfindEPKcmm_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5rfindEcm_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE12find_last_ofEPKcmm_ZSt24__throw_out_of_range_fmtPKcz_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE8_M_eraseEmm_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE14_M_replace_auxEmmmc_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE17find_first_not_ofEPKcmm__errno_locationstrtoul_ZSt20__throw_out_of_rangePKc_ZSt24__throw_invalid_argumentPKctoupper_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7reserveEmstrchrtolower__cxa_rethrow__cxa_end_catch_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareERKS4__ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE12_M_constructEmc_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6substrEmmtimegmtimestrftimeopenclosefstatmmapgetcwdfreestrerrordlopendlsymdlerrordladdrdlclosegetlinesscanf_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4findEPKcmmfcloseatoigetenvaccessgetuidgetpwuidmkdirfgets_ZNSt13basic_fstreamIcSt11char_traitsIcEEC1Ev_ZNSt13basic_filebufIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmode_ZNSt9basic_iosIcSt11char_traitsIcEE5clearESt12_Ios_Iostate_ZNKSt5ctypeIcE13_M_widen_initEv_ZSt7getlineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7__cxx1112basic_stringIS4_S5_T1_EES4__ZNSt13basic_filebufIcSt11char_traitsIcEE5closeEv_ZTTSt13basic_fstreamIcSt11char_traitsIcEE_ZNSt13basic_filebufIcSt11char_traitsIcEED2Ev_ZNSt8ios_baseD2Ev_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEmmRKS4__ZSt16__throw_bad_castv_ZNSt13basic_fstreamIcSt11char_traitsIcEED1Evrealpathgetauxvalopendirreaddirfnmatchdirfdfstatatclosedirbcmplibgcc_s.so.1GLIBC_2.0GCC_3.0$ORIGIN/netcoredepslibstdc++.so.6libc.musl-aarch64.so.1€ðhˆ0i�To˜tz D‰¨¤”°Ðø ÞÀlÈ�hÐ^=ØB9à@Dè5ð³>ø93A3»5û9øìÜXyXÌE°° (08@ àHP%``€ˆ�˜ ¨ ° ¸ À ÈÐØàèðø (08@H P!X"`#h$p&x'€(ˆ)�*˜+ ,¨-°.¸/À0È1Ð2Ø3à4è5ð6ø789:; <(=0>8?@@HAPBXC`DhEpFxG€HˆI�J˜K L¨M°N¸OÀPÈQÐRØSàTèUðVøWXYZ[ \(]0^8_@aHbPcXd`ehfpgxh€iˆj�k˜l m¨nÿÿ0ä¸ ôTÈüð4øÈ$˜¸ ø´¬¸ àÈÿœlÿÿÈÈÀØtÌlĸ(ÿÿ PP,À|hÿœÀ±H  ”¬ ¼¨ �   œÐì,° œ¤ ¤ ° °8è   ¸ˆ ÀŒ ðX� È Ø ÔÔ Ü° Œ¤ ˜Ü Ì<„ ˆ ¬ � ¨   @à ð œ € Œ ° (Ü Ø 4  Œ 8Ä Ð ô € ü ÀÿÿLLø`¼ÿÿfààÌ„ØØ è@¨Ô˜ü Ä � ° Ĩ üÌÜ Ð ” ä ,È è ¼ ô „€ ˜ Ø ¤ Ü ° ÄÿÿJTT Ü` ÈÀ¨È ¬ÔÈÜdÐôÄü È œÜ ˜ìdðдÿÿ¤¤dÔ¨ ˜´¼ÿÿ0ÐHÌ\ Èh„ÿœ1#ŒŒˆ¨ÌŒÔÌ ÄØ´ÿœÄĬÈhÿœ)¨¨Ð¬$дØüÿœÄĬÈhÿœ´´¤¸pÿœÄĬÈhÿœ `` ÿÿ¨¨4°Ütÿÿ¨¨Ð°Àÿÿœœ(˜ÄtÿœQCÈÈ ìÔа�À@€””xŒˆ Pð Ðü �ˆhÿœ 0ÿÿ4€tü„˜ÿÿ4àT܈à˜dÿœaTxx üœ ôØDüœ<Øø°ˆÀ<ü„”€ÌÌ ŒØ „äè üôTÿÿ HPà˜hÿÿÀ €Ì(ô €€ÿÿ<<àX¬ÿÿ ŒÀ¨¸ÿÿ8 €P¬”üdÿÿ¨¨äÀ È¸ÿÿÄÄ Ô<�  œ$ÿÿ¨¨äÀ È¸ÿÿ,,¬d ¨p ô|�ŒÄÿÿB¨¨ÄŒ€\ Ü´è°ˆœŒ Èô  € ŒŒ Ș¤ÿÿ H`¨¨ ÿœ9-ÐÐüàŒì üøˆŒ�œ” ÿÿèè�øˆ€ �Œ$ÿÿ0t8\ÿÿ @ ˜L�ÿÿ @¤X�ÿÿ  ð´ ìÈTŒœÈÿÿ5””ô Ì ð „´ ¼ô ô ° ¬ ¤ Ð ô ì ¤ÿÿWÜÜ ô èðØ è äpÔ Ô àhȸ ìœ „ÌÐ ä ÜŒè ˜ ü   Œ ˆÿÿ˜˜ Ф€¤ İÀÿÿˆˆ è”tÿÿP`˜tˆ„,Œ°D�¤„Ø�à°�´Ð”¸€ä ˜ðô „€lÿÿ `è�Èhÿÿ `ø Øhÿÿ¼¼œÌpÿÿh üˆ�˜Ðü세ÿœ)€€ ŒŒ” �°€ÿÿ 4l¸ 8ÿÿ!HH äh¤€” Œ 8�ØÜÿÿ 8TèŒ|ÿÿ¡˜ ¤¨8¨ 0øð°üà Ðœà`À¨ÈxÀ”è„ø˜ ˜¨ @è ˆø �ˆ¼˜¸ÐÌÔðøô ¼€„(¸¬(´ÔÔð ˜ü øˆ�ÿÿÀÀôÐDÿÿ @ ˆL\ÿÿ @ ˆL\ÿœ1"¸¸ØÈœäèìøð  app-local: [%s]false0123456789The managed DLL bound to this executable is: '%s'}You must install .NET to run this application. %shttps://go.microsoft.com/fwlink/?linkid=2063370Considering fxr version=[%s]... coreservicing/proc/self/mapsUsing environment variable %s=[%s] as runtime location.Not found ]aRuntime config is cfg=%s dev=%s%s: __pos (which is %zu) > this->size() (which is %zu)/proc/self/exeThe managed DLL bound to this executable is longer than the max allowed length (%d)fxrError: [%s] does not existFound registered install location '%s'./usr/share/dotnet global install location [%s] self-registered config location [%s] environment_variablelinux-musl-arm64riscv64s390xFailed to resolve full path of the current executable [%s]Invoking fx resolver [%s] hostfxr_main_startupinfoResolved fxr [%s]...Checking if CoreCLR path exists=[%s]Failed to unload library, error: %sLooking for architecture-specific install_location file in '%s'.The library %s was found, but loading it from %s failedThe required library %s does not support single-file apps.Reading fx resolver directory=[%s]COREHOST_TRACECOREHOST_TRACE_VERBOSITY.loongarch64_DOTNET_TEST_GLOBALLY_REGISTERED_PATHError: [%s] does not contain any version-numbered child foldersCOREHOST_TRACEFILEDOTNET_ROOT_&framework_version=x64linux-muslUsing core servicing at [%s]%*p-%*p %*[-rwxsp] %*p %*[:0-9a-f] %*d %s This executable is not bound to a managed DLL to execute. The binding value is: '%s'.runtimeconfig.dev.jsonThe required library %s could not be found. Search location options [0x%x].runtimeconfig.json_DOTNET_TEST_INSTALL_LOCATION_PATH%s App: %s Architecture: %s App host version: %s .NET location: %s Learn more: https://aka.ms/dotnet/app-launch-failed Download the .NET runtime: %s&apphost_version=%sThe application to execute does not exist: '%s'. app_local.netDOTNET_RUNTIME_IDmissing_runtime=truevector::_M_realloc_insertFailed to load %s, error: %sThe app-relative .NET path is longer than the max allowed length (%d)%s - search options: [basic_string::substrDirectory core servicing at [%s] was not ACL-ed properlyFailed to map file. mmap(%s) failed with error %dhostfxr_set_error_writerarm64true.., app-relative path: Tracing enabled @ %sDOTNET_ROOTtouch_fileVERSION_ID=The app-relative .NET path is not embedded.Unable to open COREHOST_TRACEFILE=%s for writingx869.0.5libcoreclr.soThe install_location file ['%s'] does not exist - skipping.App path: [%s]Using global install location [%s] as runtime location./\%c %Zrealpath(%s) failed: %salpine.apphostDid not find [%s] directory [%s]Directory core breadcrumbs [%s] was not specified or foundcorebreadcrumbsinstall_location_Failed to map file. fstat(%s) failed with error %dlibhostfxr.soInvoking fx resolver [%s] v1Failed to map file. open(%s) failed with error %d - Installing .NET prerequisites might help resolve this problem.The required library %s does not support relative app dll paths.Using app-relative location [%s] as runtime location.Found previously loaded library %s [%s].&arch=basic_string::eraseThe required library %s does not contain the expected entry point.Using app-local location [%s] as runtime location.basic_string::appendarmCORE_SERVICINGr.NET root search location options: %dHost path: [%s]Dotnet path: [%s]HOMEBundle Header Offset: [%ld]hostDOTNET_MULTILEVEL_LOOKUPgetcwd() failed: %sFallback directory core breadcrumbs at [%s] was not foundError: the required library %s could not be found in [%s]DOTNET_MULTILEVEL_LOOKUP is set to %s&rid=&os=ppc64leFallback directory core servicing at [%s] was not foundFailed to determine default extraction location. Environment variable '$HOME' is not defined and getpwuid() returned NULL.rocky. globalDetected latest fxr version=[%s]...basic_string::_M_construct null not validrhel.Embedded app-relative .NET path: '%s'stoulCORE_BREADCRUMBSFailed to create default extraction directory [%s]. %shostfxr_main_bundle_startupinfohostfxr_mainDetected Single-File app bundleError: the default install location cannot be obtained..deps.jsonRealpath CORE_BREADCRUMBS [%s]Breadcrumb store [%s] is not ACL-ed with rw-Default extraction directory [%s] either doesn't exist or is not accessible for read/write./etc/dotnetinstall_location_DOTNET_TEST_DEFAULT_INSTALL_PATHID=hostfxr_main_startupinfoInvoking fx resolver [%s] hostfxr_main_bundle_startupinfo--- Invoked %s [version: %s] main = {Multilevel lookup is %shttps://aka.ms/dotnet-core-applaunch?Probed for and did not find library symbol %s, error: %sopt/etc/os-release0123456789."'You must install or update .NET to run this application. app-relative: [%s] app_relativecannot create std::vector larger than max_size()framework=Realpath CORE_SERVICING [%s]Directory core servicing at [%s] was not specified or foundThe install_location file ['%s'] failed to open: %s.Looking for install_location file in '%s'. environment variable: [%s]armv6open(%s) failed in %sFailed to determine default extraction location. Environment variable '$HOME' is not defined and directory reported by getpwuid() [%s] either doesn't exist or is not accessible for read/write.Did not find any install location in '%s'.19ff3e9c3602ae8e841925bb461a0adb064a1f1903667a5e0d87e8f608f425acc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f200010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899;¤“ˆ!¼¸!Ðø!äX"Œ"P˜"dÔ"Œä" ô"´#È#¸<( ¬(ܼ(øø(PØ*�¼+Ðx2 À2$Ü3L4t,4d ;¸¤? ”AˆðA´@ùaÿÿÐ!”‘:ÿƒÑý{ ©üo ©úg©ø_©öW©ôO©ý‘áùÈðúª¡Dù\�Róªûªôªöª@ùõª°r¨øèC‘A‘èÑù_�8 A‘h©Hð�s9�8›Žøùߎ8¶Žø(ù 9ù©ÿƒ9éùé©ÿ9w6¨ÃÑôù@ðˆ3‘A‘´ø„)”øªô ùàªô@ùCñ¸øT ÃÑ¡CÑâª})”¨[ø ø¨øx´ñ¡THð‰s99Að!ˆ3‘âªs)”¨[ø©]ø¨ƒø?i(8¨ƒ]øùñãT`ÿÿ°,!‘¡?€Rª”ø*Pùù¨´ñãT ÕCÑö ÃÑ᪀R`)”5cÿÿÐc¤‘ ÃÑ€R€RY)” 4¹]ø¨CÑ¿8A‘સ;©E)”䪠CÑáªâªãªO)”¡[ø`ÿÿ°D?‘9”©[øù@ù?ëÀTè+@ùë`T ƒÛ<ê3@ùé+ùàƒ…<(´¨;©ªø9 [øë!T`ÿÿ°x&‘o”ù@ùø*¢ƒ[ø¢ ´à+@ù_ñ T¨\89W ƒÛ<é+ùàƒ…<¸;©èª9 [øë@T$)”8€R ]øè @ùë@T)”˜6`ÿÿ°Ì5‘á*”è/@ùè´á@ùàÑ)”á+@ùàÑi ”à@ùâÑá*ãªäªˆ”à6àªáª‹”à6ü*œœO¢@ù`ÿÿ°P‘aÿÿ°!à-‘0”`ÿÿ°T/‘-”`ÿÿ°è‘aÿÿ°!‘(”à@ùè@ù|n¹ë@Tì(”à+@ùë@Tè(”Èð¡Dù©_ø@ù ëÁTôOQ©öWP©ø_O©úgN©üoM©ý{L©ÿƒ‘À_ÖáªÌ(”¨ƒ[øé+@ùè/ù?i(8¨[ø¿ƒø9 [øëAõÿTªÿÿÔ(”ùù÷ª ]øè @ùë@T#¨[ø÷ªë!T ]øè @ùëaTà@ùè@ùë�Tà+@ùè@ùë¡T @ùëÁTè@ù@ùëáT`@ùëT઴(”દ(” ]øè @ùëàüÿT¡(”à@ùè@ùëÀüÿT÷ªôùùùà@ùè@ùëÀûÿT”(”à+@ùè@ùë ûÿT�(” @ùë€ûÿT‹(”è@ù@ùë`ûÿT†(”`@ùë@ûÿT‚(”ઌ(”ý{¾©ó ùý‘óª@ù@´”`&@ùhb‘ë@Tt(”`@ùhâ‘ë@To(”`@ùhb‘ë€Tó @ùý{¨h(ó @ùý{¨À_Ö”ý{¿©ý‘q(”t(”ý{¾©ó ùý‘ Õ³¡0àª=(”Àð ÕÂiœDùáªó @ùý{¨9(ÿƒÑý{©öW©ôO©ýÑÈð Õõy¡Dùóª@ù¨ƒø@ùàª.(”äªàªáªâªãª8(”è#‘àªA‘öù#(”ôªàªŸBñôùTà#‘á‘âª(”è@ùàùèùÔ´ŸñáTHС{99è'@©è ù?i(8è @ùñT Õöp€€RQ”ó*à@ùë@T(”ÈСDù©ƒ_ø@ù ëáTà*ôOE©öWD©ý{C©ÿƒ‘À_Ö ÕÁqâªú'”è'@©è ù?i(8è @ùñBüÿTñãT ÕC¥öPà#‘᪀Rð'”5cÿÿ°c¬‘à#‘€R€Ré'”@4a@ù`ÿÿ�\‘Ö”3€Rà@ùëAúÿTÒÿÿa@ù`ÿÿ�H‘”Èÿÿé'”óªà@ùë@TÛ'”àªå'”ÿƒÑý{©öW©ôO©ýÑÈÐôª¡Dù@ù¨ƒø3@ùÈ ””@ùõªè#‘àªáªö#‘p”æ@ùdÿÿ�„ü'‘`ÿÿ�‘aÿÿ°!H ‘âªãªåªçªð”à@ùÈB‘ë@Tµ'”ÈСDù©ƒ_ø@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_Ö±'”è@ùÉB‘óª ë`Tઢ'”બ'”ÿÑý{©üo©úg©ø_©öW©ôO©ýƒ‘ÈЩÃÑóª¡Dùô*:A‘@ù¨øº=©¿8 ÃÑÂ$”à6 ÃÑá*3%”`6¨CÑ¿8A‘¨ÃÑ¿8A‘èC‘»;©A‘¼9©÷ ©ÿƒ9àC‘)ÿÿ—˜�R°r€6àC‘á€R⪊'”õªèƒ‘ ÃÑöƒ‘ø ”á@ùÙB‘¶ÃÑ?ë T¨[øëÀTàƒÂ<©\ø¡øÀ‚‚<ˆ´èùéù2¡]ø`ÿÿ� ‘�”ˆ�R°r2 ]øë@TR'”ÈСDù©_ø@ù ëa"Tà*ôOW©öWV©ø_U©úgT©üoS©ý{R©ÿ‘À_Ö(€R*bâ@ù´ [ø_ñAT(@99àƒÂ<¡øÀ‚‚<ùùèª%'”è@ù©[ø¨ƒø?i(8è@ùÿù9à@ùë@T&'”áK@ù CÑ{”7ýÿ— ´`ÿÿ°È‘” CÑá*P”@6胑 CÑùƒ‘  ”á@ù9C‘?ë€T¨Yøë TàƒÂ<©Zø¡øÀ‚€<è´èùéùâ@ù‚´ Yø_ñT(@99àƒÂ<¡øÀ‚€<ùùèª ¡[ø`ÿÿ�°‘*”[å&”è@ù©Yø¨ƒø?i(8è@ùÿù9à@ùë@Tæ&”àƒ‘¡ÃÑýÿ—ö�@¹4àƒ‘`þÿ—àK@ùë@TÛ&” Yøë@T×&” [øëàïÿTÓ&”}ÿÿæüÿ—à´àƒ‘õüÿ—õª€ ´é£D©¶]øñ¨+{©é‰šéù_ñøˆšØüÿ—á7@ùùª`ÿÿ°d‘¨”¡]ø`ÿÿ�d6‘¤”á'@ù`ÿÿ�¤6‘ ”¡[ø`ÿÿ�<)‘œ”`ÿÿ�7‘᪘”àƒ‘×üÿ—áªàC‘¹”ã@ùà*áªâªäªåª ?Öèc@9ö*è÷ÿ4è @ù¨÷ÿ´àª?Öºÿÿàƒ‘Çüÿ—öª´é£D©«]øá7@ùñ¨+{©é‰šé/©_ñõˆš`ÿÿ�‘t”¡]ø`ÿÿ�d6‘p”á'@ù`ÿÿ�¤6‘l”¡[ø`ÿÿ�<)‘h”àƒ‘§üÿ—ùªàC‘᪈”ã @©à*áªäªÀ?Öö*\”9µKßkÁT ´èƒ‘¡‘¡ÃÑ�þÿ—èc@9Èðÿ4è @ùˆðÿ´àª?Ö�ÿÿá7@ù`ÿÿ�0‘“”胑¡‘¡ÃÑ�þÿ—Kwÿÿá7@ù¿±àT`ÿÿ�`0‘‡”–�R°rnÿÿ`ÿÿ�.‘5”ÿ”àƒ‘{üÿ—èªÀ´à*áª?Öö*aÿÿá7@ù`ÿÿ�L3‘s”–�R°rZÿÿA&”Õýÿ—Óýÿ—èc@9óªè4è @ù¨´àª?ÖÈýÿ— èc@9óªh4è @ù(´àª?Ö¼ýÿ—óªàƒ‘šýÿ—óªàK@ùë�T Yøë¡T [øëÁT ]øë�Tàª&”&” Yøë þÿT&” [øë€þÿTü%” ]øë`þÿTóª ]øëÀýÿTó%”àªý%”ý{¾©ôO©ý‘ôªóª ”tù"9Ó”À´h@ùˆ´?Ö(€Rh"9ôOA©ý{¨À_ÖÿÃÑý{©÷#ùöW©ôO©ýÑÈÐóªô*¡Dù@ù¨ƒøþ”œ” 6è#‘õ#‘f”â@ù`ÿÿ°L‘aÿÿ�!ü*‘±”à@ù¨B‘ë@TÂ%”ŸqKTö*uÿÿ�µF"‘÷ªá†@øàª£”Öñ�ÿÿT`ÿÿ�$‘ž”à*áªþÿ—ó*d”ÈСDù©ƒ_ø@ù ëTà*ôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö©%”è@ù©B‘óª ë`Tચ%”ત%”ý{¾©ó ùý‘ Õ³HPàªu%”ÀÐ ÕœDùáªó @ùý{¨q%äªãªá*âªÿCÑý{©üo©úg©ø_©öW©ôO©ýÑÈÐ)rª€R¡Dùõª÷ªóªöªT‰@ù¨øÔ6È@ùˆ´ ÕÁöàªâªm”À6Á@ù`ÿÿ�X4‘N”¡@ù`ÿÿ�Ô‘J”àªáªa%”3€RFü*³´”6h@ùñüŸ¨ÃÑ¿8A‘¨CÑ¿8A‘¹=©º;©|4àªá*ô"”à6a@ù`ÿÿ�d1‘.”àªáªE%” Õ!ýõàªâª@”`6¡@ù`ÿÿ�Ô‘!”3€R [øë!#Tt6 CÑ᪸ ”à6¡[øâ@ù`ÿÿ�È‘”Ô6 ÃÑ─7 ÃÑ¢”@#6¡]ø`ÿÿ�x)‘”¡ÃÑàª%”ø^@©¨ÃÑA‘»øxµ‹¨"µÿBñàª÷#ùT ÃÑá‘âªú$”è#@ù ø¨ø7´ÿñ�T@99áªâªò$”è#@ù©Yø¨ƒø?i(8aÿÿ�!p7‘ ÃÑN”aÿÿ�!Ì ‘ ÃÑJ” ÃÑ"”`6¨Yøéƒ‘4A‘ëô3ù@T©Zøè3ùé;ù¸¯” 6`ÿÿ�ü‘ Õ!ñõâ*­”´6Á@ù`ÿÿ�Б¨”¼4a@ù`ÿÿ°, ‘£”´6¡[ø`ÿÿ°Œ‘ž”ô6¨ƒ]øèµ Ãч”¨ƒ]øhµ ÃÑF”¹ ”è‘E”¡]øâ#@ù`ÿÿ�0 ‘�”à#@ùè‘A‘ë@T·$”è‘ÿC9A‘ö©à‘ç!”iÿÿ�)©‘ˆ €R)@ùꃑèsyWA‘(€RŸ6q÷ùè§©@ Taÿÿ�!P"‘àƒ‘‚€R¼$”µ€Òøòt6è@ù¨‹-Ñ)ñÃTaÿÿ�!t‘àƒ‘B€R¯$”|4è@ù¨‹!Ñ5ñcTaÿÿ°!€ ‘àƒ‘¢€R¤$”T6è@ù‹Uñ#Taÿÿ�!H ‘àƒ‘¢€Rš$”t6è@ùéB² ‹ñÃTaÿÿ�!Ô=‘àƒ‘â€R�$”è@ù ü’?Hë Taÿÿ�!Ð‘àƒ‘B€R†$”Ü4è@ù‹Uñc Taÿÿ�!@%‘àƒ‘¢€R|$”s@ùàªI$”è@ù ø’(Ëëà Tâªàƒ‘áªp$”ó#@ùM ”õ@ùôªè‘àªáªö”æ@ùdÿÿ�„ü'‘`ÿÿ�‘aÿÿ�!,‘âªãªåªçªv”à@ùè‘A‘ë@T:$”à@ùë@T6$”à#@ùë@T2$”ó*`À=€€=¨ƒYø»9©¿8è7ùàƒ‘áª�”ó*à3@ùë@T"$” Yøë@T$” [øë@T$” ]øë@T$”Ȱ¡Dù©_ø@ù ëAT`ôOT©öWS©ø_R©úgQ©üoP©ý{O©ÿC‘À_Ö`ÿÿ�H‘:”ó* [øëÁüÿTæÿÿ$”@ÿÿð„>‘$”@ÿÿð$5‘$”è#@ùé‘)A‘ ë!5è@ùé‘óª)A‘ ëTà@ùë!Tà#@ùëAT)àªã#”à@ùëÿÿTóªà@ùë`þÿTóªà#@ùëATè3@ùëóª TàªÏ#” óªà@ùë üÿTÈ#”à#@ùë TÄ#”óª [øë Tóª Yøë!T [øëAT ]øëaTા#”±#” [øëÿÿT­#” ]øëàþÿT©#”ળ#”ÿƒÑý{©üo©úg©ø_©öW©ôO©ý‘Ȱóª¡Dù@ù¨ø@ùáù@ÿÿð‘áª~”ÿÿ©ÿùác‘àªW"” ƒÑóù#”õïA©¿ëT¨ƒÑéÑXÿÿðË‘A‘Á‘è‘*A‘<Á‘4‘3�‘A‘ê ùµ‚‘¿ëàT¡@ùàª_”è‘દ”àÑ”à‘áÑâ*¿” 6 ƒÑáÑà”r¨ƒÑéÑ(ˆšù* @¹@ù¨øè @ù©ƒ¸–šàª_#”?r�—šàª[#”à3@ùë@TS#”à#@ùë@TO#”àC@ùë@úÿTK#”ÐÿÿàÑß” ƒÑáÑóÑ”õ*à3@ùh‘ó@ùë@T=#”à#@ùèÑ�‘ë@T7#”õ6a@ù@ÿÿð‘h”õ*$èÑ ƒÑÀ”á@ùં”a@ù@ÿÿðô=‘”â@ù Õ¡¹õàª%”õ*à6è@ù@ù@ÿÿðÔ‘”b@ù@ÿÿð 9‘ ÕÁ·õI”à@ùèÑA‘ë@T #” ]ø³ƒÑh‘ë@T#” [øh‚‘ë@T#”óÓA©ëÁTs´àªü"”Ȱ¡Dù©_ø@ù ë¡T ôOU©öWT©ø_S©úgR©üoQ©ý{P©ÿƒ‘À_ÖsB‘ëÀT`Aøë`ÿÿTæ"”ùÿÿó@ùÓüÿµçÿÿé"”%$óªàÑʔ ƒÑÈ”àc‘Ù”àªâ"”óªàc‘Ó”àªÜ"”óªàc‘Δàª×"”è@ùéÑóª)A‘ ë@TàªÃ"” ƒѯ”àc‘À”àªÉ"”óª ƒѧ”àc‘¸”àªÁ"”óªóªàÑž”àC@ùë@T¬"” ƒј”àc‘©”લ"”ÿÑý{©öW©ôO©ýC‘Ȱóª¡Dù@ù¨ƒøè£‘T@©A‘öùtµˆ‹H µ¿BñàªõùT࣑á‘âª}"”è@ùàùèù5´¿ñ�Tˆ@99áªâªu"”è@ùé@ùèù?i(8Aÿÿð!p7‘࣑Ñ”Aÿÿð!Ì ‘࣑Í”࣑ ”`6è@ùé#‘4A‘ëôù`Té@ùèùéù á@ù@ÿÿðÜ ‘š”ó*ÀÀ=€€=è@ùöÿ©ÿã9è ùà#‘᪳þÿ—ó*à@ùë@TT"”à@ùë@TP"”Ȱ¡Dù©ƒ_ø@ù ëAT`ôOG©öWF©ý{E©ÿ‘À_Ö@ÿÿð„>‘a"”H"”è@ùóªëÁTà@ùë¡TàªC"”àª5"”à@ùë@ÿÿTóªà@ùë þÿT,"”àª6"”ý{¾©ôO©ý‘óªâª Õ ™õaÿÿ�!”‘ãª�”ô*à6b@ù@ÿÿð<2‘ ÕA˜õ甀ôOA©ý{¨À_Öý{¾©ó ùý‘óª@ùh‘ë@T "”` @ùh‚‘ë€Tó @ùý{¨"ó @ùý{¨À_Öý{½©õ ùôO©ý‘T@©ë€TôªsB‘ëÀT`Aøë`ÿÿTð!”ùÿÿ“@ùÓ´àªôOB©õ @ùý{èè!ôOB©õ @ùý{èÀ_Öý{¾©ó ùý‘ Õ3ÒpàªÁ!”À° ÕBšœDùáªó @ùý{¨½!ÿÃÑý{©ôO©ýC‘Ȱ飑¡Dù4A‘@ù¨ƒøôÿ©ÿã9 Õ YõPᣑá”`6à@ùq”qëT&” 6è#‘ó#‘”á@ù@ÿÿð˜%‘¡”à@ùhB‘ë@T²!”à@ùë@T®!”Ȱ¡Dù©ƒ_ø@ù ë¡TôOF©ý{E©ÿÑÀ_Ö«!”óªà@ùë@Tœ!”દ!”ÿƒÑý{©ù3ùø_©öW©ôO ©ýC‘ȰWÐ飑¡Dù5A‘@ù¨ƒøö®C¹õÿ©ÿã9V 5SÐs¢‘ €Ráª;!”À4ø*TД¢‘ €Ráª3!” 4'rAÿÿTž!”øÿÿȰYÐ¥Dù@ù(Ûù@ÿÿð‘ᣑˆ”À6à@ùAÿÿð!Ü‘“!”`´áªB€Rãªôª‘!”ø*4Ûùø*8€Rè#‘ÿc9A‘ôÿ©@ÿÿðä‘á#‘o”`6à@ùÿ”è@ùஹë`TàªJ!”þŸá@ù¸6@ÿÿð('‘z”á@ù?ë`Tàª?!”Ȱßq¡Dù©ƒ_øàŸ@ù ëÁTôOI©ù3@ùöWH©ø_G©ý{E©ÿƒ‘À_Ö€€Rè@ùஹëüÿTáÿÿ1!”ôª ôªè@ùëôª`Tàª!”þŸà@ùë@T!”àª$!”´øÿ—ÿCÑý{©ü“ùöW©ôO©ýC‘ዩȰIÐã“ ©å› ©ç_ùà­â­ä­¡Dùæ­@ù¨ƒø(­C¹ q+T耒é‘ê#‘ðßò)‘TД¢‘óª©#<©¨‘Iá‘ €R᪨';©ª ”À4ö*Uе¢‘ €R᪢ ”Ö 4ß&rAÿÿT !”øÿÿ ‡}­¢ÃÑ•@ùáªàª ‡>­!”@€Ráª!”ŸþŸȰ¡Dù©ƒ_ø@ù ëáTôOT©ü“@ùöWS©ý{Q©ÿC‘À_ÖÔ ”høÿ—ÿƒÑý{©ü³ùø_©öW©ôO©ýC‘ዩȰé#‘ã“ ©ê‘óªå› ©£ÃÑàªç_ùáªâªà­â­ä­¡Dùæ­@ù¨ƒø(á‘©C‘©#;©è€’I‘ðßò©#<© ‡}­ ‡;­ ‡<­ ‡>­á ”1D T}@“4àªß ”á*âªôªß ”ôª ‡{­£ÃÑàªáªâª ‡>­Î ”Uе¢‘ €RáªE ”À4÷*VÐÖ¢‘ €Ráª= ”÷ 4ÿ&rAÿÿT¨ ”øÿÿ Ò€ò Õ ÕXÐ;Õöªk`ø(´àª?ÖHЭC¹h4ȰIÐ¥Dù7ÙAù@ùÿëaTkvøH´ ‡|­¢ÃÑàªáª ‡>­— ”@€R᪘ ”¿þŸt´àª\ ”Ȱ¡Dù©ƒ_ø@ù ëTôOY©ü³@ùöWX©ø_W©ý{U©ÿƒ‘À_ÖȰથDù@ù᪑ ”@€Ráª~ ”HЭC¹Húÿ5ãÿÿ`ÿÿ�¸ ‘c ”F ”óª¿þŸt´àª9 ”àªC ”Ó÷ÿ—HЭC¹qàŸÀ_ÖÿCÑý{©ü“ùöW©ôO©ýC‘á‹©È�I°ã“ ©å› ©ç_ùà­â­ä­¡Dùæ­@ù¨ƒø(­C¹q+T耒é‘ê#‘ðßò)‘T°”¢‘óª©#<©¨‘Iá‘ €R᪨';©Ä”À4ö*U°µ¢‘ €R᪼”Ö 4ß&rAÿÿT' ”øÿÿ ‡}­¢ÃÑ•@ùáªàª ‡>­+ ”@€Ráª, ”ŸþŸÈ�¡Dù©ƒ_ø@ù ëáTôOT©ü“@ùöWS©ý{Q©ÿC‘À_Ö÷ÿ—ÿCÑý{©ü“ùöW©ôO©ýC‘á‹©È�é‘ã“ ©)‘ê#‘å› ©T°”¢‘ç_ùóª €Rà­áªâ­ä­¡Dùæ­@ù¨ƒøè€’ðßò©#<©Há‘©‘©#;©|”À4ö*U°µ¢‘ €Ráªt”Ö 4ß&rAÿÿTß”øÿÿÈ�¢ÃÑ᪩Dù ‡}­@ù ‡>­àªá”@€Ráªâ”ŸþŸÈ�¡Dù©ƒ_ø@ù ëáTôOT©ü“@ùöWS©ý{Q©ÿC‘À_Ö¤”8÷ÿ—@ÿÿÐH‘´ÿÿÿCÑý{©ü“ùöW©ôO©ýC‘á‹©È�I°ã“ ©å› ©ç_ùà­â­ä­¡Dùæ­@ù¨ƒø(­C¹ q+T耒é‘ê#‘ðßò)‘T°”¢‘óª©#<©¨‘Iá‘ €R᪨';©+”À4ö*U°µ¢‘ €Ráª#”Ö 4ß&rAÿÿTŽ”øÿÿ ‡}­¢ÃÑ•@ùáªàª ‡>­’”@€R᪓”ŸþŸÈ�¡Dù©ƒ_ø@ù ëáTôOT©ü“@ùöWS©ý{Q©ÿC‘À_ÖU”éöÿ—ý{½©õ ùôO©ý‘H°ÙAùè´S°s¢‘ €Ráªø”À4õ*T°”¢‘ €Ráªð”µ 4¿&rAÿÿT[”øÿÿ`@ù|”þŸÈ�¥Dù@ùw”È�©Dù@ùôOB©õ @ùý{èpÀöÿ—ý{¿©ý‘誠Ҁò Õ ÕJÐ;Õéª@i`øHi)øý{Á¨À_Öý{¿©ý‘ Ò€ò Õ ÕHÐ;Õi`øý{Á¨À_Öý{¾©ó ùý‘ Õswàªé”À� ÕB?œDùáªó @ùý{¨åÿÃÑý{©÷#ùöW©ôO©ýÑÈ�óªôª¡Dù@ù¨ƒøè#‘X@©A‘÷ùuµ¨‹ÈµßBñàªöùTà#‘á‘âªÒ”è@ùàùèù6´ßñ�T¨@99áªâªÊ”è'@©è ù?i(8à#‘áª(”à#‘û”ñô*èŸ ˆ6á#‘àªÊ”à@ùë@T”È�¡Dù©ƒ_ø@ù ëaT€ôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö@ÿÿЄ>‘Ò”¹”è@ùóªë`Tફ”વ”ÿÃÑý{©÷#ùöW©ôO©ýÑÈ�é#‘¡Dù7A‘@ù¨ƒø÷ù ´ôªàªóªƒ”õªàª¿>ñõù Tà#‘á‘âª~”è@ùàùèù5´¿ñ�Th@99áªâªv”è'@©è ù?i(8à#‘è”ö*à@ùë@Tx”•@ùV6àª`”äªàªáªâªãªj”&u´ˆ@ù ‹)ñ_8?½qÀT骶‘* Aø ëé€R)Ššß ë Tàªáªâªãª$€R©”ˆ@ùé€R i58ˆ@ù–ùi68•@ùàª<”ø’Ëë£Tâªàªáªd”È�¡Dù©ƒ_ø@ù ëATôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö@ÿÿЄ>‘Y”@”@ÿÿÐ$5‘Y”è@ùóªë`Tàª/”àª9”ÿƒÑý{©õ#ùôO©ýÑÈ�¡Dù@ù¨ƒøè#‘P@©A‘õùsµh‹hµŸBñàªôùTà#‘á‘⪔è@ùàùèù4´Ÿñ�Th@99áªâªý”è'@©è ù?i(8 Õ! õ0à#‘Zÿÿ—á@ù@ÿÿÐ(‘Îýÿ—à#‘)”ó*à@ùë@T÷”È�¡Dù©ƒ_ø@ù ëAT`ôOE©õ#@ùý{C©ÿƒ‘À_Ö@ÿÿЄ>‘”ï”è@ùóªë`Tàªá”àªë”@ùIÑ?ëâTý{¿©ý‘ Õèr@ù Õ)(ˆš?ÖqàŸý{Á¨À_Öà*À_ÖB|@“B|@“@ùëbTà*À_Öý{¿©ý‘ @ù Õ© ÕKrH‹i‰šË ?ÖqàŸý{Á¨À_Ö ÿÑý{©õùôO©ýC‘óªÈ�骡Dù`B‘@ùèù4U@©`ùtµˆ‹hµ¿BñõùTá‘àªâªˆ”è@ù`ùh ù5´¿ñ�Tˆ@99áªâª€”è@ùi@ùhù?i(8È�¡Dùé@ù@ù ë!TôOC©õ@ùý{A©ÿ‘À_Ö@ÿÿЄ>‘š”�”ÿÑý{©õùôO©ýC‘É�óª)¡Dù)@ùéù @ù ´AÿÿÐ!X*‘€’C€RõªË”ôªàªÁ€R€’Ê”Ÿ±èªÀTŸë‰T´V@©`B‘`ùµˆ‹È´0èª 8h~© ´&@©`B‘`ù?ë51ˆštµˆ‹ˆµ¿BñõùTá‘àªâª3”è@ù`ùh ù5´¿ñ�Tˆ@99áªâª+”è@ùi@ùhù?i(8È�¡Dùé@ù@ù ëÁTôOC©õ@ùý{A©ÿ‘À_Ö/”@ÿÿЄ>‘D”ÿÑý{©öW©ôO©ýC‘É�óª)¡Dù)@ùéù @ù©´AÿÿÐ!X*‘€’C€Rôª€’}”õªàªÁ€R€’t”ƒ@ù¢‘hëƒT ëŠ@ù Vú`B‘)ŸÚT‹`ù ë1‰štµˆ‹¨µ¿BñõùTá‘àªâªå”è@ù`ùh ùµ´¿ñTˆ@99èª 8h~©áªâªÙ”è@ùi@ùhù?i(8È�¡Dùé@ù@ù ëÁTôOC©öWB©ý{A©ÿ‘À_ÖÝ”@ÿÿÐd‘AÿÿÐ!¤"‘@”@ÿÿЄ>‘í”ÿÑý{©õùôO©ýC‘É�óª)¡Dù)@ùéù @ùi´á€R€’õª%”± T£@ù‘ë©T¨@ù`B‘`ù‹tµ‹hµuË¿BñõùTá‘àªâª˜”è@ù`ùh ùõ´¿ñATˆ@99èª 8h~©´V@©`B‘`ùtµˆ‹µ¿BñõùÃýÿTæÿÿáªâª‚”è@ùi@ùhù?i(8È�¡Dùé@ù@ù ëÁTôOC©õ@ùý{A©ÿ‘À_Ö†”@ÿÿЄ>‘›”@ÿÿÐd‘AÿÿÐ!¤"‘æ”ÿCÑý{©÷3ùöW©ôO©ýC‘óªÈ�¡Dù@ù¨ƒøè£‘T@©A‘öùtµˆ‹èµ¿BñàªõùT࣑á#‘âªL”è@ùàùèù5´¿ñ�Tˆ@99áªâªD”è@ùé@ùèù?i(8è@ùh´é@ù)‹)ñ_8?½qÁTÑ࣑"€Rµ”õÿÿ࣑á€R€’¤”±àTô@ù ø7ˆJ`8½qÁTQqà*LÿÿT€|@“é@ùë#‘wA‘ ‘÷ù? ë55ˆštµˆ‹ˆ µ¿BñàªõùTà#‘á‘⪠”è@ùàùèù´¿ñaTˆ@99ô×B©wB‘wùtµˆ‹ µ¿BñàªõùTá#‘àªâªö”è@ù`ùh ùu´¿ñÁTˆ@99&áªâªî”è'@©è ù?i(8á @ùà#‘âª#€Rä€Ri”iB‘èªiù Aø_ë Tjù @ùi ù@À= €= @ùiù|©è@ù@9ë TàªÜ”áªâªÌ”è@ùi@ùhù?i(8a@ùàªâª#€Rä€RF”à@ùë@TÊ”¨ð¡Dù©ƒ_ø@ù ëATôOH©÷3@ùöWG©ý{E©ÿC‘À_Ö@ÿÿ°„>‘۔”@ÿÿ°„>‘×”@ÿÿ°„>‘Ô”h@ùè@ùëóªÁTà@ùë¡T઴”દ”à@ùë@ÿÿTóªà@ùë þÿT�”ધ”7óÿ—ý{¿©ý‘ @©)‹)ñ_8?½q�TÑ"€R”ý{Á¨À_Ö*óÿ—ý{½©õ ùôO©ý‘ó*âªô*õªœ”±@T⪨@ùàªá*i"8””⪱!ÿÿTôOB©õ @ùý{èÀ_ÖÿCÑý{©ø_©öW©ôO©ýC‘óª¨ðô*¡Dùâªõ*÷ª@ùèù}”ö^@©øª`B‘`ùÈ‹ßñ @ú蟱@Tˆ 6ÿBñ÷ùTá‘àªâªC”è@ù`ùh ùW´ÿñ¡TÈ@99h6ÿBñ÷ùTá‘àªâª2”è@ù`ùh ù·´ÿñTÈ@99 áªâª*”è@ùi@ùhù?i(8h@ùàªá*âªi88D”øª±ÿÿT¨ð¡Dùé@ù@ù ë�TôOD©öWC©ø_B©ý{A©ÿC‘À_Öáªâª”è@ùi@ùhù?i(8¨ð¡Dùé@ù@ù ëÀýÿT”@ÿÿ°„>‘,” €RÀ_Ö Õ‘ Ù`øÀ_Ö@ÿÿ°%‘À_ÖÿCÑý{©ôO©ýÑóª¨ðé#‘¡Dù4A‘@ù¨ƒøôÿ©ÿc9@ÿÿ°´‘á#‘”hB‘hù6é@ù?ë@Tè@ùiùh ù€Rèùá‘àªâªÒ”Iÿÿ°)¡ ‘è@ù À=`ùh ù€=i@ùhù?i(8à@ùëàTÔ”€À=€=è @ùhù¨ð¡Dù©ƒ_ø@ù ë¡TôOD©ý{C©ÿC‘À_ÖÌ”óªà@ùë@T¾”àªÈ”áª@ÿÿ°´‘ÔÿƒÑý{©õ#ùôO©ýѨðé#‘¡Dù5A‘@ù¨ƒøõÿ©ÿc9@ÿÿ°„7‘á#‘ÔSÿÿ°s"%‘À6à@ùQ”á@ùô*@ÿÿ°:‘múÿ—ŸqHÿÿ°‘ôŸsˆš4€R@ÿÿÐä‘áª|ùÿ—à@ùë@TŽ”¨ð¡Dù©ƒ_ø@ù ëáTà*ôOE©õ#@ùý{C©ÿƒ‘À_Ö‰”óªà@ùë@T{”અ”ÿÑý{©úg©ø_ ©öW ©ôO ©ýѨðóªõª¡Dù@ù¨ƒø!6ô*¨£Ñ¿ƒ8A‘¨@ù¸ÿ=©Hµ2«ÿÿ—ô*¨£Ñ¿ƒ8A‘¨@ù¸ÿ=©H´ £Ñáª\”©£}©)‹)ñ_8?½q¡TÑ £Ñ"€RÔu¢@©¿ëàT B‘ ù¶ß}©vµÈ‹µÿBñ÷ùTᣑàªâª/” ùè@ù¨ ù÷´ÿñATÈ@99 ¢£ÑàªáªJ”´7oáªâª!”è@ù©@ù¨ù?i(8h@ù�‘hù” 6ÿÿ©ÿùà#‘¢”ô@ù 6ú @ùŸë  T裑A‘”‚‘ŸëÀ Tùù•Z@©uµ¨‹ˆ µßBñàªöùT࣑ბâªù”è@ùàùèù6´ßñ�T¨@99áªâªñ”è'B©èù?i(8࣑¡£Ñí” 7u¢@©¿ëàT B‘ ùößB©vµÈ‹h µÿBñ÷ùTბàªâªÖ” ùè@ù¨ ù×´ÿñ!TÈ@99 ⣑àªáªñ” áªâªÉ”è@ù©@ù¨ù?i(8h@ù�‘hùà@ùë`öÿTÊ”±ÿÿô@ùó @ùŸëTt´àªÂ” ƒ]øë@T¾”¨ð¡Dù©ƒ_ø@ù ë!TôOK©öWJ©ø_I©úgH©ý{G©ÿ‘À_Ö”B‘ŸëÀT€Aøë`ÿÿTª”ùÿÿô@ù”üÿµåÿÿ@ÿÿ°„>‘Ô@ÿÿ°„>‘À”§”@ÿÿ°„>‘¼”8ñÿ—óª óªóªà@ùë@T�”à#‘Ž÷ÿ— ƒ]øë@T‰”ઓ”ÿƒÑý{©õ#ùôO©ýѨðé#‘óª¡Dùôª5A‘@ù¨ƒø(@ù?ù9õÿ©ÿc9á#‘�”@6à#‘á*¥” 6á#‘àªo”3€Rà@ùëaT â@ù@ÿÿ°+‘áª1ùÿ—ó*à@ùë@T\”¨ð¡Dù©ƒ_ø@ù ëáTà*ôOE©õ#@ùý{C©ÿƒ‘À_ÖW”è@ùóªë`TàªI”àªS”âªAÿÿ°!0‘C€R¾ÿÑý{©öW©ôO©ýC‘¨ð¡Dù@ùèù@ùH´ôªAÿÿ°!0‘âªC€R󪬔± Tà*¨ð¡Dùé@ù@ù ëATôOC©öWB©ý{A©ÿ‘À_Öu@ù ”óª@¹¹á‘àªB€R�”è@ùë@Th@¹È4‰q¡T@ÿÿ°Ü?‘—”v¹€¹ €R¨ð¡Dùé@ù@ù ëüÿT ”@ÿÿ°Ü?‘Ž”h@¹H5v¹ ”ÿCÑý{©ôO©ýÑ©ðóªè#‘)¡Dùô#‘)@ù©ƒø Õ)N  Ù`ø3”Cÿÿ°ch‘à#‘áªâª„€Rä”iB‘èªiù Aø_ë Tjù @ùi ù@À= €= @ùiùé#‘|©)A‘è@ù@9 ë`TàªÑ”¨ð¡Dù©ƒ_ø@ù ë¡TôOD©ý{C©ÿC‘À_ÖΔè@ù‰B‘óª ë`Tિ”àªÉ”ÿÑý{©öW©ôO©ýC‘óª¨ð¡DùuB‘@ùèùuùÀ´ôªš”öª<ñàù)Tá‘àªâª–”è@ùõª`ùh ù¶´ßñATˆ@9¨9è@ùi@ùhù?i(8t@ù”µàªáªâª†”è@ùi@ùhù?i(8t@ùô´s@ù`À9””ñ`8�ÿÿT¨ð¡Dùé@ù@ù ë!TôOC©öWB©ý{A©ÿ‘À_Ö@ÿÿ°„>‘˜””ÿÑý{©öW©ôO©ýC‘¨ð)LŽRôª¡Dù©Í¦rŠ€R €Róª@ù¨ƒøè£‘A‘¨€Ré;¹ê{yö£©ñ”àã9@€Rî”àç9  €Rë”àë9À€Rè”àï9€€Rå”àó9Cÿÿ°ch‘࣑áªâª„€RI”è#‘A‘èªõù Aø?ë T @ùéùêù À=骠€= @ù@9ê ù|©è@ùë€Tàª6”é@ù?ë Tˆ@ùŠB‘ ë TàÁ<Š @ù‰ù€‚€‘@”'”è@ùé#‘ôª)A‘ ëÁT`@ùë¡Tઠ”ઔ`@ùë@ÿÿTôª`@ùë þÿT ”ઔÿCÑý{©÷ùöW©ôO©ýC‘óª¨Ðôª¡DùwB‘@ùèùX@©wùuµ¨‹µßBñàªöùTá‘àªâªÝ”è@ù`ùh ù6´ßñ�T¨@99áªâªÕ”è@ùi@ùàªhù?i(8Ç”h@ù ø’(ËëƒTâªàªáªî”¨Ð¡Dùé@ù@ù ë¡TôOD©÷@ùöWC©ý{A©ÿC‘À_Ö@ÿÿ�„>‘ã”@ÿÿ�$5‘ä”Ç”h@ùóªë`Tહ”àªÃ”ÿÃÑý{©÷#ùöW©ôO©ýÑóª¨Ðôª¡DùwB‘@ù¨ƒøX@©wùuµ¨‹hµßBñàªöùTá#‘àªâª�”è@ù`ùh ù6´ßñ�T¨@99áªâª…”è@ùi@ùhù?i(8Aÿÿ�!œ‘è#‘ંÿÿ—á@ùàªÝ÷ÿ—à@ùè#‘A‘ë@T”¨Ð¡Dù©ƒ_ø@ù ëATôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö@ÿÿ�„>‘�”w”è@ùé#‘ôª)A‘ ëÁT`@ùë¡Tàªp”àªb”`@ùë@ÿÿTôª`@ùë þÿTY”àªc”ÿÃÑý{©÷#ùöW©ôO©ýѨÐóªôª¡Dùõªöª÷#‘@ù¨ƒøè#‘àþÿ—á@ù÷B‘?ë Tˆ@ù‰B‘ ë TàÁ<‰ @ù�ù€‚€‘”ø”(ÐáL9•qAT à*À_ÖÿÃÑý{©üo©úg©ø_©öW©ôO©ýC‘¨°éç{²¡Dù@ùèùp@©ˆË ëTýE“ €ÿ’úªóªöªñ …Ÿš*«_ ëJ1‹š?«(ËýE“{!Šš[´`ë{Ó”õª‹Xk@©ùB‘ùù´ õª÷‹Xk@©ùB‘ùùxµ‹h µ_Cñúù#Tá‘àªâª¡”è@ùàù(ùùªZ´_ñ�T@9(9àªáªâª—”è@ùé@ùŸëèù?i(8èª`Tèª,@ùjùl ùŠ‹«‹�‘L@ùI}©Š‹_ë?9lù`T‰‹«‹*AølA‘lù? ë¡ýÿT@À=€€=íÿÿ¨‹Ÿë`TéªM@ù‹ù�ùË ‹  ‹)�‘m@ùj}©Ë ‹ë_9�ù`TÊ ‹  ‹KAø�Á‘�ù_ ë¡ýÿT`À= €=íÿÿ ‹�‘t´àªb”¨‹uZ©h ù¨°¡Dùé@ù@ù ëáTôOF©öWE©ø_D©úgC©üoB©ý{A©ÿÑÀ_Ö ÿÿðP ‘r” ÿÿð„>‘k”R”Y”àªG”æ”óªè”àªN”Þêÿ—ý{¾©ó ùý‘ ÕÓþઔ ° ÕâÅœDùáªó @ùý{¨ÿƒÑý{©ùùø_©öW©ôO©ýC‘¨°€‘ôª¡Dùóªõª@ùèù)¹™ø–\@©vµÈ‹ µÿBñ÷ùCTá‘àªâª”è@ù€ù(ù—µ àª7´ÿñ�TÈ@99áªâªú”è@ùi @ùv‘øªhù?i(8ø·V@©wµè‹Hµ¿Bñõù#Tá‘àªâªä”è@ùùÈùöªU´¿ñ�Tè@9È9àªáªâªÚ”è@ùi@ùhù?i(8¨°¡Dùé@ù@ù ëÁTôOE©ù@ùöWD©ø_C©ý{A©ÿƒ‘À_Ö ÕàÐó0ò” Õ€Ðó0ï”Ö”ˆ@ùóªë`TàªÈ”àªÒ”ÿCÑý{©ó#ùýѨ°é#‘¡Dù3A‘@ù¨ƒøóÿ©ÿc9å#‘‡ÿÿ—à@ùë@T´”¨°¡Dù©ƒ_ø@ù ë¡Tý{C©ó#@ùÿC‘À_Ö±”è@ùëóª`Tણ”ભ”ÿÃÑý{©ôO©ýC‘¨°é£‘¡Dù4A‘@ù¨ƒøè#‘A‘ôÿ©ÿã9óÿ©ÿc9䣑å#‘]ÿÿ—à@ùë@TŠ”à@ùë@T†”¨°¡Dù©ƒ_ø@ù ë¡TôOF©ý{E©ÿÑÀ_Öƒ”è@ùëóªÁTà@ùëTàª~”àªp”à@ùë@ÿÿTl”àªv”ÿÃÑý{©ôO©ýC‘¨°é£‘¡Dù4A‘@ù¨ƒøè#‘A‘ôÿ©ÿã9óÿ©ÿc9䣑å#‘€€€#ÿÿ—à@ùë@TP”à@ùë@TL”¨°¡Dù©ƒ_ø@ù ë¡TôOF©ý{E©ÿÑÀ_ÖI”è@ùëóªÁTà@ùëTàªD”àª6”à@ùë@ÿÿT2”àª<”ý{¿©ý‘”qàŸý{Á¨À_ÖÿÃÑý{©ôO©ýC‘¨°¡Dù@ù¨ƒø@¹)@¹ kaT@¹)@¹ káT@¹)@¹ kaT @ù¨´( @ù(´ Aø+Aø3€RH@9i@9 kATL€Rˆ4¹qHil8iil8““šŒ‘ k ÿÿTÈ4¹qTé5 €R(€RňZ¨°¡Dù©ƒ_ø@ù ë¡TôOF©ý{E©ÿÑÀ_Ö( @ùñàŸóÿÿ?¹qaT€ïÿÿèªà£‘ôªA‘âªØ”à#‘�B‘âªÔ”ÿ¹à£‘ᑘøÿ—ó*ÿ¹à#‘á‘“øÿ—h 6é#@) k(€R…ˆZ à*Óÿÿh*¨6r(€RˆZ࣑á#‘j”è@ùé#‘)A‘ ë Tó*઺”à*è@ù飑)A‘ ë€÷ÿTó*઱”à*·ÿÿ¶”óªà@ù裑A‘ë Tóªà@ùè#‘A‘ëTà@ù裑A‘ë!Tથ”˜”à@ù裑A‘ë ÿÿT’”જ”ý{¿©ý‘eÿÿ—qàŸý{Á¨À_Öý{¿©ý‘^ÿÿ—|Sý{Á¨À_Öý{¿©ý‘Xÿÿ—qàןý{Á¨À_Öý{¿©ý‘Qÿÿ—qà§Ÿý{Á¨À_Öý{¿©ý‘Jÿÿ—è *}Sý{Á¨À_ÖÿÑý{©úg©ø_©öW©ôO©ýÑ󪨰Ô�R¡Dùôª7 r@ù¨ƒø@¹qUˆZ¿*qbT8€R)ë‚R˜€Rê*é6ºr_�qÉT_�qÉTK}SÅ qƒTK}©›_kkýmÓê *‚þÿTQ QQý_ÓvB‘ઢ€Rvù ç”h@ù¿’q‹CT©°ì£�R Q)­Dùl=ªr� €R«~¬›P±~S? qkýeÓnÕ õ *Î}ÓÏy’/‹ï@9I08.in8I*8J Q(þÿT)q£Ti}Ó ÕjËó+y’K ‹k@9 9Iii8ë*)q¢þÿTi2 9ê€RhV@©i @ù븑I‰š ë Tàªáªâªãª$€RS”h@ùÉ€R i58h@ùxùi88ˆ@¹qUˆZ¿*qbT9€R)ë‚R™€Rê*é6ºr_�qÉT_�qÉTK}SÅ qƒTK}©›_k9kýmÓê *‚þÿT9Q9 Q9Qý_Óè#‘A‘! øùà#‘¢€R„”è@ù¿’q‹CT©°ì£�R* Q)­Dùl=ªr� €R«~¬›P±~S? qkýeÓnÕ õ *Î}ÓÏy’/‹ï@9I08.in8I*8J Q(þÿT)q£Ti}Ó Õ ¿ó+y’K ‹k@9 9Iii8ë*)q¢þÿTi2 9á‹@©àªÇ”à@ùë@T§”hV@©ê€Ri @ù븑I‰š ë Tàªáªâªãª$€Ré”h@ùÉ€R i58h@ùxùi88ˆ @¹qUˆZ¿*qbT8€R)ë‚R˜€Rê*é6ºr_�qÉT_�qÉTK}SÅ qƒTK}©›_kkýmÓê *‚þÿTQ QQý_Óè#‘A‘ ÷ùà#‘¢€R”è@ù¿’q‹CT©°ì£�R Q)­Dùl=ªr� €R«~¬›P±~S? qkýeÓnÕ õ *Î}ÓÏy’/‹ï@9I08.in8I*8J Q(þÿT)q£Ti}Ó Õʱó+y’K ‹k@9 9Iii8ë*)q¢þÿTi2 9á‹@©àª]”à@ùë@T=”‚@ù‚´� @ùàªT”‚@ù‚´�@ùàªO”¨�¡Dù©ƒ_ø@ù ëTôOG©öWF©ø_E©úgD©ý{C©ÿ‘À_Ö-”è@ùëè@ùëôªÁT`@ùëÁTàª$”ઔ`@ùë@ÿÿTôª`@ùë€þÿT ”ઔÿÑý{©öW©ôO©ýC‘¨�öªóª¡DùàªÁ€Rõª@ùèù”±ÀTÃ@ùhëC TÉ@ù Ë`B‘ ë`ù4‹1Šštµˆ‹Èµ¿BñõùTá‘àªâªÕ”è@ù`ùh ùÕ´¿ñÁTˆ@99!Ã@ùtë£TÈ@ù`B‘`ù‹uµ‹ˆµŸBñôùTá‘àªâª»”è@ù`ùh ù”´ŸñáT¨@99áªâªáªâª°”è@ùi@ùhù?i(8¨�¡Dùé@ù@ù ëáTôOC©öWB©ý{A©ÿ‘À_Ö ÿÿÐd‘!ÿÿÐ!¤"‘⪔ Õ kó0Ä”«”ÿCÑý{ ©üo ©úg ©ø_©öW©ôO©ýѨ�õ*óª¡DùÁ€Râªôª@ù¨ƒø©”± T–"@©éc‘øª9A‘ÿ¹ù/ùë1€švµÈ‹è'µÿBñàª÷ùTàc‘áã‘âªl”è@ùà/ùè7ù7´ÿñ�TÈ@99áªâªd”è@ùé/@ùè3ù?i(8àc‘áS‘(öÿ—ö*à/@ùë@Td”¶6 ñ£Tˆ@ù@9ÁqàT‘àªÁ€Râªp”±Tƒ@ùÿ¹ë "Tˆ@ùËjËéc‘_ëöª;A‘‹Y1ššû/ùxµ‹ µ?CñàªùùTàc‘áã‘âª-”è@ùà/ùè7ù9´?ñ�T@99áªâª%”è@ùé/@ùè3ù?i(8àc‘áC‘éõÿ—ø*à/@ùë@T%”Ø6_ ñ£Tˆ@ùiw8ÁqTבàªÿ¹áªÓõÿ—± T7Ëøªèc‘àªáªâªùc‘¾”àc‘á3‘Êõÿ—ö*à/@ù(C‘ë@T”Ö 6¿ ñ£Tˆ@ùiw8Áq Tàªa€Râªöª”˱õªŸÚèã‘àªáª¡”àã‘F”@ 6èc‘¿±ÿ£9A‘öÿ©`Tèc‘àªáª€’ôc‘’”á/@ù”B‘?ë€ Tè@ùë TàÆ<é@ùáùà‚<è ´è/ùé7ùeƒ@ùëÉTˆ@ùéc‘9A‘‹ù/ùuµ‹hµxËàªCñøùTàc‘áã‘⪯”è@ùà/ùè7ù8´ñ�T¨@99áªâª§”è@ùé/@ùè3ù?i(8àc‘á3‘kõÿ—õ*à/@ùë@T§”6ˆ@ù ‹) @9i4iw8ÁqTà*¨�¡Dù©ƒ_ø@ù ëA TôOP©öWO©ø_N©úgM©üoL©ý{K©ÿC‘À_ÖâB)ã@¹àc‘ìûÿ—ác‘àªl”àc‘uìÿ— €Rèÿÿà*3â3@ù´à@ù_ñAT(@99àÆ<áùà‚<ô/ùèªh”è3@ùé@ùèù?i(8è/@ùÿ3ù9à/@ùë@Ti”àc‘¾”À6âB)ã@¹àc‘äã‘åc‘/ûÿ—ác‘àª@”àc‘Iìÿ— €Rà*è@ùë Tó*àªR”à*è@ùéã‘)A‘ ëàõÿTó*àªI”à*ªÿÿN” Õ?ó0c” ÿÿÐd‘!ÿÿÐ!¤"‘⪭”óªà@ùë€T6”óªà@ùèã‘A‘ëáTè/@ù)C‘ ëè/@ùëè/@ùëóª`Tઠ”àª*”ý{¾©ôO©ý‘(@¹)@ù¹ ù)€‘(@ù ë`T @ù €‘_ ëàT+ @ù,©@ù+@ù ùª´*|©(ù)‘_9(@ù ë¡T?" @ùB´ @ù_ñT@9(9,* @ù(©(@ùù)|©ê ª)‘_9(@ù ë€T @ù ‘_ ë T+@ù,© @ù+ @ù ùj´*|©( ù_9ôOA©ý{¨À_Ö*@ù(©( @ù ù)|©?9ôOA©ý{¨À_Öóªà ªôªáªÄ ”áªàª( @ù @ù ù?i(8*@ù? ù)‘_9(@ù ëÁúÿT"@ù´ @ù_ñ�T@9(9óªà ªôªáªª ”áªàª(@ù @ùù?i(8*@ù?ù_9ôOA©ý{¨À_ÖÿÃÑý{©ø_©öW©ôO©ýѨ�¡Dù@ù¨ƒø@ùˆ´@ùóª@9ÿ¶q`Tÿ®qáTàªÁ€R"€R5€Rª ”±  Tè#‘ôª5€RA‘c@ùhëãTi@ùŠËøù ë5‹1Ššuµ¨‹ˆµßBñàªöùTà#‘á‘âªh ”è@ùàùèù¶´ßñ!T¨@99è'@©è ù?i(8è @ùHµ1áªâª[ ”è'@©è ù?i(8è @ù(´è@ù @9)4 ‘ë *åq(TK@8ë4lŸqëTmqCÿÿTkíQi1âþÿTŸ½qLþÿTµq@þÿTÿ®q@T?ÁqT@9È4à#‘!€Rûóÿ—±ÀT5€Rà@ùëÁTõ*à@ùë@T5 ”•6•‘àªÁ€RâªG ”ôª±!ôÿTc@ùtëã Th@ùé#‘6A‘‹öùsµ‹¨ µŸBñàªôùTà#‘á‘⪠”è@ùàùèù´´Ÿñ¡Th@99è'@©è ù?i(8è @ùȵ5 €R;à*9áªâªø ”è'@©è ù?i(8è @ù(´è@ù @9)4 ‘ë *åq(TK@8ë4lŸqëTmqCÿÿTkíQi1âþÿTŸ½qLþÿTµq@þÿTÿ®q@T?ÁqT@9È4à#‘!€R˜óÿ—±ÀT €Rè@ùëÁT à*è@ùë Tó*àªÐ ”à*¨�¡Dù©ƒ_ø@ù ëTôOF©öWE©ø_D©ý{C©ÿÑÀ_Ö ÿÿÐd‘!ÿÿÐ!¤"‘âª- ” Õà ó0Ú ”Á ”è@ùëè@ùëóª`Tર ”઺ ” ýÿý{¾©ó ùý‘ ÕS,0ઊ ” � ÕbóœDùáªó @ùý{¨† ÿÃÑý{©õKùôO ©ý‘óª¨�ડDù@ù¨ƒøF ”àùà‘G ”㪠ÕÂßò0àS‘� €RE ”uB‘àS‘uùq ”ôª@ñàù#Tá#‘àªâªm ”è@ùõª`ùh ùT´Ÿñ�TèS@9¨9áS‘àªâªc ”è@ùi@ùhù?i(8¨�¡Dù©ƒ_ø@ù ëÁTôOJ©õK@ùý{H©ÿÑÀ_Ög ”ý{¾©ôO©ý‘ôª@ù€R‚$€R ”ó*1Tà* ”1àŸôOA©ý{¨À_Ö�@ù ÿÿð‘"ÿÿÐB&‘°ìÿ—1àŸôOA©ý{¨À_Ö"€R#€RÿCÑý{ ©÷SùöW ©ôO ©ýC‘¨�ôª÷ª¡Dùá*õ*ö*@ù¨ƒø@ùî ”1àTá#‘ó*ñ ”€45ÿÿ°µ-‘”@ù¤ ”@¹àªáªVëÿ—õª“@ùœ ”@¹ ÿÿ°Œ.‘áªMëÿ—õªá@ùW´áùàªâ*ã*ä*åªØ ”õª±Tà*Ë ”ˆð¡Dù©ƒ_ø@ù ëaTàªôOL©÷S@ùöWK©ý{I©ÿC‘À_Ö5ÿÿ°µÞ#‘Òÿÿþ ”b€RC€RµÿÿÿƒÑý{©öW©ôO©ýшðôªáª¡Dù@ù¨ƒø@ùùàª9³ ”óª ´•@ùàªÊ ”äªàªáªâªãªÔ ”પ ”,T ”@¹qT¨ ”è#‘A‘öù ´ôª¶ ”õªàª¿>ñõù Tà#‘á‘⪱ ”è@ùàùèù5´¿ñ�Tˆ@99áªâª© ”è'@©è ù?i(8á@ù ÿÿ°è7‘äêÿ—à@ùë@Tª ”ˆðñ¡Dù©ƒ_øàŸ@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_Ö¤ ” ÿÿ°„>‘¹ ”è@ùóªë`Tઓ ”àª� ”ý{º©ü ùúg©ø_©öW©ôO©ý‘ÿ@Ñÿƒшðéc‘óª¡Dù9A‘ôªöªõª@ù¨ƒøùÿ©ÿ£9g ”ü~ÓHµâªàc‘᪑ ”à@ù¡€RN ”÷ªÀ´àªáªM ”õªàµN ”⪠ÿÿÐÜ ‘áªOêÿ—áã‘àªJ ”qõŸ 4—ùô@ùv@ùàªF ”äªàªáªâªãªP ”ˆèã‘A‘÷ù•´àª9 ”öªàªß>ñ¶ƒø Tàã‘¡£Ñâª4 ”¨ƒ]øàùè'ù–´ßñáT¨@99àª% ”máªâª) ”¨ƒ]øé@ùè#ù?i(8à@ù@9ë@T, ”ß¾q T(ÿÿ°‰‘êã‘ @ùq@øVA‘!ÿÿ°!Ä5‘àªé'ùèóøè€Rÿÿ©ö£©ÿ_9F ”è@ùëöª`Tઠ”V´¨£Ñ7ÿÿ°÷ž‘A‘¿ƒ8ºÿ=©àC‘á#‘âªù ”±`Tà @ùâã‘áª÷ ”q¡þÿT¸^øàã‘ê ”äª £Ñãã‘áªâªô ” £Ñá€R€’\ ”±ÀüÿTøªàªÛ ”㪠£Ñáªâªâ ”±€ûÿTàªâ ”à @ù¸ ” ƒ]ø¡€R½ ”´€ù¡£Ñàªà ”5€R õ*à@ùëÁTàªÏ ”à @ù¥ ”õ* ƒ]øë@TÌ ”à@ùë@TÈ ”ˆð¡Dù©ƒ_ø@ù ëÁTà*ÿ@‘ÿƒ‘ôOE©ü @ùöWD©ø_C©úgB©ý{ƨÀ_Ö ÿÿ°$5‘Ù ”¼ ” ÿÿ°„>‘Ñ ” óªà@ùë`T¦ ”ર ”óª ƒ]øëÁTà@ùëáþÿTધ ”š ”à@ùë`ÿÿTðÿÿ@ù@9½qàŸÀ_Öý{¾©ôO©ý‘óªl ”€´ôOA©ý{¨À_Öôªj ”⪠ÿÿÐÜ ‘áªkéÿ—àªôOA©ý{¨À_Öý{½©õ ùôO©ý‘ôª@ùõª!€RP ”óª ùà´ñàŸôOB©õ @ùý{èÀ_Ö”@ùM ”⪠ÿÿ°¸ ‘᪚éÿ—ñàŸôOB©õ @ùý{èÀ_Öý{¿©ý‘G ”à4= ”᪠ÿÿ°¼‘ý{Á¨½êÿý{Á¨À_ÖQ ÿƒÑý{©õ#ùôO©ýшðé#‘óª¡Dù5A‘@ù¨ƒø@ùù ÿÿ°ô?‘9õÿ©ÿc9A ”`´ôª& ”äªà#‘áªâªãª0 ”è @ù(´à#‘á*Ú” 6á@ù ÿÿÐT‘éÿ—à@ùá*/ ” 4á@ù ÿÿ° +‘ éÿ—è@ùÿ ù9!ÿÿÐ!À ‘à#‘sëÿ—!ÿÿ°!Œ,‘à#‘oëÿ—à@ùá* ”@4á@ù ÿÿ°88‘ùèÿ—ó*à@ùë!Tà@ùÁ€R ” 4á@ù ÿÿÐБìèÿ—á#‘ઠ”3€Rà@ùë@Tú ”ˆð¡Dù©ƒ_ø@ù ëáTà*ôOE©õ#@ùý{C©ÿƒ‘À_Öõ ”è@ùóªë`Tàªç ”àªñ ”ý{½©õ ùôO©ý‘(@ùóª?ù9à ”@´t@ùõªÄ ”äªàªáªâªãªÎ ”h@ùñàŸôOB©õ @ùý{èÀ_ÖuÿƒÑý{©õ#ùôO©ýшðé#‘óª¡Dù5A‘@ù¨ƒø@ùù ÿÿ°ˆ5‘9õÿ©ÿc9º ”`´ôªŸ ”äªà#‘áªâªãª© ”è @ù(´à#‘á*S” 6á@ù ÿÿШ ‘�èÿ—à@ùá*¨ ” 4á@ù ÿÿБ…èÿ—è@ùÿ ù9!ÿÿÐ!À ‘à#‘ìêÿ—!ÿÿ°!P‘à#‘èêÿ—à@ùá*• ”@4á@ù ÿÿ°ì:‘rèÿ—ó*à@ùë¡Tà@ù�€Rˆ ” 4á@ù ÿÿ°ø"‘eèÿ—á#‘àª| ”á@ù ÿÿ°(‘^èÿ—3€Rà@ùë@To ”ˆð¡Dù©ƒ_ø@ù ëáTà*ôOE©õ#@ùý{C©ÿƒ‘À_Öj ”è@ùóªë`Tàª\ ”àªf ”ý{¾©ó ùý‘á*óªÿ”è*à*È6`@ùá€RV ”qàŸó @ùý{¨À_Öý{½©öW©ôO©ý‘@ùóªù ÿÿ°ì6‘9B ”@´t@ùõª& ”äªàªáªâªãª0 ”h@ùH´àªá*Ú” 6`@ùá€R3 ”à4a@ù ÿÿЄ‘\èÿ—à*ôOB©öWA©ý{èÀ_Ö  ”ôª¹) ”, ”€µˆ@¹q`ÿÿT ÿÿ°Ì;‘Kèÿ—à*ôOB©öWA©ý{èÀ_Ö@ùöªu@ùàª÷”äªàªáªâªãª ”àªá*­” 6`@ùá€R ”@4Á@ù ÿÿÐp‘/èÿ—à*ôOB©öWA©ý{èÀ_Ö €RôOB©öWA©ý{èÀ_ÖÿƒÑý{©öW©ôO©ýшðóª¡Dù@ù¨ƒøœÿÿ—€ 6!ÿÿ°! ‘àª7êÿ—àªá*‡” 6`@ùá€Rà ” 4`@ù8€Rè ” 4R ”@¹DqáTàªá*w”è*à*h6`@ùá€RÎ ”qàŸ- €R+s@ù— ”è#‘A‘öù ´ôª¥”õªàª¿>ñõù Tà#‘á‘⪠”è@ùàùèù5´¿ñ�Tˆ@99áªâª˜”è'@©è ù?i(8â@ù ÿÿÐ8‘áªÒçÿ—à@ùë@T˜”à*ˆð¡Dù©ƒ_ø@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_Ö“” ÿÿ°„>‘¨”è@ùóªë`Tં”ઌ”à*À_ÖÿƒÑý{©öW©ôO ©ýÑóªˆð)ÿÿÐ)õ‘¡Dùöª)@ùô*@ù¨ƒøˆÎ�R¨Œ®rÉøhr¸h€Rv"©¨£ÑA‘n9µÿ=©¿ƒ8 ÿÿ°x‘¡£Ñqôÿ—€6¡£Ñàª`”à*Qíÿ—è#‘#ôÿ—#ÿÿ°cÌ,‘à#‘áªâª$€RM”裑A‘èªôùAø?ë T @ùáùéù À=᪀€= @ù@9éù|©àª•éÿ—à@ùë@T9”à@ùè#‘A‘ë@T3” ƒ]øë@T/”ˆÐ¡Dù©ƒ_ø@ù ëÁTôOI©öWH©ý{G©ÿƒ‘À_Ö+”è@ùëôªÁTà@ùè#‘A‘ëáT ƒ]øëAT`@ùëaTઔઔà@ùè#‘A‘ë@þÿTôªà@ùè#‘A‘ë`ýÿT” ƒ]øë@ýÿT ôª ƒ]øë üÿTôª ƒ]øëüÿTõ”`@ùëàûÿTñ”àªû”ÿCÑý{©ü“ùöW©ôO©ýC‘ˆÐ)€Róª¡Dùöªõª@ù¨ƒø)9!ÿÿ�!Ä5‘@ù”€´è#‘©@ùÿc9A‘¿ùôªèù?9é@ùÿ ù?9à@ùë@TÌ”à#‘ €Râªà”€´ø’à#‘ €RâªÙ” ´à#‘ª”¨@ùÈËë# Tâªá#‘àªÒ”¨@ùþÿ´©@ùÑ(ia8)qaýÿTàª"€R ”¨@ù¨´àª¤” €RˆÐ¡Dù©ƒ_ø@ù ë@ T¨””@¹s@ùq¡T ÿÿ�L(‘áªgçÿ—à*ß9ˆÐ¡Dù©ƒ_ø@ù ëTîÿÿa@ù ÿÿ°t‘óçÿ—઄”)^”è#‘A‘öù€´ôªl”õªàª¿>ñõù Tà#‘á‘âªg”è@ùàùèù5´¿ñ�Tˆ@99áªâª_”è'@©è ù?i(8â@ù ÿÿ° ‘᪙æÿ—à@ùë@T_”à*ˆÐ¡Dù©ƒ_ø@ù ë÷ÿTôOT©ü“@ùöWS©ý{Q©ÿC‘À_Ö ÿÿ�$5‘s” ÿÿ�„>‘l”èÞÿ—è@ùóªë`TàªE”àªO”ÿCÑý{©ôO©ýшÐé#‘óª¡Dù4A‘@ù¨ƒøôÿ©ÿc9 ÿÿ�„‘á#‘Cóÿ—€6á#‘àª2”3€Rà@ùë@T)”ˆÐ¡Dù©ƒ_ø@ù ëÁT`ôOD©ý{C©ÿC‘À_Öìÿ—᪔ó*à@ùëÁýÿTîÿÿ”è@ùóªë`TઔઔÿÃÑý{©÷CùöW ©ôO ©ýшÐóªô*¡Dù@ù¨ƒø(@ù?ù9¨£Ñþÿ—¡ƒ]ø ÿÿ�L‘Èæÿ—裑ÿã9A‘ÿ9õÿ© £Ñá‘⣑ÿÿ—@6ᣑàªï”a@ù ÿÿ�H ‘·æÿ—è@9qóŸà@ùë@Tà” ƒ]ø¨£ÑA‘ë@TÚ”ˆÐ¡Dù©ƒ_ø@ù ëATà*ôOJ©÷C@ùöWI©ý{G©ÿÑÀ_Öè@9ˆ5¿ëÿ—k!Tè#‘ £ÑPêÿ—!ÿÿ°!$‘à#‘ö#‘èÿ—á@ù ÿÿ°à‘Œæÿ—à#‘á‘⣑÷#‘Éþÿ—ô*à@ùèB‘ë@T±”tø7ó*à@ùë�ùÿTÌÿÿ²”è@ùÉB‘óª ëaTà@ùë!T ƒ]ø¨£ÑA‘ëATદ”ઘ”à@ùë þÿT óª ƒ]ø¨£ÑA‘ë`þÿT óªà@ùë ýÿTˆ” ƒ]ø¨£ÑA‘ëýÿT‚”ઌ”ÿƒÑý{©õ#ùôO©ýшÐé#‘óª¡Dù5A‘@ù¨ƒøõÿ©ÿc9 ÿÿ°h‘á#‘òÿ— 6á#‘àªn” ]ëÿ—ô*[ëÿ—k!Tb@ù#ÿÿ�cè ‘àªáª$€RY”3€Rà@ùë@TX”ˆÐ¡Dù©ƒ_ø@ù ë�Tà*ôOE©õ#@ùý{C©ÿƒ‘À_Öó*à@ùëþÿTðÿÿN”è@ùóªë`Tàª@”àªJ”ý{½©õ ùôO©ý‘õªó*,ëÿ—ô*kT¢@ù#ÿÿ�cè ‘àªáª$€R)”ŸkàŸôOB©õ @ùý{èÀ_Öý{¾©ôO©ý‘A€Râªóªôª7”±`Tèªc@ùëCTâŸàªáª‰”àªáª”àªA€Râª&”誱þÿTàªá€R⪔±`Tèªc@ùëCTâŸàªáªq”àªáªþ”àªá€R⪔誱þÿT誊B‘ AøŠù?ë T @ù‰ùŠ ù À=@€=i@ùh~©B9‰ùôOA©ý{¨À_Ö ÿÿ�d‘!ÿÿ�!ü2‘âªJ”ý{º©üo©úg©ø_©öW©ôO©ý‘ÿƒÑôªˆÐ©ÃÑ¡Dù:A‘¸CÑC‘á*@ùનø(ÿÿ°Ñ ‘ @ùq@øº9©¿8©øsøè€R³#7©¿ó8Ôà4µ[y©è‘A‘÷#ù3´™à‘ö‘Í”¡WøÀb‘€RÍ”éƒ@ùê‘èª)�^ø@ ‹h´á* @¹2Æ”èƒ@ù÷‘ �^øé ‹)!@¹)-5©ÃÑ*‰ˆR¿8)A‘ª rÿÃ9éë©©=©©CÑ+A‘ª¸i€Rꃑ«';©IA‘*ÿÿ�JI&‘鯩J@ùé©é+‰R‰¨§rÿo9és ¸é‘+A‘i€RÿÃ9é« ©éƒ‘)A‘ëcùé ©�^øé¯©è‹y@ù5?´¨â@9h4¢A9 ઘ”¨@ù@ùàªA€R?Öâ*à‘¡ÃÑõ‘’”è‘ù*û*¼‚‘ºÂ‘A‘ÂA9à‘¡Ãч”èƒ@ù�^øˆkh87y69€R6;€R( 7èƒ@ù�^øVkhøvµÏ¡{© ÃÑâª@”´ù*[þ7áL© ÃÑâª9” ´û*èƒ@ù�^øVkhøÖ7´Èâ@9hûÿ5àª_”È@ù@ùàªA€R?Öâ*Óÿÿ¶ƒ]øß ñ 7T¨]øõCù ‘wµ‹è5µØÑàªCñøùTà‘áã‘⪔è@ùàCùèKùßñ`TßñÁTè@99&¶ƒ]øß*ñÉ4T¨]øõCù-‘wµ‹¨3µØ.ÑàªCñøùTà‘áã‘âªò”è@ùàCùèKùß.ñ Tß2ñaTè@99è@ùéC@ùèGù?i(8èG@ùˆµ#áªâªã”è@ùéC@ùèGù?i(8á H©àƒ‘”àC@ùëòÿTä”9€Rûñ7�ÿÿáªâªÒ”è@ùéC@ùèGù?i(8èG@ù(´à‘!ÿÿ°! ‘⪣€RN”±ÀTû*àC@ùë!Tyÿÿá H©àƒ‘ä”;€RàC@ùë@îÿTÔpÿÿ9€R;€Rè‘ú@ùa‘씸Cѵèƒ@ùé‘�^ø ‹ @¹2×”ù6á N© ÃÑË”›7ù7^» 6¨ƒYø ø’ ë )T!ÿÿ�!L‘ ÃÑ"€R¾”á J© ÃÑ»”µ[y©èƒ‘A‘÷3ùuµ¨‹h'µßBñàªöùTàƒ‘áã‘⪀”è@ùà3ùè;ù6´ßñ�T¨@99áªâªx”è@ùé3@ùè7ù?i(8è‘àƒ‘õ‘Vþÿ—áC@ùµB‘?ë€T¨Yøë TàƒÈ<©Zø¡øƒ‚<è´èCùéKùâG@ù´ Yø_ñAT(@99àƒÈ<¡øƒ‚<õCùèªR”èG@ù©Yø¨ƒø?i(8èC@ùÿGù9àC@ùë@TS”à3@ùë@TO”àS@ùè@ùë@TJ”àc@ùè @ùö‘÷‘ë@TC”às@ùè@ùë@T>” [øè@ùë@T9” ]øè@ùë@T4”•°Àb‘µ²Dù¨@ù©*D©èƒù�^øéj(øê‹ù^”¨&A©à"‘èƒù�^øéj(øÿ‡ù[”µ[y©è‘A‘÷#ùuµ¨‹µßBñàªöƒùTà‘á‘⪔èƒ@ùà#ùè+ù6´ßñ�T¨@99áªâªü”èƒ@ùé#@ùJ�RªŒ­rè'ù?i(8è‘É€RA‘¨€Ré+yõ#©(Œ�R©ÃÑ.­r6A‘©CѨ¸(Í�R¨Ì¥rê¹3¸è€R8A‘¶#=©Hî�Rhl­r¿s8¨¸(Ï…R¨CxÈ€R¸#;©¿c8à‘ã‘᪢€R”à4¢ƒ]øà‘£ÃÑ᪔`4¢ƒ[øà‘£CÑ᪔@5¢ƒ[ø â‡@ù ¢ƒ]øÿÿð!L‘à‘#€R”±ÀT‘ÿÿð!L‘à‘#€Rº”±ÀTã'@ùbë# TB´_±¡Tè#@ùà'ùi 8áªà‘'”è#@ù‰B‘‰ùë Té+@ùˆù‰ ùàÀ= €= [øè'@ù÷©ÿC9ëˆù@T ” ]øë@Tœ”àƒ@ùë@T˜”à#@ùë@T”” Wøë@T�” Yøë@TŒ”ˆ°¡Dù©_ø@ù ë¡Tÿƒ‘ôOE©öWD©ø_C©úgB©üoA©ý{ƨÀ_Ö½”ÿÿð„>‘š”�”ÿÿð„>‘–”ÿÿðd‘ÿÿð!¤"‘b€Rãªß”ÿÿð„>‘Œ”ÿÿðd‘ÿÿð!¤"‘b€RãªÕ”ÿÿðe‘ÿÿð!ü2‘âªàªÎ”ÿÿð„>‘{”ÿÿð$5‘|”è3@ùëYXW&èC@ùëôªÁTàS@ùè@ùëa Tàc@ùè @ùë� Tàs@ùè@ùë¡ T [øè@ùëÁ T ]øè@ùëá TWàª5”àS@ùè@ùë@ýÿT;ôªúùM21úùôª Wøë`TJ¨[øôªúùëaT ]øë¡Tàƒ@ùëÁTà#@ùëáT Wøë!T Yøè@ùëATઔઠ” ]øë ýÿT”àƒ@ùë€ýÿT”à#@ùë`ýÿT” Wøë@ýÿT!ôªàS@ùè@ùëàõÿTõ”àc@ùè @ùëÀõÿTð”às@ùè@ùë õÿTë” [øè@ùë€õÿTæ” ]øè@ùë@Tá”à‘#” Wøë ùÿTÛ” Yøè@ùëùÿTÖ”àªà”ý{¿©ý‘@ùá*×”qàŸý{Á¨À_Öý{½©õ ùôO©ý‘óªÿÿð@‘᪠” ´t@ùõªª”äªàªáªâªãª´”ઊ” €RôOB©õ @ùý{èÀ_Öà€Rû”@ÿÿ´áªô”`ýÿµöÿÿÿƒÑý{©öW©ôO©ýш°ôª Õÿÿ¡Dùá#‘@ù¨ƒø„”ó*`4õ@ù–@ùં”äªàªáªâªãªŒ”ˆ°q¡Dù©ƒ_øàŸ@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_ÖŠ”ÿƒÑý{©öW©ôO©ýщ°èªôª)¡Dùá#‘àª)@ù©ƒø\”ó*`4õ@ù–@ùàªZ”äªàªáªâªãªd”ˆ°q¡Dù©ƒ_øàŸ@ù ëÁTôOE©öWD©ý{C©ÿƒ‘À_Öb”à*À_Öà*À_ÖÿÃÑý{©÷#ùöW©ôO©ýш°ôªõ*¡Dùáª@ù¨ƒø@ù�”óª@´•@ùàª.”äªàªáªâªãª8”ઔˆ°ñ¡Dù©ƒ_øàŸ@ù ëÁTôOF©÷#@ùöWE©ý{C©ÿÑÀ_Ö«”@¹qàýÿTÕý7”@ùý”è#‘A‘÷ùÀ´õª ”öªàªß>ñöù Tà#‘á‘⪔è@ùàùèù6´ßñ�T¨@99áªâªþ”è'@©è ù?i(8â@ùÿÿð|*‘áª8áÿ—à@ùëÀøÿTþ”Äÿÿ”ÿÿð„>‘”è@ùóªë`Tàªó”àªý”@ùãªâ*ÿÃÑý{ ©üo ©úg ©ø_ ©öW ©ôO©ýC‘ˆ°óªô*¡Dùõª@ù¨ƒø-”à ´öª.”@ ´ùªÿÿð÷N‘ûÿ‰ÿÿð7%‘(3A8ˆ9è@ùI@ùûÿ‰Hù?i(8h@ù�‘hùઔùª  ´ @ù!O‘"€R”ÿÿ5(K@9ˆ q¬TH4 q Tðÿÿq Tq�ýÿTtý7ઠ”!O‘â#‘ã* ”1@üÿTè@¹ ”6@q¡ûÿT@Qj!ûÿT O‘áªù” úÿ4 O‘áªõ” úÿ4z¢@©_ë T\C‘ O‘\ù}”ûª@ñàù#Tá#‘àªâªy”@ùüªè@ùH ù»öÿ´ñ öÿT!O‘àªâªr”®ÿÿ"O‘àªáª�”±ÿÿàªÚ”ˆ°¡Dù©ƒ_ø@ù ë!TôON©öWM©ø_L©úgK©üoJ©ý{I©ÿÑÀ_Öo”ÿCÑý{©ó#ùýш°é#‘J€R¡Dù3A‘ãª@ù¨ƒø(€R@ùê3yó£©á#‘â*hÿÿ—à@ùë@TP”ˆ°¡Dù©ƒ_ø@ù ë¡Tý{C©ó#@ùÿC‘À_ÖM”è@ùëóª`Tàª?”àªI”@ùãª"€RMÿÿÿCÑý{©ó#ùýш°é#‘J€R¡Dù3A‘ãª@ù¨ƒø(€R@ùê3yó£©á#‘"€R:ÿÿ—à@ùë@T"”ˆ°¡Dù©ƒ_ø@ù ë¡Tý{C©ó#@ùÿC‘À_Ö”è@ùëóª`Tઔઔà*À_Öà*À_Ö@ù(@ù_ëaT‚´ý{¿©ý‘!@ù@ùi”qàŸý{Á¨À_Öà*À_Ö €RÀ_ÖÿÃÑý{©üo©úg©ø_©öW©ôO©ýC‘ˆ°éç{²¡Dù@ùèùp@©ˆË ë@TýE“ €ÿ’÷ªóªöªñ …Ÿš*«_ ëJ1‹š?«(ËýE“{!Šš»´`ë{Ó”õªõª¸‹àªC‘ù»”úª@ñàù#Tá‘àªâª·”è@ùù(ùùªZ´_ñ�Tè@9(9àªáªâª­”è@ù @ùŸëù?i(8èª`Tèª,@ùjùl ùŠ‹«‹�‘L@ùI}©Š‹_ë?9lù`T‰‹«‹*AølA‘lù? ë¡ýÿT@À=€€=íÿÿ¨‹Ÿë`TéªM@ù‹ù�ùË ‹  ‹)�‘m@ùj}©Ë ‹ë_9�ù`TÊ ‹  ‹KAø�Á‘�ù_ ë¡ýÿT`À= €=íÿÿ ‹�‘t´àªx”¨‹uZ©h ùˆ°¡Dùé@ù@ù ë�TôOF©öWE©ø_D©úgC©üoB©ý{A©ÿÑÀ_ÖÿÿðP ‘ˆ”k”r”àª`”ÿ”óª”àªg”÷×ÿ—ý{¾©ó ùý‘ Õó¡Pàª7”€° ÕiœDùáªó @ùý{¨3_$ÕÐîN9p4 € 8À_Öð* ü_0|Ñÿÿ5À_Öý{¿©ý‘ý{Á¨À_Öý{¿©ý‘ý{Á¨À_Öð{¿©��¾Dùâ%‘ Ö Õ Õ Õ��ÂDù&‘ Ö��ÆDù"&‘ Ö��ÊDùB&‘ Ö��ÎDùb&‘ Ö��ÒDù‚&‘ Ö��ÖDù¢&‘ Ö��ÚDùÂ&‘ Ö��ÞDùâ&‘ Ö��âDù'‘ Ö��æDù"'‘ Ö��êDùB'‘ Ö��îDùb'‘ Ö��òDù‚'‘ Ö��öDù¢'‘ Ö��úDùÂ'‘ Ö��þDùâ'‘ Ö��Eù(‘ Ö��Eù"(‘ Ö�� EùB(‘ Ö��Eùb(‘ Ö��Eù‚(‘ Ö��Eù¢(‘ Ö��EùÂ(‘ Ö��Eùâ(‘ Ö��"Eù)‘ Ö��&Eù")‘ Ö��*EùB)‘ Ö��.Eùb)‘ Ö��2Eù‚)‘ Ö��6Eù¢)‘ Ö��:EùÂ)‘ Ö��>Eùâ)‘ Ö��BEù*‘ Ö��FEù"*‘ Ö��JEùB*‘ Ö��NEùb*‘ Ö��REù‚*‘ Ö��VEù¢*‘ Ö��ZEùÂ*‘ Ö��^Eùâ*‘ Ö��bEù+‘ Ö��fEù"+‘ Ö��jEùB+‘ Ö��nEùb+‘ Ö��rEù‚+‘ Ö��vEù¢+‘ Ö��zEùÂ+‘ Ö��~Eùâ+‘ Ö��‚Eù,‘ Ö��†Eù",‘ Ö��ŠEùB,‘ Ö��ŽEùb,‘ Ö��’Eù‚,‘ Ö��–Eù¢,‘ Ö��šEùÂ,‘ Ö��žEùâ,‘ Ö��¢Eù-‘ Ö��¦Eù"-‘ Ö��ªEùB-‘ Ö��®Eùb-‘ Ö��²Eù‚-‘ Ö��¶Eù¢-‘ Ö��ºEùÂ-‘ Ö��¾Eùâ-‘ Ö��ÂEù.‘ Ö��ÆEù".‘ Ö��ÊEùB.‘ Ö��ÎEùb.‘ Ö��ÒEù‚.‘ Ö��ÖEù¢.‘ Ö��ÚEùÂ.‘ Ö��ÞEùâ.‘ Ö��âEù/‘ Ö��æEù"/‘ Ö��êEùB/‘ Ö��îEùb/‘ Ö��òEù‚/‘ Ö��öEù¢/‘ Ö��úEùÂ/‘ Ö��þEùâ/‘ Ö��Fù0‘ Ö��Fù"0‘ Ö�� FùB0‘ Ö��Fùb0‘ Ö��Fù‚0‘ Ö��Fù¢0‘ Ö��FùÂ0‘ Ö��Fùâ0‘ Ö��"Fù1‘ Ö��&Fù"1‘ Ö��*FùB1‘ Ö��.Fùb1‘ Ö��2Fù‚1‘ Ö��6Fù¢1‘ Ö��:FùÂ1‘ Ö��>Fùâ1‘ Ö��BFù2‘ Ö��FFù"2‘ Ö��JFùB2‘ Ö��NFùb2‘ Ö��RFù‚2‘ Ö��VFù¢2‘ Ö ! í 0  ûÿÿo@H ùÿÿoˆ� h  ô G õþÿoЀHÈ Ü ìðÿÿo¸ þÿÿoœÿÿÿo‹¹ja 8r{“× 2õ¹æï®3î;-Î$³j®19ff3e9c3602ae8e841925bb461a0adb064a1f1903667a5e0d87e8f608f425acapp.dll@(#)Version 9.0.525.21509 @Commit: e36e4d1a8f8dfb08d7e3a6041459c9791d732c01d38cc827-e34f-4453-9df4-1e796e9f1d07clang version 18.1.8GCC: (Alpine 10.2.1_pre1) 10.2.1 20201203Linker: LLD 18.1.8.init_array.fini_array.text.got.comment.got.plt.rela.plt.init.tbss.bss.dynstr.eh_frame_hdr.gnu.version_r.interp.data.rel.ro.rela.dyn.gnu.version.dynsym.gnu_debuglink.fini.gnu.hash.relro_padding.eh_frame.gcc_except_table.tm_clone_table.note.gnu.build-id.dynamic.shstrtab.rodata.dataapphost.dbg/ì1‹vààüü$¢   ˜ •ÿÿÿo¸ ¸ âgþÿÿoœœ0¿öÿÿoÐÐ$QôôG ‹@@H6Bˆˆ� â''\ *2t0t0!Y˜F˜F¤Ø@K@K�ÐgÐg ¨@Üܹìì;€F€€€€H ÈÈ~ÐÐHàøøp-hhHɰ°P2°°­ô`#`#L`#`#\$0`#S ³#8ªì$open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/libc/dotnet-app-x86_64-glibc000077500000000000000000002226101524057645600321420ustar00rootroot00000000000000ELF>ps@@8 @" @@@  àààlclcpcpsps ª ª..`ðppDpDØô¸¸.¸.  Råtd..`ðPåtd,F,F,FœœQåtdüüüDD/lib64/ld-linux-x86-64.so.2GNU GNUÖ`Áݵٽ‹XI2tT2×¼Ö " > X"g {“«¸¿ O˜Øß%4E•ªéû Pm‹"—ž¦®·½ÇÍÔãéð÷>FRYd§ç2U—â2CKm‹“ÓÚâðE†‹’›¢¨³ºÁÆÏÖÜäëóþGNU\cjsy­ñ- N º ì  E X Ÿ · À Ê Ò Ü ä ê ÷         ` `, `  •  É àui  ui  t)’; aø— G pø— V qø—e Ó¯kt P&y � ui  ii Ÿ ii © ”‘–³ –‘–¾ ii Ÿ p__libc_start_main__gmon_start___ITM_deregisterTMCloneTable_ITM_registerTMCloneTable__cxa_finalize_Jv_RegisterClasses_ZNSt8ios_base4InitC1Ev_ZNSt8ios_base4InitD1Ev__cxa_atexitstrlen_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_createERmmmemcpy_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEmmPKc_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_replaceEmmPKcm_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4swapERS4__ZdlPv_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4__Unwind_Resume__stack_chk_fail_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEED2Ev__cxa_call_unexpected__gxx_personality_v0_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4findEcm__cxa_begin_catch_ZSt9terminatev_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm_ZSt19__throw_logic_errorPKc_ZSt20__throw_length_errorPKcsched_yieldstderrfopen64setvbufvfprintffputcvsnprintf_Znwmmemset__tls_get_addrfputsstdoutfflush_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_mutateEmmPKcmstrncmpstrncasecmpstrcmpstrcasecmp_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5rfindEPKcmm_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5rfindEcm_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE12find_last_ofEPKcmm_ZSt24__throw_out_of_range_fmtPKcz_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE8_M_eraseEmm_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE14_M_replace_auxEmmmc_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE17find_first_not_ofEPKcmm__errno_locationstrtoul_ZSt24__throw_invalid_argumentPKc_ZSt20__throw_out_of_rangePKctoupper_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7reserveEmstrchrtolower__cxa_rethrow__cxa_end_catch_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareERKS4__ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6substrEmmtimegmtimestrftimeopen64close__fxstat64mmap64getcwdfreestrerrordlopendlsymdlerrordladdrdlclose__getdelimsscanf_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4findEPKcmmfclosestrtolgetenvaccessgetuidgetpwuidmkdirfgets_ZNSt13basic_fstreamIcSt11char_traitsIcEEC1Ev_ZNSt13basic_filebufIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmode_ZNSt9basic_iosIcSt11char_traitsIcEE5clearESt12_Ios_Iostate_ZNKSt5ctypeIcE13_M_widen_initEv_ZSt7getlineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7__cxx1112basic_stringIS4_S5_T1_EES4__ZNSt13basic_filebufIcSt11char_traitsIcEE5closeEv_ZTTSt13basic_fstreamIcSt11char_traitsIcEE_ZNSt13basic_fstreamIcSt11char_traitsIcEED2Ev_ZNSt8ios_baseD2Ev_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEmmRKS4__ZSt16__throw_bad_castvrealpathgetauxvalopendirreaddir64fnmatchdirfd__fxstatat64closedirbcmplibdl.so.2GLIBC_2.2.5libpthread.so.0libstdc++.so.6GLIBCXX_3.4GLIBCXX_3.4.11GLIBCXX_3.4.20GLIBCXX_3.4.21CXXABI_1.3libgcc_s.so.1GCC_3.0libc.so.6GLIBC_2.3GLIBC_2.4GLIBC_2.14GLIBC_2.16ld-linux-x86-64.so.2$ORIGIN/netcoredepslibm.so.6.0t .pt(.°t0.{8.°…@.`•H.p P.€ÍX.°ã`.Àp.;5x.!1€.ì;ˆ.-�.�6˜.(+ .0+¨.�-°.Õ1xDxDØ0à0è0ð0ø01°F11#1( 1b@1H1P1X1`1 h1 p1 x1 €1 ˆ1�1˜1 1¨1°1¸1À1È1Ð1Ø1à1è1ð1ø122 2!2" 2#(2$02%82'@2)H2*P2+X2,`2-h2.p2/x20€21ˆ22�23˜24 25¨26°27¸28À29È2:Ð2;Ø2<à2=è2>ð2?ø2@3A3B3C3D 3E(3F03G83H@3IH3JP3KX3L`3Mh3Np3Ox3P€3Qˆ3R�3S˜3T 3U¨3V°3W¸3XÀ3YÈ3ZÐ3[Ø3\à3]è3^ð3_ø3`4a4c4d4e 4f(4g04h84i@4jH4kP4lX4m`4nh4o app-local: [%s]false0123456789The managed DLL bound to this executable is: '%s'}You must install .NET to run this application. %shttps://go.microsoft.com/fwlink/?linkid=2063370Considering fxr version=[%s]... coreservicing/proc/self/mapsUsing environment variable %s=[%s] as runtime location.Not found ]aRuntime config is cfg=%s dev=%s%s: __pos (which is %zu) > this->size() (which is %zu)/proc/self/exeThe managed DLL bound to this executable is longer than the max allowed length (%d)fxrError: [%s] does not existFound registered install location '%s'./usr/share/dotnet global install location [%s] self-registered config location [%s] environment_variableriscv64s390xFailed to resolve full path of the current executable [%s]Invoking fx resolver [%s] hostfxr_main_startupinfoResolved fxr [%s]...Checking if CoreCLR path exists=[%s]Failed to unload library, error: %sLooking for architecture-specific install_location file in '%s'.The library %s was found, but loading it from %s failedThe required library %s does not support single-file apps.Reading fx resolver directory=[%s]COREHOST_TRACECOREHOST_TRACE_VERBOSITY.loongarch64linux_DOTNET_TEST_GLOBALLY_REGISTERED_PATHError: [%s] does not contain any version-numbered child foldersCOREHOST_TRACEFILEx64DOTNET_ROOT_&framework_version=Using core servicing at [%s]%*p-%*p %*[-rwxsp] %*p %*[:0-9a-f] %*d %s This executable is not bound to a managed DLL to execute. The binding value is: '%s'.runtimeconfig.dev.jsonThe required library %s could not be found. Search location options [0x%x].runtimeconfig.json_DOTNET_TEST_INSTALL_LOCATION_PATH%s App: %s Architecture: %s App host version: %s .NET location: %s Learn more: https://aka.ms/dotnet/app-launch-failed Download the .NET runtime: %s&apphost_version=%sThe application to execute does not exist: '%s'. app_local.netDOTNET_RUNTIME_IDmissing_runtime=trueFailed to load %s, error: %sThe app-relative .NET path is longer than the max allowed length (%d)%s - search options: [linux-x64basic_string::substrDirectory core servicing at [%s] was not ACL-ed properlyFailed to map file. mmap(%s) failed with error %dhostfxr_set_error_writertruearm64.., app-relative path: Tracing enabled @ %sDOTNET_ROOTtouch_fileVERSION_ID=The app-relative .NET path is not embedded.Unable to open COREHOST_TRACEFILE=%s for writingx869.0.5libcoreclr.soThe install_location file ['%s'] does not exist - skipping.App path: [%s]Using global install location [%s] as runtime location./\%c %Zrealpath(%s) failed: %salpine.apphostDid not find [%s] directory [%s]Directory core breadcrumbs [%s] was not specified or foundcorebreadcrumbsinstall_location_Failed to map file. fstat(%s) failed with error %dlibhostfxr.soInvoking fx resolver [%s] v1Failed to map file. open(%s) failed with error %d - Installing .NET prerequisites might help resolve this problem.The required library %s does not support relative app dll paths.Using app-relative location [%s] as runtime location.Found previously loaded library %s [%s].&arch=%dbasic_string::eraseThe required library %s does not contain the expected entry point.Using app-local location [%s] as runtime location.basic_string::appendarmCORE_SERVICINGr.NET root search location options: %dHost path: [%s]Dotnet path: [%s]HOMEBundle Header Offset: [%ld]hostDOTNET_MULTILEVEL_LOOKUPgetcwd() failed: %sFallback directory core breadcrumbs at [%s] was not foundError: the required library %s could not be found in [%s]DOTNET_MULTILEVEL_LOOKUP is set to %s&rid=&os=ppc64leFallback directory core servicing at [%s] was not foundFailed to determine default extraction location. Environment variable '$HOME' is not defined and getpwuid() returned NULL.rocky. globalDetected latest fxr version=[%s]...basic_string::_M_construct null not validrhel.Embedded app-relative .NET path: '%s'stoulCORE_BREADCRUMBSFailed to create default extraction directory [%s]. %shostfxr_main_bundle_startupinfohostfxr_mainDetected Single-File app bundleError: the default install location cannot be obtained..deps.jsonRealpath CORE_BREADCRUMBS [%s]Breadcrumb store [%s] is not ACL-ed with rw-Default extraction directory [%s] either doesn't exist or is not accessible for read/write./etc/dotnetinstall_location_DOTNET_TEST_DEFAULT_INSTALL_PATHID=hostfxr_main_startupinfoInvoking fx resolver [%s] hostfxr_main_bundle_startupinfo--- Invoked %s [version: %s] main = {Multilevel lookup is %shttps://aka.ms/dotnet-core-applaunch?Probed for and did not find library symbol %s, error: %sopt/etc/os-release0123456789."'You must install or update .NET to run this application. app-relative: [%s] app_relativeframework=Realpath CORE_SERVICING [%s]Directory core servicing at [%s] was not specified or foundThe install_location file ['%s'] failed to open: %s.Looking for install_location file in '%s'. environment variable: [%s]armv6open(%s) failed in %sFailed to determine default extraction location. Environment variable '$HOME' is not defined and directory reported by getpwuid() [%s] either doesn't exist or is not accessible for read/write.Did not find any install location in '%s'.19ff3e9c3602ae8e841925bb461a0adb064a1f1903667a5e0d87e8f608f425acc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2ÄÓ|Ó,Ô|Ó ÔÄÓÿÿ0‰é˜Së‚’5€ë%“º€åÅéªÝÿ› VhÿÿÆÆîá‹ðì1ÿÿ 88.’f]ÿ›°§;÷ ­ ë Ýé œ!í É÷ � í  ç Ôå ÇÐ Õ Ò ŸoÔ Ž ¤ 𢠩!ü êð ýí � ¦ èGÎ ¯ ú »ø ÍI¨ ³ æ à $Ò ÿ &¦ ¥ <î ” š À Æ Ä Øÿÿ??ÖT³ÿÿaÞÞ×Þôµ‰KÔ‰ðݲûŽÁ ŽŠø·Œ ýœ 1·à ŸÀ Þã µ‚ ·ŽãÿÿJQQÖ`ѱó¿ õÊúÒhÿ‹ Ï—¾ê¼ýº›hÛƒÔÿÿ˜˜`¾› ‚§¼ÿÿ,­D«V ©_{ÿÿ€òå$÷­í»~ÿÿÊÊ÷ÏÛÿÿ��/–Ìwÿÿ˜˜ µ¡Áÿÿ’’+„½tÿ›MC·· è¾»�¦«Kö¨Œpü™‘aò »� ¦™Wÿ›  !ÿÿ /·?¥ÿÿ,¸D¶€¸ŽWÿ›YNXX Áv·¿6ÁõNà ¿ºÈÉEŽŠÏ˜Ùñ Ê� Æ« Á·cÿÿ DG·‹Yÿÿ55ºN�ÿÿ `ºzíÿÿ5 íQœúíbÿÿ™™È°Š¸´ÿÿ´´™ÂK� ™™,ÿÿ™™È°Š¸´ÿÿ''ÚT Øa ´n ßz‰ÿÿA¢¢°ŠàTŒÈõÎçä å¹$÷£ Œ¾ ŠÙ ÷åSÿÿ ?Z™™ÿ›5-½½ªËÓž ªª ÊÏÒó×$ÿÿéé‘÷Ž… ‘‘1ÿÿ, [5Sÿÿ B …O‰ÿÿ B”^‰ÿÿùù§� ¥¥K¹ð¨ÿÿ2ggҕл¹ôÒ¢·È©ëÒˆwÿÿcëë­ ûûö› †…‹‹ šk…å ¿ ú ½  Ï · Á âå� õvë ý ú àÿÿÿÿ„�âï‚ý´ÿÿûûÔ‰xÿÿG[¤©šùœÀžÒžðÓ¾¢ö Ð˜ñ ¤Œ ž˜�ÿÿ [äê¿Xÿÿ [ôùÏWÿÿ°°ŒÁxÿÿ` ր䗳úØ‚”ÿ›%‘‘ àžÃáèò¦ÿÿ 1]œŽ;ÿÿ!>>½g�Ïù î†6ð¼Êÿÿ 1~¿¯=ÿ›È¾�è¬*¤½)¡„£¹ ¶æ ¾ÃÞÑãæ]à ‡ÏqÀHìâ £ý °¡ê Ùÿ OÎ ÅìÆ�¾¿~áÃ<ê˜á𡲠ÆÑ ±ð ¬�±¿¬ïáš Ù¹ ¡ÅžÿÿššÝ«_ÿÿ4 e?Sÿÿ4hBSÿ›-"««ï¹Ö�”—¸œ$;˜’D-´t.ä„.Ä.$ä.D/d$/„D/üT40Ô4¤5d´6”„7Ø$>Äd> t?è„?Ä?$ä?DdH@DLt4ND„NhÄN�4OÀtO¨TPÔRàdS”U¤U(ôVPXx$X˜tYÀôYä$ZDZ$„Z4Ô[hÄ]œ_DD_dT_„d_¤¤_Ä´_äÄ_ ”`0 Db\ ÔcŒ teÌähi¸ tià k $k0 DkP Tk(Dlp dlTTm€tq´dr� „r° dsäTtÜ duÔv@¤xpz¤d{ØÔ| ”} $8ä‚l¤ƒ8 ´„d Ô„œT‡Œ ”‡Ðd‰ô‰0ÔŠ`Ä‹¬ Ô‹�ô�Ì Žì Ž $Ž, 4ŽL D޼D�l ä‘ðD˜˜ „˜$Dœ¼ TœÜ „� Ä�( ÄžT $Ÿx DŸ˜ T È t \¢Œ´¦è Ħ§4d§\”§„´§ÀT©¤´©ÌÄ©ðt«ì¤«ä¬ ¤®<´®PÔ°„ô³¸Ä´èÔ¶Ä·\¸€¹HôƨÇȤÇô$È ¤ÈL´ÈlÄÈ|TÊŒtʬ¤Ì°DÍÜdÍÜÎüÎ$Î<TΔÐ\ÔÐ|DÑÄzRx �ˆ(*zRx �ˆ) A†C H <x)1A†C Bƒf \˜)A†C K |˜)A†C K œ˜)A†C K ¼˜)A†C K Ü(/1A†C Bƒf  üX89A†C CƒŽq  „9A†C <x91A†C Bƒf \˜9A†C K  |èGIA†C BƒB $ H6A†C Bƒh E B ,È,HaA†C FƒŽ�G E J ølH1A†C Bƒf $,KAA†C JƒŽ[ A @„NA†C K $`tNAA†C JƒŽ[ A $ˆœO A†C JƒŽõ A °„PA†C J $ЄPAA†C JƒŽ[ A  ø¬QtA†C Bƒi R-A†C Bƒf <RA†C T \R1A†C Bƒf |¸V2K†C ` ÆœØV A†C D ¼ÈV A†C D ܸV7P†C c ÆüØV A†C A ÈV A†C A (<¸VÈA†C IƒŽ�š A (h\W§A†C IƒŽ�y A ,”àX�A†C MƒŒ�Ž�( A (Ä@Z A†C IƒŽ�K A $ðT_UA†C FƒŽ�J ,Œ_—A†C MƒŒ�Ž�e A Hü` A†C F hì`A†C O ˆì` A†C H ¨ÌaA†C K ÈÌgA†C P (èÌgÓA†C IƒŽ�Š A (piA†C KƒŒŽ�Õ A ,@Äq»A†C MƒŒ�Ž�¤ A (pdxA†C KƒŒŽ�Õ A $œHyA†C L E C ÄÀ{1A†C Bƒf ä€A†C K  ‚A†C K $‚A†C I D‚A†C K dð�A†C K „à�A†C K (¤Ѓ˜A†C KƒŒŽ�A A  Ф‹>A†C CƒŽv ô€� A†C A (p�/A†C NƒŒŽ� A @t�1A†C Bƒf (`”�ùA†C LƒŽ�ã A  Œh‘QA†C CƒŽI °¤‘A†C K ,Ф‘A†C PƒŒ�Ž�Ü A  „’A†C K  ĘA†C J (@ ´˜=A†C CƒŽQ A c $l ȘUA†C FƒŽ�J $” ™&A†C [ E A ¼ ™A†C H $Ü ¨šZA†C FƒŽ�O  àš A†C A  $ €œ0A†C CƒŽh (H Œœ2A†C GƒŒŽ�" A t `ŸA†C C  ” `¨GA†C FƒŽ�| $¸ Œ¨çA†C CƒŽÇ A à D·A†C P ( D·�A†C FƒŽ�P A (, ¨·€A†C KƒŒŽ�k A (X ü·A†C KƒŒŽ�j A „ P¸A†C C ¤ @¸A†C C Ä À¹A†C I ,ä À¹(A†C PƒŒ�Ž� A  `¼A†C L 4 ½A†C C T ð¼A†C C t à¼+P†C Q Æ” 0¿1A†C Bƒf D´ P¿eB�BŽE �B(ŒH0†H8ƒM@r8A0A(B BBBü x¿zPLRx›•î �0$@“åÿÿA†C PƒŒ�Ž�k A 0X"“åÿÿA†C CƒŽE E D A ,Œ¨"�såÿÿA†C KƒŒŽ�I A @¼$Ã_åÿÿA†C IƒŽ�S.K.M.{.^ A P.T.H.0¤$œ/åÿÿA†C PƒŒ�Ž�x A 44P+¯åÿÿA†C MƒŒ�Ž�Ä A øl˜,q‹åÿÿA†C PƒŒ�Ž�r.Y.R.N._.Q.N.K.j.Y.X.V.M.U.N.K.y.g.a.S.E.M.a.V.X.[.P.I.E.Y.a.e.I.y.r.k.o.p.n.[.\.Y.O.P.D.Q.U.}.Z.Q.R.R.k A B.c._.f.B.D.X.Q.U.H.R.R.T.0h4×÷äÿÿA†C PƒŒ�Ž�ò J 0œÈ7ãåÿÿA†C KƒŒŽ�W A (ÐÄ:ÚûäÿÿA†C GƒŽ� A ,üx;¹çäÿÿA†C KƒŒŽ�] A ,,X>*ÓäÿÿA†C NƒŒŽ�´ A 0\HEC»äÿÿA†C MƒŒ�Ž�é A 0�dFâ›äÿÿA†C MƒŒ�Ž�m A ,Ä H1äÿÿA†C IƒŽ�Û A 0ô OpcäÿÿA†C MƒŒ�Ž�² A $(ÜR)äÿÿA†C \ A (P$UägäÿÿA†C GƒŽ« A (|VåKäÿÿA†C GƒŽª A 0¨ÌV;äÿÿA†C MƒŒ�Ž� A ,ܸZäcäÿÿA†C KƒŒŽ�§ A ( x\ëCäÿÿA†C GƒŽ® A ,8L^g+äÿÿA†C KƒŒŽ�* A ,hŒ_Ï äÿÿA†C KƒŒŽ�B A 0˜,alïãÿÿA†C MƒŒ�Ž�Ý A 0ÌhbE×ãÿÿA†C MƒŒ�Ž�Ñ A 0„clÃãÿÿA†C MƒŒ�Ž�Ý A (4€eƒ«ãÿÿA†C GƒŽÓ A 0`äf¸£ãÿÿA†C MƒŒ�Ž�þ A ,”pj±·ãÿÿA†C FƒŽ�‚ A 0Ä0l{—ãÿÿA†C MƒŒ�Ž�ó A 0ø¼n›ãÿÿA†C MƒŒ�Ž�E A (,Xpˆ‡ãÿÿA†C GƒŽO A ,X¼pØgãÿÿA†C IƒŽ�w A ,ˆlqçGãÿÿA†C IƒŽ�† A (¸ÿÿÿè3žH‹…@ÿÿÿH‹8H;½0ÿÿÿ„9ÿÿÿèžH‹{H;½(ÿÿÿ„4ÿÿÿèždH‹%(H;EЄ/ÿÿÿèžÌÌÌÌÌÌÌÌÌUH‰åAVSH‰ûH‹?H…ÿtèùrH‹{HH�CXH9Çtè·�H‹{(H�C8H9Çtè¥�H‹{HƒÃH9ßt [A^]é��[A^]ÃI‰ÆH�{Hè¾�H�{(èµ�HƒÃH‰ßè©�L‰÷è±�ÌUH‰åSPH�=ÐH‰ßèËœH‹=äµH�UÉH‰ÞHƒÄ[]鿜ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0H‰ûdH‹%(H‰EØL‹wL�=VËL‰ÿ莜H‰ß1öL‰òL‰ùI‰À軜L�eÈL‰e¸L‰ÿèkœI‰ÆH‰E°L‰àIƒþrH�}¸H�u°1Òè\œH‰E¸H‹M°H‰MÈM…öt#Iƒþu ¶ îʈëH�5ãÊH‰ÇL‰òè8œH‹E°H‰EÀH‹M¸ÆH‹EÀH=r1ÛH�=®ÿÿ¾1ÀèwëRHƒø@r9H� 8ÁÿÿH�}¸º 1öèø›…ÀuH� MÁÿÿH�}¸¾ º èÚ›…Àt?H‹3³H�=1¬ÿÿ1ÀèÓH‹}¸L9çtèå›dH‹%(H;EØuN‰ØHƒÄ0[A\A^A_]ÃH‹31ÛH�=W±ÿÿ1Àèäë¿ëH‰ÃH‹}¸L9çt蟛dH‹%(H;EØuH‰ß訛賛ÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàL‹6èk4I‰ÇH‹H�}À1ö1Òè8HL�º´ÿÿH�=űÿÿH�5‚½ÿÿL‰òL‰ùI‰Ù1ÀAPÿuÀèNHƒÄH‹}ÀH�EÐH9Çtè›dH‹%(H;Eàu7HƒÄ([A^A_]ÃH‰ÃH‹}ÀH�EÐH9ÇtèÙšdH‹%(H;EàuH‰ßèâšèíšÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ì(H‰óA‰þdH‹%(H‰EÐL�mÀL‰m°HÇE¸ÆEÀH�}°èL�„À„óH�}°1öèé�„À„àH�E H‰E�HÇE˜ÆE H�E€H‰…pÿÿÿHÇ…xÿÿÿÆE€H�…`ÿÿÿH‰…PÿÿÿHÇ…XÿÿÿÆ…`ÿÿÿH�½Pÿÿÿè×üÿÿA¿•€€„À„8H�½Pÿÿÿ¾/1Òè6šI‰ÄH�½àþÿÿH�u°è-H�}�H�µàþÿÿ裙H‹½àþÿÿL�½ðþÿÿL9ÿtè›™H‹µPÿÿÿH�}�è+#èÆõÿÿH…Àt^H�=i¹ÿÿ1ÀèSëaA¿…€€H‹u°H�=6¬ÿÿ1Àè‰H‹}°L9ïtèK™dH‹%(H;EÐ…D‰øH�Ä([A\A]A^A_]ÃH�}�1öè˜p„À„ÓH�½àþÿÿH�u�è@,H�½pÿÿÿH�µàþÿÿèݘH‹½àþÿÿL9ÿtèܘH�½àþÿÿH�µpÿÿÿèÙõÿÿD‹½LÿÿÿE…ÿtTH�½àþÿÿèÑúÿÿH‹½PÿÿÿH�…`ÿÿÿH9Çt虘H‹½pÿÿÿH�E€H9Çt脘H‹}�H�E H9Ç„ÿÿÿèn˜éÿÿÿè¤ôÿÿH…À„;H�½àþÿÿèßôÿÿI‰ÇH…À„OH‹…ÿÿÿH…ÀtH‹…ÿÿÿH‰…¸þÿÿL‹e˜M…ätL‹e�H‹E°H‰…ÀþÿÿèLôÿÿH‰…ÈþÿÿH‹µ(ÿÿÿH�=M¹ÿÿ1ÀèÐH‹u°H�=û´ÿÿ1Àè¾H‹µÿÿÿH�=ö´ÿÿ1Àè©H‹u�H�=‡±ÿÿ1Àè—H�=í´ÿÿH‹µÈþÿÿ1Àè‚H�½àþÿÿèFôÿÿH�½ÐþÿÿH‰ÆèwD‰÷H‰ÞH‹•ÀþÿÿH‹�¸þÿÿM‰àL‹�ÈþÿÿAÿ×A‰Ç€½Øþÿÿ„™þÿÿH‹…ÐþÿÿH…À„‰þÿÿ1ÿÿÐé€þÿÿA¿š€€H‹u�H�=b®ÿÿ1ÀèUéoþÿÿH�½àþÿÿèäóÿÿI‰ÇH…À„DH‹…ÿÿÿH…ÀtH‹…ÿÿÿH‰…ÈþÿÿH‹µ(ÿÿÿH‹E˜H…ÀtH‹E�H‰…¸þÿÿH‹E°H‰…ÀþÿÿH�=Ò©ÿÿ1ÀèšH‹u°H�=ųÿÿ1ÀèˆH‹µÿÿÿH�=À³ÿÿ1ÀèsH‹u�H�=Q°ÿÿ1ÀèaH�½àþÿÿè%óÿÿI‰ÄH�½ÐþÿÿH‰ÆèSD‰÷H‰ÞH‹•ÀþÿÿH‹�ÈþÿÿL‹…¸þÿÿAÿ×A‰Çè-M…äuA�ÿ–€€uH…ÀtH�½ÿÿÿH�u°èšúÿÿ€½Øþÿÿ„PýÿÿH‹…ÐþÿÿH…À„@ýÿÿ1ÿÿÐé7ýÿÿH‹µ(ÿÿÿH�=ªÿÿ1ÀèA¿–€€H�½ÿÿÿH�u°èIúÿÿéýÿÿH‹µ(ÿÿÿIƒüÿtA¿„€€H�=<±ÿÿ1ÀèÓéáüÿÿH�=—°ÿÿ1ÀèpèËH�½àþÿÿèoòÿÿH…ÀtD‰÷H‰ÞÿÐA‰Çé­üÿÿA¿„€€H‹µ(ÿÿÿH�=¦±ÿÿ1Àèé�üÿÿH‰Çè‚ëdH‰ÇèxëZëXH‰Ã€½ØþÿÿtOH‹…ÐþÿÿH…ÀtC1ÿÿÐë=H‰ÇèNë0ë.ë,H‰Ã€½Øþÿÿt#H‹…ÐþÿÿH…Àt1ÿÿÐëH‰Çè"ëëëH‰ÃH�½àþÿÿèíöÿÿë ëëëëH‰ÃH‹½PÿÿÿH�…`ÿÿÿH9Çu=H‹½pÿÿÿH�E€H9ÇuBH‹}�H�E H9ÇuGH‹}°L9ïuZdH‹%(H;EÐu_H‰ßè�”èk”H‹½pÿÿÿH�E€H9Çt¾èV”H‹}�H�E H9Çt¹èD”H‹}°L9ït´ë H‰ÃH‹}°L9ït¦è(”dH‹%(H;EÐt¡èD”ÌÌÌÌUH‰åAVSI‰öH‰ûè>L‰3ÆCèâH…ÀtH‹ H…Ét H‰ÇÿÑÆC[A^]ÃÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì(H‰óA‰þdH‹%(H‰EÐèçè„ÀtrH�}°èEDH‹U°H�=µÿÿH�5¼­ÿÿ1Àè\H‹}°H�EÀH9Çtèj“E…ö~+E‰ôL�=\«ÿÿE1í„J‹4ëL‰ÿ1Àè"IÿÅM9ìuêH�=œ£ÿÿ1Àè D‰÷H‰Þèqøÿÿ‰ÃèZdH‹%(H;EÐu=‰ØHƒÄ([A\A]A^A_]ÃH‰ÃH‹}°H�EÀH9Çtèå’dH‹%(H;EÐuH‰ßèî’èù’ÌÌÌÌÌÌÌÌÌUH‰åè'“è2“ÌÌUH‰åSPH�ŽÅH‰ßè’H‹=4«H�¥¾H‰ÞHƒÄ[]é’ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åI‰ÐH‰ñ1ö1Ò]é ÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ìøM‰ÄI‰ÍH‰ÓI‰ÿdH‹%(H‰EÐ…öA¾ DEöAöÆtRIƒtKH�5õ¬ÿÿL‰ÿL‰âèH„Àt5I‹7H�=}®ÿÿ1ÀèÃI‹4$H�=¥ÿÿ1Àè±L‰ïL‰þèÖ‘³éL‰¥àþÿÿE1äH…ÛtD‰ðƒàt Hƒ{A•ÄH�EÀH‰E°HÇE¸ÆEÀH�E H‰E�HÇE˜ÆE E„ätgH‰ß1öèø‡„ÀtYH‹3H�=-­ÿÿ1Àè3L‰ïH‰ÞèX‘H�53¬ÿÿH‰ßH‹•àþÿÿè‚„À„�H‹…àþÿÿH‹0³H�=[¤ÿÿ1Àèðé.AöÆt(H�}�L‰îè)4„ÀtH‹u�I‹UH�=ø¡ÿÿ1Àè¿ëBAöÆtë'H‰ÃH‹½ÿÿÿL9ït“è…ŠH‹½0ÿÿÿL9çtètŠëH‰ÃH‹}�H�E H9Çt"ëVH‰ÃH‹½pÿÿÿH�E€H9Çu1H‹}�H�E H9Çu6H‹}°H�EÀH9Çu;dH‹%(H;EÐu@H‰ßè<ŠèŠH‹}�H�E H9ÇtÊèŠH‹}°H�EÀH9ÇtÅèó‰dH‹%(H;EÐtÀèŠÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ìøH‰µèþÿÿH‰ûdH‹%(H‰EÐH‹7H�=ñ�ÿÿ1Àèt WÀ)…ðþÿÿHÇ…ÿÿÿH�µðþÿÿH‰ßè „H‰�àþÿÿH�}€èBL‹¥ðþÿÿH‹�øþÿÿI9Ü„L�½`ÿÿÿL�­ÿÿÿëf„IƒÄ I9Ü„ÞI‹4$H�=ê™ÿÿ1ÀèñL‰ÿL‰æè¦L‰ïèžAL‰ÿL‰î1ÒèS„ÀtWH�}€L‰îè±DA‰Æ„ÀH�E€IEÅ‹H‰MˆH‹H‰E€H�}�H‰þH�… ÿÿÿHEðèÀˆE„öH�}°H‰þH�…@ÿÿÿHEð覈H‹½@ÿÿÿH�…PÿÿÿH9Çtè~ˆH‹½ ÿÿÿH�…0ÿÿÿH9ÇtèfˆH‹½`ÿÿÿH�…pÿÿÿH9Ç„ÿÿÿèJˆéÿÿÿH�½ÿÿÿèÙ@H�}€H�µÿÿÿè¹A‰ÃH‹½@ÿÿÿH�…PÿÿÿH9ÇtèˆH‹½ ÿÿÿH�…0ÿÿÿH9Çtè÷‡„Ût H‹…àþÿÿH‹0E1ÿH�=»œÿÿ1Àè éžH�½ÿÿÿH�u€èÓCH‹�àþÿÿH‹µÿÿÿH‰ßèMH‹3H�=�¦ÿÿ1Àè|H�5‡¢ÿÿH‰ßH‹•èþÿÿèÖA‰Ç„ÀtH‹…èþÿÿH‹0H�=²šÿÿ1ÀèGëH‹H�= ¥ÿÿH�5F¢ÿÿ1Àè}H‹½ÿÿÿH�… ÿÿÿH9Çtè5‡H‹}°H�EÀH9Çtè#‡H‹}�H�E H9Çtè‡H‹�ðþÿÿL‹µøþÿÿL9óuGH…ÛtH‰ßèñ†dH‹%(H;EÐ…àD‰øH�Äø[A\A]A^A_]Ãf„HƒÃL9ótH‹;HƒÃH9ßtë覆ëäH‹�ðþÿÿH…ÛuŸë¥ë9ë7H‰ÃH�½ÿÿÿèÓëWë$H‰ÃëYH‰ÃëTH‰ÃH‹½ÿÿÿH�… ÿÿÿH9Çu0ë3ëH‰Ãë,H‰ÃëH‰ÃH�½ÿÿÿè’H‹½`ÿÿÿH�…pÿÿÿH9Çtè*†H�}€èqH�½ðþÿÿè¥dH‹%(H;EÐuH‰ßè†è)†ÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒìPH‰ódH‹%(H‰EØL�eÈL‰e¸L‹7L‹M…öu L‰ðLø…(L‰}�L‰àIƒÿrH�}¸H�u�1ÒèE…H‰E¸H‹M�H‰MÈM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úè(…H‹E�H‰EÀH‹M¸ÆH�5ª¢ÿÿH�}¸èèH�5T—ÿÿH�}¸èØH�}¸èïy„ÀtL�u¨L‰u˜H‹E¸L9àt$H‰E˜H‹EÈH‰E¨ë1ÛH‹u¸H�=—ÿÿ1Àè(ë=A$AH‹EÀH‰E L‰e¸HÇEÀÆEÈH�}˜H‰Þèùúÿÿ‰ÃH‹}˜L9÷t蹄H‹}¸L9çtè«„dH‹%(H;EØ…†‰ØHƒÄP[A\A^A_]ÃdH‹%(H;EØuhH�=‘£ÿÿè…H‰ÃH‹}˜L9÷u H‹}¸L9çu3dH‹%(H;EØu8H‰ßèb„è=„H‹}¸L9çtÛë H‰ÃH‹}¸L9çtÍè!„dH‹%(H;EØtÈè=„ÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åSPH‰óH‰úH�=ßžÿÿH�5È£ÿÿH‰Ùè®S„ÀtH‹H�=ÙŸÿÿH�5»žÿÿ‰Ã1Àè0‰ØHƒÄ[]ÃÌÌÌÌÌÌÌUH‰åSPH‰ûH‹0H�C@H9Çt蕃H‹{HƒÃ HƒÄH9ßt[]é}ƒ[]ÃÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSPH‹L‹L9ût)I‰þëDHƒÃL9ûtH‹;HƒÃH9ßtëè6ƒëäI‹H…ÛtH‰ßHƒÄ[A^A_]éƒHƒÄ[A^A_]ÃÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åSPH�ßµH‰ßèk‚H‹=„›H�õ®H‰ÞHƒÄ[]é_‚ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSHƒìPdH‹%(H‰EèL�uØL‰uÈHÇEÐÆEØH�=—ÿÿH�uÈè¤Y„ÀtCH‹}Èè×W…À~6莄Àt-H�}¨èñMH‹u¨H�=6›ÿÿ1Àè/H‹}¨H�E¸H9Çtè=‚H‹}ÈL9÷tè/‚dH‹%(H;Eèu5HƒÄP[A^]ÃëëH‰ÃH‹}ÈL9÷tè‚dH‹%(H;EèuH‰ßè ‚è‚ÌÌÌÌÌÌUH‰åAWAVATSHƒìPdH‹%(H‰EØL�uÈL‰u¸HÇEÀÆEÈ‹ž´…Û…°†š´„Àt%E1ÿë�°†ˆ´AÿÇ„ÀtA÷Çÿuèè3‚ëáH‹šH‹H‰X´H�=u–ÿÿH�u¸èpX„Àt5H‹}¸H�5˜’ÿÿè ‚H…Àt%E1ÿH‰Ç1öº1ÉI‰Äè‚L‰%´ëE1ÿëA·L�e¨L‰e˜HÇE ÆE¨H�=|•ÿÿH�u˜è X‰Á¸„Ét H‹}˜è7V‰ųH‹}˜L9çtèÀƼ³H‹u¸E„ÿtH�=ð™ÿÿ1ÀèÕH‹u¸L9ötH‰÷蔀…Û”ÀdH‹ %(H;MØuTHƒÄP[A\A^A_]ÃH‰ÃëH‰ÃëH‰ÃH‹}˜L9çtèX€ÆQ³H‹}¸L9÷tèC€dH‹%(H;EØuH‰ßèL€èW€ÌÌÌÌÌÌÌUH‰åAVSH�ìÐH‰µ(ÿÿÿH‰•0ÿÿÿH‰�8ÿÿÿL‰…@ÿÿÿL‰�Hÿÿÿ„Àu5dH‹%(H‰Eèƒ=˲}^dH‹%(H;Eè…ÞH�ÄÐ[A^]Ã)…Pÿÿÿ)�`ÿÿÿ)•pÿÿÿ)]€)e�)m )u°)}ÀdH‹%(H‰Eèƒ=m²|¢H‰ûH�… ÿÿÿH‰EàH�EH‰EØH¸0H‰Eа†K²„Àt&E1öëf�°†8²AÿÆ„ÀtA÷ÆÿuèèãëáL‹5²H�UÐL‰÷H‰Þèû¿ L‰öèþÆ÷±dH‹%(H;Eè„"ÿÿÿèÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSH�ìH‰ûH‰µØþÿÿH‰•àþÿÿH‰�èþÿÿL‰…ðþÿÿL‰�øþÿÿ„Àt8)…ÿÿÿ)�ÿÿÿ)• ÿÿÿ)�0ÿÿÿ)¥@ÿÿÿ)­Pÿÿÿ)µ`ÿÿÿ)½pÿÿÿdH‹%(H‰EØH�…ÐþÿÿH‰EÐH�MH‰MÈH¹0H‰MÀH‰E°(EÀ)E H‰E�)E€E1öH�MÀ1ÿ1öH‰ÚèÿÀLcøtL‰ÿèI‰ÆH‰Ç1öL‰úèH�M€L‰÷L‰þH‰Úèæ~°†ΰ„Àt)E1ÿëD°†¸°AÿÇ„ÀtA÷Çÿuèèc~ëáfffdH‹%I‰ÇH‹€øÿÿÿH…Àt}L‰÷ÿЃ=n°tH�UÐH‰Þè[{I‹6¿ è^{ÆW­dH‹%(H;Eèu H�ÄÐ[A^]ÃègzÌÌÌÌÌÌÌUH‰åH�=ÆŽÿÿ1À]éÝþÿÿÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSH�ìÐH‰µ(ÿÿÿH‰•0ÿÿÿH‰�8ÿÿÿL‰…@ÿÿÿL‰�Hÿÿÿ„Àu5dH‹%(H‰Eèƒ=»¬}^dH‹%(H;Eè…ÞH�ÄÐ[A^]Ã)…Pÿÿÿ)�`ÿÿÿ)•pÿÿÿ)]€)e�)m )u°)}ÀdH‹%(H‰Eèƒ=]¬|¢H‰ûH�… ÿÿÿH‰EàH�EH‰EØH¸0H‰Eа†;¬„Àt&E1öëf�°†(¬AÿÆ„ÀtA÷ÆÿuèèÓyëáL‹5¬H�UÐL‰÷H‰Þèëy¿ L‰öèîyÆç«dH‹%(H;Eè„"ÿÿÿèÿxÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åSPHƒ=ª«t@°†¨«„Àt!1Ûë°†˜«ÿÄÀt÷ÃÿuêèEyëãH‹=t«èÇyÆp«H‹‘H‹8è±yH‹‘H‹8HƒÄ[]éœyÌÌÌÌÌÌÌÌÌÌÌÌUH‰åSPH‰ûfffdH‹%H‹ˆøÿÿÿH‰˜øÿÿÿH‰ÈHƒÄ[]ÃÌÌÌUH‰åfffdH‹%H‹€øÿÿÿ]ÃÌÌÌÌÌÌÌUH‰åSPH�ÓªH‰ßè[wH‹=t�H�å£H‰ÞHƒÄ[]éOwÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì8H‰ÓI‰ödH‹%(H‰EÐL�mÀL‰m°L‹?L‹gM…ÿu L‰øLà…·L‰e¨L‰èIƒürH�}°H�u¨1ÒèwH‰E°H‹M¨H‰MÀM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âèãvH‹E¨H‰E¸H‹M°ÆH�}°L‰öè§H�}°è¾kA‰ÆH…Û•ÀD„ðt H�u°H‰ßèôvH‹}°L9ïtèÖvdH‹%(H;EÐuUD‰ðHƒÄ8[A\A]A^A_]ÃdH‹%(H;EÐu4H�=½•ÿÿè:wH‰ÃH‹}°L9ïtè‰vdH‹%(H;EÐuH‰ßè’vè�vÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì8dH‹%(H‰EÐL�mÀL‰m°H…ö„PH‰óI‰þH‰÷èÃuI‰ÇH‰E¨L‰èIƒÿvH�}°H�u¨1Òè´uH‰E°H‹M¨H‰MÀM…ÿtIƒÿu¶ ˆëH‰ÇH‰ÞL‰úè˜uH‹E¨H‰E¸H‹M°ÆH�}°è?JA‰ÄH‹}°L9ïtè®uM‹~E„ät H‰ßè=uL‰÷1öL‰úH‰ÙI‰Àèjué†M…ÿtVI‹B€|8ÿ/tNM�gI�V¹H9ÐtI‹NI9ÌvA¸L‰÷L‰þ1Ò1Éè¥vI‹BÆ8/M‰fI‹BÆD8M‹~ëE1ÿH‰ßè¿tH¹ÿÿÿÿÿÿÿL1ùH9ÁrGL‰÷H‰ÞH‰Âè�udH‹%(H;EÐumHƒÄ8[A\A]A^A_]ÃdH‹%(H;EÐuOH�=é“ÿÿèfudH‹%(H;EÐu4H�=v‘ÿÿè[uH‰ÃH‹}°L9ïtèštdH‹%(H;EÐuH‰ßè£tè®tÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSHƒì8dH‹%(H‰EàL�}ÐL‰}ÀH‹L‹wH…Ûu H‰ØLð…¯L‰u¸L‰øIƒþrH�}ÀH�u¸1ÒèÊsH‰EÀH‹M¸H‰MÐM…ötIƒþu¶ ˆëH‰ÇH‰ÞL‰òè®sH‹E¸H‰EÈH‹MÀÆH�5V�ÿÿH�}ÀènýÿÿH‹uÀH�=‡ÿÿ1Àè,÷ÿÿH�}ÀèshH‹}ÀL9ÿt ‰Ãè£s‰ØdH‹ %(H;MàuNHƒÄ8[A^A_]ÃdH‹%(H;Eàu4H�=�’ÿÿè tH‰ÃH‹}ÀL9ÿtè[sdH‹%(H;EàuH‰ßèdsèosÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌH�BÿH;Gs%UH‰åH�+L�4„ÉLEÀH‹?AÿÐ…À”À]Ã1ÀÃÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åHcÒ]éStÌÌÌUH‰åHcÒ]éStÌÌÌH‰øH‹H9×s1ÀÃUH‰åL�&L� /„ÉMEÈH8H)×AÿÑ…À”À]ÃÌÌÌÌÌÌÌÌÌUH‰å]étÌÌÌÌÌÌUH‰å]étÌÌÌÌÌÌUH‰åAWAVSHƒìH‰ûdH‹%(H‰EàH�GH‰L‹6L‹~M…öuL‰ñLùupL‰}ØIƒÿrH�uØH‰ß1ÒèÐqH‰H‹MØH‰KM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úè´qH‹EØH‰CH‹ ÆdH‹%(H;Eàu)H‰ØHƒÄ[A^A_]ÃdH‹%(H;Eàu H�=Ð�ÿÿèMrèØqÌÌÌÌÌÌÌÌUH‰åAWAVSHƒìH‰ûdH‹%(H‰EàHƒ~„ I‰÷H�5~‹ÿÿ¹L‰ÿHÇÂÿÿÿÿè sI‰ÆL‰ÿ¾.HÇÂÿÿÿÿèsIƒþÿt}I9ÆvxH�CH‰M‹7M‹M…öu L‰ñLù…L‰}ØIƒÿrH�uØH‰ß1Òè±pH‰H‹MØH‰KM…ÿ„¨Iƒÿ…�A¶ˆé“H�CH‰HÇCÆCéŠI‹OH�{H‰;M‹7H9ÁHBÁM…öu L‰ñHÁ…�H‰EØHƒør"H�uØH‰ß1ÒI‰Çè/pH‰ÇL‰øH‰;H‹MØH‰KH…Àt$HƒøuA¶ˆëL‰öH‰Âë H‰ÇL‰öL‰úèpH‹EØH‰CH‹ ÆdH‹%(H;Eàu)H‰ØHƒÄ[A^A_]ÃdH‹%(H;Eàu H�=!�ÿÿèžpè)pÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒìH‰ûdH‹%(H‰EÐHƒ~„»I‰ôH�5ʉÿÿIÇÅÿÿÿÿ¹L‰çHÇÂÿÿÿÿèoqI‰ÆL‰ç¾.HÇÂÿÿÿÿèHqIÿÆI‹L$H‰ÊL)ò‚¾I‰ÇM)÷MBýHƒøÿLDøH�CH‰M4$L9úLBúM…öu L‰ñLù…°L‰}ÈIƒÿrH�uÈH‰ß1Òè×nH‰H‹MÈH‰KM…ÿt1IƒÿuA¶ˆë#H�CH‰HÇCÆCëH‰ÇL‰öL‰úè¦nH‹EÈH‰CH‹ ÆdH‹%(H;EÐuTH‰ØHƒÄ[A\A]A^A_]ÃdH‹%(H;EÐu3H�=€ÿÿH�5¼†ÿÿL‰ò1ÀèopdH‹%(H;EÐu H�=—�ÿÿèoèŸnÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSHƒìH‰ûdH‹%(H‰EàHƒ~„†I‰÷H‰÷¾/HÇÂÿÿÿÿèáoHƒøÿt~H�PM‹wI9ƆH�CH‰I‹I‰ÏI×u Lñ…èI)ÖL‰uØIƒþrH�uØH‰ß1Òè„mH‰H‹MØH‰KM…ö„�IƒþunA¶ˆëH�CH‰HÇCÆCëyH�CH‰M‹7M‹M…öuL‰ñLùu{L‰}ØIƒÿrH�uØH‰ß1ÒèmH‰H‹MØH‰KM…ÿt'IƒÿuA¶ˆëH‰ÇL‰þL‰òë H‰ÇL‰öL‰úèólH‹EØH‰CH‹ ÆdH‹%(H;EàuPH‰ØHƒÄ[A^A_]ÃdH‹%(H;Eàu3H�=ŒÿÿèŒmdH‹%(H;EàuH�=O~ÿÿH�5ò„ÿÿL‰ñ1Àè¥nèðlUH‰åAWAVAUATSHƒìXH‰ûdH‹%(H‰EÐL�eÀL‰e°L‹6L‹~M…öu L‰ðLø…ƒL‰}�L‰àIƒÿrH�}°H�u�1ÒèlH‰E°H‹M�H‰MÀM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úèökH‹E�H‰E¸H‹M°ÆL�u°f�H‹u¸H…ötH‹E°€|0ÿ/uHÿκL‰÷èìmëÚH�}°¾/HÇÂÿÿÿÿè¥mHƒøÿ„‹L‹u°…Àx‰ÁA€</u�Hÿ…À‰Èî¸ÿÿÿÿLcøIÿÇH‹E¸L�m L‰m�L9øLBøM…öu L‰ðLø…²L‰}ˆL‰èIƒÿrH�}�H�uˆ1Òè#kH‰E�H‹MˆH‰M M…ÿ„€IƒÿulA¶ˆërL�kL‰+L‹u°L‹}¸M…öu L‰ðLø…nL‰}�L‰èIƒÿrH�u�H‰ß1ÒèÅjH‰H‹M�H‰KM…ÿ„ªIƒÿ…’A¶ˆé•H‰ÇL‰öL‰úèžjH‹EˆH‰E˜H‹M�ÆH‹u˜H�}�¹1ÒA¸/è´lH�SH‰H‹0H‰ÁHƒÁH9Ît H‰3H‹H‰SëH‹PH‰SH‰HÇ@Æ@H‹}�L9ït=ègjë6H‰ÇL‰öL‰úèjH‹E�H‰CH‹ ÆH‹s¹H‰ß1ÒA¸/è/lH‹}°L9çtè!jdH‹%(H;EÐ…±H‰ØHƒÄX[A\A]A^A_]ÃdH‹%(H;EÐ…ŒH�=‰ÿÿè}jdH‹%(H;EÐuqH�=åˆÿÿèbjdH‹%(H;EÐuVH�=ʈÿÿèGjI‰ÆH‹;L9ïtëëI‰ÆH‹}�L9ït è‡iëI‰ÆH‹}°L9çtètidH‹%(H;EÐuL‰÷è}ièˆiH‰Çè�ÖÿÿUH‰åH‹H‹w€|0ÿ/u Hÿκè!k]ÃH‰ÇègÖÿÿÌÌÌÌÌÌÌUH‰åAWAVSP‰ÓA‰÷I‰þ1ÒègiHƒøÿt+E¾ÿffff.„I‹ˆL‰÷D‰þH‰ÂèIƒÆL9÷tëè–aëäL‹µpÿÿÿM…öH�]Àuœë¢dH‹%(H;EÐ…¤H�=†€ÿÿèbdH‹%(H;EÐ……H�=g€ÿÿèäadH‹%(H;EÐujH�=L€ÿÿèÉaH‰ÇèaÎÿÿë I‰Æë&ëëI‰ÆëI‰ÆH‹}�H9ßtèaH�½pÿÿÿè„ÝÿÿH‹}°H�EÀH9Çtèâ`dH‹%(H;EÐuL‰÷èë`èö`ÌÌÌÌÌÌUH‰åAWAVATSHƒì0I‰÷I‰þdH‹%(H‰EØHÇFH‹ÆL�eÈL‰e¸HÇEÀÆEÈH�u¸è“7„ÀtH�}¸1öèä7„Àt³H�u¸L‰ÿèb`ë1Ûë1ÛH‹U¸H�=€zÿÿL‰ö1Àè¥ãÿÿH‹}¸L9çtè'`dH‹%(H;EØu7‰ØHƒÄ0[A\A^A_]ÃH‰ÃH‹}¸L9çtèø_dH‹%(H;EØuH‰ßè`è `ÌÌÌÌÌÌÌÌÌÌÌÌUH‰åH‰òH�5îoÿÿ¹ ]éÇaÌÌÌÌÌÌÌUH‰åAWAVSHƒìdH‹%(H‰EàHƒtWH‰óI‰ÿH�5®oÿÿE1ö¹ 1ÒèƒaHƒøÿu8M‹?è…aI‰ÆÇH�uØL‰ÿº è{aL9}Øt-Aƒ>"tB‰A¶ëE1ödH‹%(H;EàuDD‰ðHƒÄ[A^A_]ÃdH‹%(H;Eàu'H�=v~ÿÿè=adH‹%(H;Eàu H�=[~ÿÿè2aè _ÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSHƒì0H‰ûdH‹%(H‰EèHcÆH� »tH‹4ÁL�uÈL‰÷è»H� ÇsÿÿA¸ L‰÷1ö1Òèb^H�SH‰H‹0H‰ÁHƒÁH9Ît H‰3H‹H‰SëH‹PH‰SH‰HÇ@Æ@H‹}ÈH�EØH9Çtè1^dH‹%(H;Eèu8H‰ØHƒÄ0[A^]ÃH‰ÃH‹}ÈH�EØH9Çtè^dH‹%(H;EèuH‰ßè ^è^ÌÌÌÌÌUH‰åAWAVATSHƒìdH‹%(H‰EØL�gL‰'H…ö„¹I‰öH‰ûH‰÷èF]I‰ÇH‰EÐHƒøvH�uÐH‰ß1Òè;]I‰ÄH‰H‹EÐH‰CM…ÿtIƒÿu A¶Aˆ$ëL‰çL‰öL‰úè]H‹EÐH‰CH‹ ÆL‹sM…öt(L‹;E1äfff.„C¾<'è†_Cˆ'IÿÄM9æuêdH‹%(H;EØu+H‰ØHƒÄ[A\A^A_]ÃdH‹%(H;EØu H�=|ÿÿè€]è ]ÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒìPH‰óI‰þdH‹%(H‰EØL�}ÈL‰}¸ÇEÈx64HÇEÀ¿xèí^ˆEÈ¿6èà^ˆEÉ¿4èÓ^ˆEÊH� œqÿÿH�}¸A¸ 1ö1Òè6\L�e¨L‰e˜H‹H‰ÁHƒÁH9Êt H‰U˜H‹H‰U¨ëA$H‹PH‰U H‰HÇ@Æ@H‹}¸L9ÿtè\H�u˜L‰÷èé[H‹}˜L9çtèë[I‹>H‰Þè ûÿÿ‰Á°„Éu&I‹VH� ÂtÿÿA¸ L‰÷1öè�[I‹>H‰ÞèòúÿÿdH‹ %(H;MØu5HƒÄP[A\A^A_]ÃH‰ÃH‹}¸L9ÿtè…[dH‹%(H;EØuH‰ßèŽ[è™[ÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0I‰öH‰ûdH‹%(H‰EØL�L‰?HÇGÆGH�}¸H‰Öè¿ìÿÿI‹FH‹MÀH�4HƒÆH‰ßèw]I‹6I‹VH‰ßèˆ[I‹FH…ÀtRI‹€|ÿ/tHH‹L‹sM�f¹L9øtH‹KI9ÌvA¸H‰ßL‰ö1Ò1Éè\H‹BÆ0/L‰cH‹BÆD0H�58oÿÿH�}¸¹HÇÂÿÿÿÿèQ\H‹u¸H‹MÀH9ÁHBÁH‰ßH‰Âè÷ZH¸öÿÿÿÿÿÿH9C}IH�5—zÿÿº H‰ßèÓZH‹}¸H�EÈH9Çtè1ZdH‹%(H;EØ…ˆH‰ØHƒÄ0[A\A^A_]ÃdH‹%(H;EØuiH�=¾vÿÿè£ZI‰ÆH‹;L9ÿtëL‹fM…ÿu L‰øLà…¬L‰e°L‰èIƒürH�u°H‰ß1ÒèâXH‰H‹M°H‰KM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âèÆXH‹E°H‰CH‹ ÆH�DoÿÿH�}°L‰öèÄH‹u°H‰ßèxâÿÿH‹}°H�EÀH9ÇtèÆXdH‹%(H;EÐ…ŠH‰ØHƒÄ([A\A]A^A_]ÃdH‹%(H;EÐuiH�=©wÿÿè&YI‰ÆH‹}°H�EÀH9ÇuH‹;L9ïu1dH‹%(H;EÐu6L‰÷èwXèRXH‹;L9ïtÜë I‰ÆH‹;L9ïtÏè8XdH‹%(H;EÐtÊèTXÌÌÌÌUH‰åAWAVAUATSHƒìI‰ÖH‰ûdH‹%(H‰EÐL�oL‰/L‹>L‹fM…ÿu L‰øLà… L‰eÈL‰èIƒürH�uÈH‰ß1ÒèrWH‰H‹MÈH‰KM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âèVWH‹EÈH‰CH‹ ÆL‰÷èWH¹ÿÿÿÿÿÿÿH3KH9ÁrJH‰ßL‰öH‰ÂèîWdH‹%(H;EÐuoH‰ØHƒÄ[A\A]A^A_]ÃdH‹%(H;EÐuNH�=EvÿÿèÂWdH‹%(H;EÐu3H�=Òsÿÿè·WI‰ÆH‹;L9ïtè÷VdH‹%(H;EÐuL‰÷èWè WÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì(I‰ÖH‰ûdH‹%(H‰EÐL�oL‰/L‹>L‹fM…ÿu L‰øLà…¬L‰e°L‰èIƒürH�u°H‰ß1Òè"VH‰H‹M°H‰KM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âèVH‹E°H‰CH‹ ÆH�!lÿÿH�}°L‰öèþÿÿH‹u°H‰ßè¸ßÿÿH‹}°H�EÀH9ÇtèVdH‹%(H;EÐ…ŠH‰ØHƒÄ([A\A]A^A_]ÃdH‹%(H;EÐuiH�=étÿÿèfVI‰ÆH‹}°H�EÀH9ÇuH‹;L9ïu1dH‹%(H;EÐu6L‰÷è·Uè’UH‹;L9ïtÜë I‰ÆH‹;L9ïtÏèxUdH‹%(H;EÐtÊè”UÌÌÌÌUH‰åAWAVAUATSHƒì(H‰ËI‰ÖI‰÷I‰üdH‹%(H‰EÐL�m°L‰ïL‰æL‰úè”ûÿÿL‰÷L‰îè UH‹}°H�EÀH9ÇtèUL‰ïL‰æL‰úè)þÿÿH‰ßL‰îèÞTH‹}°H�EÀH9ÇtèÜTI‹6H‹H�=(fÿÿ1Àè8ØÿÿdH‹%(H;EÐuHƒÄ([A\A]A^A_]ÃèÕTÌÌÌÌÌUH‰åAVSHƒìpH‰ûdH‹%(H‰EèL�uÈL‰÷è¹çÿÿL‰÷èàÿÿ„Àt!H�CH‰H‹MÈH�UØH9ÑtkH‰ H‹EØH‰CëdH�}¨H�uÈèçÿÿH�}ˆH�u¨èrçÿÿH�uˆH‰ßèfçÿÿH‹}ˆH�E˜H9ÇtèTH‹}¨H�E¸H9ÇtèTH‹}ÈH�EØH9ÇtèðSëH‹EÐH‰CdH‹%(H;Eè…«H‰ØHƒÄp[A^]ÃH‰ÃH‹}ˆH�E˜H9Çu1H‹}¨H�E¸H9ÇuHH‹}ÈH�EØH9ÇuadH‹%(H;EèufH‰ßè Sè{SH‹}¨H�E¸H9ÇtÊëH‰ÃH‹}¨H�E¸H9Çt¸èWSH‹}ÈH�EØH9Çt³ëëH‰ÃH‹}ÈH�EØH9ÇtŸè1SdH‹%(H;EètšèMSÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒìHI‰ÖI‰÷H‰ûdH‹%(H‰EÐI½üÿÿÿÿÿÿL�gL‰'HÇE°%H�u°1ÒèuRH‰H‹M°H‰KHºplaunch?H‰Pƒtÿÿ@htÿÿH‰KÆM…ÿ„£A€?„™I�EùH9C��H�5uÿÿº H‰ßèêRL‰ÿèòQL‰éH+KHƒÁH9Á‚]H‰ßL‰þH‰ÂèÀRM…ötmA€>tgI�EðH9C�6H�5PgÿÿºH‰ßè“RL‰÷è›QH‰ÂL‰èH+CHƒÀH9ÐséI�EïH9C�ôºL�5RiÿÿH‰ßL‰öèNRI�EýH9C�°H�5ÛmÿÿºH‰ßè,RL9k�’H�5ºfÿÿºH‰ßèRI�EþH9C�pH�5ŽoÿÿºH‰ßèìQL�}ÀL‰}°HÇE¸ÆEÀH�=¸hÿÿH�u°èX(L�u L‰u�„ÀtH‹u°L9þtDH‰u�H‹EÀH‰E ëAH¸linux-x6I‰AÆF4HÇE˜ ÆE©H‹}°L9ÿt$èéPH‹u�H‹U˜ëAAL‰öH‹U¸H‰U˜ëº L‰öH‰ßèJQH‹}�L9÷tè¬PH�}°ès7Hƒ}¸u4L‰u�ÇE linufÇE¤xHÇE˜H�}°H�u�èfPH‹}�L9÷tèhPH‹CHƒàüL9è„‘H�5{nÿÿºH‰ßèÓPH‹u°H‹U¸H‰ßèÃPH‹}°L9ÿtè%PdH‹%(H;EÐ…µH‰ØHƒÄH[A\A]A^A_]ÃdH‹%(H;EÐ…�H�=¬lÿÿè‘PdH‹%(H;EÐuuH�=‘lÿÿèvPdH‹%(H;EÐuZH�=vlÿÿè[Pë%I‰ÅH‹}�L9÷uëëI‰ÅH‹}°L9ÿt èˆOëëI‰ÅH‹;L9çtètOdH‹%(H;EÐuL‰ïè}OèˆOÌÌÌÌÌÌÌÌUH‰åAWAVSPH‰ûL�L‰?ÇG9.0.fÇG5HÇGH�=œ�¾@è–QH…Àt@I‰ÆH�5ù_ÿÿºH‰ßèŠOL‰÷è’NH¹ÿÿÿÿÿÿÿH3KH9ÁrH‰ßL‰öH‰ÂèaOH‰ØHƒÄ[A^A_]ÃH�=‚kÿÿègOI‰ÆH‹;L9ÿtè§NL‰÷è¿NÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒìdH‹%(H‰EØL�gL‰'H…ö„¹I‰öH‰ûH‰÷èöMI‰ÇH‰EÐHƒøvH�uÐH‰ß1ÒèëMI‰ÄH‰H‹EÐH‰CM…ÿtIƒÿu A¶Aˆ$ëL‰çL‰öL‰úèÊMH‹EÐH‰CH‹ ÆL‹sM…öt(L‹;E1äfff.„C¾<'èfPCˆ'IÿÄM9æuêdH‹%(H;EØu+H‰ØHƒÄ[A\A^A_]ÃdH‹%(H;EØu H�=³lÿÿè0Nè»MÌÌÌÌÌÌÌÌÌÌÌUH‰å¶5€I$H9ÂHBÂH…Ûu H‰ÙHÁ… H‰EÐHƒør"H�uÐL‰÷1ÒI‰ÇèAH‰ÇL‰øI‰>H‹MÐI‰NH…À„‚Hƒøuf¶ˆëuI‹L$I‰ÏI)ß‚�I�FI‰I‹$HÓu HÊ…�L‰}ÐIƒÿrH�uÐL‰÷1Òè­@I‰H‹MÐI‰NM…ÿt#Iƒÿu¶ ˆëH‰ÞH‰Âë H‰ÇH‰ÞL‰úèŠ@H‹EÐI‰FI‹ÆdH‹%(H;EØuOHƒÄ[A\A^A_]ÃdH‹%(H;EØu3H�=RÿÿH�5¥XÿÿH‰Ú1ÀèXBdH‹%(H;EØu H�=€_ÿÿèý@èˆ@ÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ì¸A‰ÕI‰÷I‰þdH‹%(H‰EÐE1ä¾.1Òèw@Hƒøÿ„bH‰ÃD‰­0ÿÿÿL‰½ ÿÿÿÇ…<ÿÿÿM‹fL�}�L‰}€M‹.I9ÄLCàM…íu L‰èLà…½L‰¥`ÿÿÿL‰øIƒür!H�}€H�µ`ÿÿÿ1ÒèY?H‰E€H‹�`ÿÿÿH‰M�M…ätIƒüu A¶MˆëH‰ÇL‰îL‰âè8?H‹…`ÿÿÿH‰EˆH‹M€ÆH�}€H�µ<ÿÿÿè¥ßÿÿA‰ÄH‹}€L9ÿtèD?E„ä„�Hƒûr I‹€80„{L�kL‰÷¾.L‰êèu?Hƒøÿ„]Ç…8ÿÿÿM‹fI9܆H‰…(ÿÿÿI‰ÇM)ïH�M�H‰M€I‹LëM)ìM9üMCçH…Ûu H‰ØLà…°L‰¥`ÿÿÿH‰ÈIƒür!H�}€H�µ`ÿÿÿ1ÒèL>H‰E€H‹�`ÿÿÿH‰M�M…ätIƒüu¶ ˆëH‰ÇH‰ÞL‰âè->H‹…`ÿÿÿH‰EˆH‹M€ÆH�}€H�µ8ÿÿÿèšÞÿÿ‰ÃH‹}€H�E�H9Çtè6>„Û„€IƒÿrI‹B€<(0„lÇ…4ÿÿÿL‹½(ÿÿÿM�oL‰÷L‰îè*ÞÿÿHƒøÿ„t€½0ÿÿÿ…5I‰ÄH‰ÃL)ëL�}€L‰ÿL‰öL‰êH‰Ùè•@H�µ4ÿÿÿL‰ÿèÞÿÿA‰ÇH‹}€H�E�H9Çtè¡=E„ÿ„êHƒûrI‹B€<(0„ÖL‰÷¾+M‰çL‰âèÑ=H‰ÃH‰ÁL)áHƒøÿHDÈL�¥`ÿÿÿL‰çL‰öL‰úè@L‰çè“„ÀL‹¥ ÿÿÿ„ìL�½PÿÿÿL‰½@ÿÿÿHÇ…HÿÿÿÆ…PÿÿÿHƒûÿtLH�}€L‰öH‰ÚHÇÁÿÿÿÿèÈ?H�½@ÿÿÿH�u€èØ<H‹}€H�E�H9ÇtèÖ<H�½@ÿÿÿè„À„‹µ<ÿÿÿ‹•8ÿÿÿ‹�4ÿÿÿH�}€L�…`ÿÿÿL��@ÿÿÿèùñÿÿH�u€L‰çè�H�}€èÔ¸ÿÿA´é<I‹^L9û†®L�e�L‰e€I‹I‰ÇMïu HØ…EL)ëH‰�`ÿÿÿL‰àHƒûr!H�}€H�µ`ÿÿÿ1ÒèÞ;H‰E€H‹�`ÿÿÿH‰M�H…ÛtHƒûuA¶ˆëH‰ÇL‰þH‰Úè¾;H‹…`ÿÿÿH‰EˆH‹M€ÆH�}€H�µ4ÿÿÿè+Üÿÿ‰ÃH‹}€L9çL‹½(ÿÿÿtèÄ;„ÛtI‹B€|8t2B€<(0u+E1ädH‹%(H;EÐ…�D‰àH�ĸ[A\A]A^A_]˵<ÿÿÿ‹•8ÿÿÿ‹�4ÿÿÿH�]€H‰ßè%óÿÿH‹½ ÿÿÿH‰ÞèfH�}€è�·ÿÿA´ë�E1äëE1äH‹½@ÿÿÿL9ÿtè/;H‹½`ÿÿÿH�…pÿÿÿH9Ç„mÿÿÿè;écÿÿÿdH‹%(H;EÐ…H�=Zÿÿè�;dH‹%(H;EÐ…áH�=NLÿÿH�5ñRÿÿL‰êL‰á1Àè¡<dH‹%(H;EÐ…³H�= LÿÿH�5ÃRÿÿL‰êH‰Ù1Àès<ëëH‰ÃH�}€èÓ¶ÿÿëH‰ÃH‹½@ÿÿÿL9ÿt èm:ëH‰ÃH‹½`ÿÿÿH�…pÿÿÿH9Çu>ëAH‰ÃH�}€è—¶ÿÿë3ëH‰ÃH‹}€L9çu ë#H‰ÃH‹}€H�E�H9ÇuëH‰ÃH‹}€L9ÿtè:dH‹%(H;EÐuH‰ßè:è&:ÌÌÌÌÌÌUH‰åAVSH‰óI‰þ‹F‰GH‹H‰HƒÇHƒÆèº9I�~0HƒÃ0H‰Þèª9L‰ð[A^]ÃÌÌUH‰åAWAVAUATSHƒì8dH‹%(H‰EÐHƒ„)H‰ûH‹¶ƒø-t ƒø+…‰E¤A½ºH‰ß¾.èµ9Hƒøÿ„ˆI‰ÄL�}À@H‹KH‰ÈL)è‚ÀM‰æM)îL‰}°L+L9ðLBðM…íu L‰èLð…ÂL‰u¨L‰øIƒþrH�}°H�u¨1ÒèŸ8H‰E°H‹M¨H‰MÀM…öt,IƒþuA¶Mˆëffffff.„H‰ÇL‰îL‰òèr8H‹E¨H‰E¸H‹M°ÆHƒ}¸„—H‹E°¶„É„xH�P‰Îë@€þ9wz¶2HÿÂ@„öt-@€þA|@€þ[ré@€Æ…@€þæsßëW€@€þ/Ê@€þ-tÊëB€}¤+t,€ù0u'€xt!¾H�}°èSØÿÿHƒøÿtffff.„A¶H‹}°L9ÿuëf�E1öH‹}°L9ÿtèï7E„ö„�M‰åIÿÅH‰ß¾.L‰êè08I‰ÄHƒøÿ…ƒþÿÿH‹KI‰ÎM)î‚CL�}ÀL‰}°H‹IÅu HÈ…QL‰u¨L‰øIƒþrH�}°H�u¨1Òè.7H‰E°H‹M¨H‰MÀM…öt+IƒþuA¶Mˆë³éÇ1ÛéÀH‰ÇL‰îL‰òè7H‹E¨H‰E¸H‹M°ÆHƒ}¸„‡H‹E°¶„ÉtoH�P‰Îë@@€þ9wj¶2HÿÂ@„öt-@€þA|@€þ[ré@€Æ…@€þæsßëG€@€þ/Ê@€þ-tÊë2€}¤+t€ù0u€xtH�}°¾èãÖÿÿHƒøÿt ³H‹}°L9ÿu ë1ÛH‹}°L9ÿtè�6dH‹%(H;EÐu}‰ØHƒÄ8[A\A]A^A_]ÃdH‹%(H;EÐu]H�=ÓGÿÿH�5vNÿÿL‰ê1Àè)8dH‹%(H;EÐu6H�=QUÿÿèÎ6ëH‰ÃH‹}°L9ÿtè6dH‹%(H;EÐuH‰ßè$6è/6ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰å]é–õÿÿÌÌÌÌÌÌUH‰åAWAVATSH�ìàI‰òH‰ûL‰… ÿÿÿL‰�(ÿÿÿ„Àt/)…0ÿÿÿ)�@ÿÿÿ)•Pÿÿÿ)�`ÿÿÿ)¥pÿÿÿ)m€)u�)} dH‹%(H‰EØI‰æH�BHƒàðI)ÆL‰ôH�…ÿÿÿH‰EÐH�EH‰EÈH¸ 0H‰EÀH�EÀL‰÷H‰ÖH‰ÊH‰ÁAÿÒA‰ÄLcøH�CH‰L‰}¸AƒÿrH�u¸H‰ß1ÒèÀ4H‰H‹M¸H‰KE…ätAƒüuA¶ˆëH‰ÇL‰öL‰úè¤4H‹E¸H‰CH‹ ÆdH‹%(H;EØuH‰ØH�eà[A\A^A_]Ãèá4ÌUH‰åSPH�¥gH‰ßè4H‹=4MH�¥`H‰ÞHƒÄ[]é4ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSH�ìˆH‰ûdH‹%(H‰Eà1ÿè)7H‰…`ÿÿÿH�½`ÿÿÿè&7H�BNÿÿL�µpÿÿÿ¾dL‰÷H‰Áè7L�{L‰;L‰÷è©3I‰ÆH‰…hÿÿÿHƒør"H�µhÿÿÿH‰ß1Òè˜3I‰ÇH‰H‹…hÿÿÿH‰CM…öt$Iƒþu ¶…pÿÿÿAˆëH�µpÿÿÿL‰ÿL‰òèn3H‹…hÿÿÿH‰CH‹ ÆdH‹%(H;EàuH‰ØH�Ĉ[A^A_]Ãè§3ÌÌÌÌÌÌÌUH‰åAVSI‰þH‹?¾Àº$1Àèb6‰Ãƒøÿt ‰ßèd6ëI‹6H�=ÊVÿÿH�2Lÿÿ1Àè¹ÿÿƒûÿ•À[A^]ÃÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åº¹]é ÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ì˜A‰ÏA‰ÔI‰õI‰þdH‹%(H‰EÐH‹?1ö1ÀèÇ5ƒøÿt<‰ÃH�•@ÿÿÿ¿‰ÆèÍ5…ÀtCL�%QMÿÿM‹6èª4‹E1ÿL‰çL‰ö1À訳ÿÿëLI‹èŽ4‹H�=‚MÿÿE1ÿH‰Þ1À舳ÿÿë3H‹µpÿÿÿM…ítI‰u1ÿD‰âD‰ùA‰ØE1Éès5I‰ÇHƒøÿt+‰ßèC5dH‹%(H;EÐu!L‰øH�Ę[A\A]A^A_]ÃL�%iJÿÿéaÿÿÿè2ÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åº¹]éÜþÿÿÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0I‰þdH‹%(H‰EØHÇGH‹Æ1ÿ1öèÚ4H‰ÃH…Àt,M‹~H‰ßè1L‰÷1öL‰úH‰ÙI‰ÀèC1H‰ßè»4é­èq3‹8ƒÿ„�è±4L�eÈL‰e¸H…À„©I‰ÆH‰ÇèÅ0I‰ÇH‰E°L‰àIƒÿvH�}¸H�u°1Òè¶0H‰E¸H‹M°H‰MÈM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úè™0H‹E°H‰EÀH‹M¸ÆH‹u¸H�=5Nÿÿ1Àèç±ÿÿH‹}¸L9çtè©0H…Û•ÀdH‹ %(H;MØuPHƒÄ0[A\A^A_]ÃdH‹%(H;EØu4H�=�Oÿÿè 1H‰ÃH‹}¸L9çtè[0dH‹%(H;EØuH‰ßèd0èo0ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ìhI‰ÌI‰ÖH‰óI‰ÿdH‹%(H‰EÐL�­ ïÿÿL‰­�ïÿÿHÇ…˜ïÿÿÆ… ïÿÿè~/H…Àˆ‡L‰µxïÿÿH�½�ïÿÿL‰þH‰ÂèL0H‹½�ïÿÿ¾è+3H…À„ƒI‰ÆH‰ÇH‰Þè$3H…ÀuI‰Çè'3H�=tQÿÿH‰ÞH‰Â1ÀèS¯ÿÿL‰øH�µ°ïÿÿH‰Çè3…ÀA•Ç„œH‹…xïÿÿL‰0H‹�°ïÿÿM‹t$H‰ßè×.L‰ç1öL‰òH‰ÙI‰Àè/é†L�µÀïÿÿL‰µ°ïÿÿM…ÿ„ÕL‰ÿè .H‰ÃH‰E°L‰ðHƒûv$H�½°ïÿÿH�u°1ÒèŽ.H‰…°ïÿÿH‹M°H‰�ÀïÿÿH…Ût)HƒûuA¶ˆëL‰÷èq2éH‰ÇL‰þH‰Úè^.H‹E°H‰…¸ïÿÿH‹�°ïÿÿÆH‹½°ïÿÿ¶L9÷tèt.€û/„�L‰¥pïÿÿHÇ…ˆïÿÿHÇ…€ïÿÿH��ÀïÿÿH‰�°ïÿÿH¸/proc/seH‰…ÀïÿÿH¸elf/mapsH‰…ÇïÿÿHÇ…¸ïÿÿÆ…ÏïÿÿH�5óJÿÿH‰ßèÍ.I‰ÄH‹½°ïÿÿH9ßtèé-M…ä„H�EÀH‰E°HÇE¸ÆEÀH��°ïÿÿL�m°�H�½ˆïÿÿH�µ€ïÿÿº L‰áèu1Hƒøÿ„ÒH‹½ˆïÿÿH�5þBÿÿH‰Ú1Àèc1ƒøu¾L‹u¸H‰ßè-L‰ï1öL‰òH‰ÙI‰Àè?-L‰ï¾/HÇÂÿÿÿÿè /Hƒøÿt…I‰ÆL‰ÿèÚ,L‰ïL‰þL‰òH‰Áè1Hƒøÿ„_ÿÿÿL‰çè1H‹½ˆïÿÿèk0H‹}°¾è}0H…ÀL�­ ïÿÿ„�H‹�xïÿÿH‰A·H�u°H‹½pïÿÿèà,ë#E1ÿë0L‰çèÁ0H‹½ˆïÿÿè0E1ÿL�­ ïÿÿH‹}°H�EÀH9Çtè™,H‹½�ïÿÿL9ïtèˆ,dH‹%(H;EÐ…“D‰øH�Äh[A\A]A^A_]ÃE1ÿH‹}°H�EÀH9Çu²ëµdH‹%(H;EÐu]H�=þHÿÿèã,dH‹%(H;EÐuBH�=;Kÿÿè¸,ë9ëëëë1ë/H‰ÃH‹½�ïÿÿH�… ïÿÿH9Çt<èñ+dH‹%(H;EÐt7è ,H‰ÃH‹}°H�EÀH9Çu*H‹½�ïÿÿH�… ïÿÿH9ÇuÄdH‹%(H;EÐuÉH‰ßèÃ+èž+H‹½�ïÿÿH�… ïÿÿH9ÇtÑë“ÌÌÌÌÌÌÌÌÌUH‰åH‹€8/”À]ÃÌUH‰åAVSH‰óèñ.H…Àt[A^]ÃI‰Æèï.H�=ÿÿH‰Æ1À]霰ÿÿ]ÃÌÌÌÌÌÌÌÌÌÌUH‰å1öº ]éŸ.ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàHÇGH‹ÆL�}ÐL‰}ÀHÇEÈÆEÐH�=´IÿÿèU.H…ÀtFH‰ÇI‰ÆèÅ)H�}À1ö1ÒL‰ñI‰Àèò)Hƒ}Èt!H�}À1öè� „ÀtH‹uÀH�=CJÿÿ1ÀèÊ©ÿÿH‹}À1öè.…ÀtgH‹uÀH�=2Dÿÿ1Àè©©ÿÿHÇEÈH‹EÀÆH�5àKÿÿH�}ÀèJ³ÿÿH�5@DÿÿH�}Àè:³ÿÿH‹}À1öè¿-…ÀtE1öH‹uÀH�=Gÿÿ1ÀèV©ÿÿë3H‹}À¾è–-…ÀtH‹uÀH�=ÈIÿÿ1Àè0©ÿÿA¶H�uÀH‰ßèQ)H‹}ÀL9ÿtè3)dH‹%(H;Eàu6D‰ðHƒÄ([A^A_]ÃH‰ÃH‹}ÀL9ÿtè)dH‹%(H;EàuH‰ßè)è)ÌÌÌÌÌÌÌÌÌUH‰åAWAVSPH‰óHÇFH‹Æèà,H…Àt"L‹sH‰ÇI‰ÇèL(H‰ß1öL‰òL‰ùI‰Àèy(Hƒ{•ÀHƒÄ[A^A_]ÃÌÌÌÌÌÌUH‰å]éÌÌÌÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàHÇGH‹ÆL�}ÐL‰}ÀHÇEÈÆEÐH�= EÿÿèE,H…ÀtFH‰ÇI‰Æèµ'H�}À1ö1ÒL‰ñI‰Àèâ'Hƒ}Èt!H�}À1ö耄ÀtH‹uÀH�=—Jÿÿ1À躧ÿÿH‹}À1öèÿ+…ÀtfH‹uÀH�=“Jÿÿ1Àè™§ÿÿHÇEÈH‹EÀÆH�5ÐIÿÿH�}Àè:±ÿÿH�5‡8ÿÿH�}Àè*±ÿÿH‹}À1öè¯+…Àt1ÛH‹uÀH�=¦Eÿÿ1ÀèG§ÿÿëDH‹}À¾è‡+…ÀtH‹uÀH�=€?ÿÿ1Àè!§ÿÿH�uÀH‰ßèE'³H‹uÀH�=|<ÿÿ1Àè§ÿÿH‹}ÀL9ÿtè'dH‹%(H;Eàu5‰ØHƒÄ([A^A_]ÃH‰ÃH‹}ÀL9ÿtèæ&dH‹%(H;EàuH‰ßèï&èú&ÌÌÌÌÌÌÌÌÌÌUH‰åAVSI‰þ1Û1öè=„ÀtI‹>¾èÌ*…À”ÉØ[A^]ÃUH‰åAWAVATSH‰ûHÇGH‹ÆH�=¥Cÿÿèˆ*H…Àt"L‹sH‰ÇI‰Çèô%H‰ß1öL‰òL‰ùI‰Àè!&Hƒ{t2H‰ß1öèÀ„ÀtH‹;¾èO*…À„µH‹3H�=«Fÿÿé’è#(I‰ÆÇf.„è+*‰Çè4*H…ÀuAƒ>téH�=BDÿÿ1Û1Àèù¦ÿÿëZI‰ÄL‹p L‹{L‰÷èT%H‰ß1öL‰úL‰ñI‰Àè�%H‰ß1öè'„ÀtH‹;¾è¶)…Àt I‹t$ H�=Iÿÿ1Û1Àè�¦ÿÿ‰Ø[A\A^A_]óëñÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0H‰ûdH‹%(H‰EØèœþÿÿ„À„#H�5�<ÿÿH‰ßèµ®ÿÿH‰ß1ö蛄ÀtH‹;¾è*)…ÀtJH‹;¾ÀèI)…Àt9è'‹8ƒÿu5E1öH‰ß1öè\„À„ÆH‹;¾èç(…ÀA”Æé®A¶é¦H‹è (L�eÈL‰e¸H…À„¬I‰ÆH‰Çè $I‰ÇH‰E°L‰àIƒÿvH�}¸H�u°1Òè$H‰E¸H‹M°H‰MÈM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úèô#H‹E°H‰EÀH‹M¸ÆH‹U¸H�=¤CÿÿH‰Þ1Àè?¥ÿÿH‹}¸L9çtè$E1ödH‹%(H;EØuSD‰ðHƒÄ0[A\A^A_]ÃdH‹%(H;EØu4H�=çBÿÿèd$H‰ÃH‹}¸L9çtè³#dH‹%(H;EØuH‰ßè¼#èÇ#ÌÌÌÌÌÌÌUH‰å1À]ÃÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒìhA‰öH‰ûdH‹%(H‰EÐL�gL‰'H¸/etc/dotH‰GÇGtnetHÇG ÆGL�}ÀL‰}°HÇE¸ÆEÀH�=�9ÿÿH�u°è�Õÿÿ„Àt H�u°H‰ßè#D‰÷èè»ÿÿH�½pÿÿÿH‰ÆèYÔÿÿH� �=ÿÿH�½pÿÿÿA¸1ö1Òè¬"L�m L‰m�H‹0H‰ÁHƒÁH9Ît H‰u�H‹H‰U ë AEL‰îH‹PH‰U˜H‰HÇ@Æ@H‰ßè¬ÿÿH‹}�L9ïtèp"H‹½pÿÿÿH�E€H9Çtè["H‹}°L9ÿtèM"dH‹%(H;EÐ…ËH‰ØHƒÄh[A\A]A^A_]ÃI‰ÆH‹}�L9ïu8H‹½pÿÿÿH�E€H9ÇuRH‹}°L9ÿusH‹;L9çuxdH‹%(H;EÐu}L‰÷è"èß!H‹½pÿÿÿH�E€H9ÇtÃëI‰ÆH‹½pÿÿÿH�E€H9Çt®èµ!H‹}°L9ÿt©ëI‰ÆH‹}°L9ÿt›ë I‰ÆH‹}°L9ÿt�è‹!H‹;L9çtˆè~!dH‹%(H;EÐtƒèš!ÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ìI‰×I‰ôH‰ûdH‹%(H‰EÐÆH‹?H�5>ÿÿèô!H…À„I‰ÆL�¥àþÿÿL‰¥ÐþÿÿHÇ…ØþÿÿÆ…àþÿÿH�µÐþÿÿL‰ÿèÙ H‹½ÐþÿÿL9çtèØ H�½Ðþÿÿ¾L‰òè4%H…Àt}L�¥ÐþÿÿI½ÿÿÿÿÿÿÿë!fff.„L‰ç¾L‰òè%H…ÀtIL‰çè# I‹OL1éH9Á‚½L‰ÿL‰æH‰Âèõ I‹wH…öt¼I‹€|0ÿ u²HÿκL‰ÿè2"IƒtcL‰÷è3$A·dH‹%(H;EЄRéËè3"‹8H‹ƒÿueH�=�9ÿÿE1ÿH‰Þ1Àèe£ÿÿAÆ$dH‹%(H;EЄé‹H‹3H�=7DÿÿE1ÿ1Àè´¥ÿÿL‰÷è¼#dH‹%(H;EЄÞéWè#L�¥àþÿÿL‰¥ÐþÿÿH…À„íI‰ÆH‰ÇèI‰ÇH‰…ÈþÿÿL‰àIƒÿv*H�½ÐþÿÿH�µÈþÿÿ1ÒèH‰…ÐþÿÿH‹�ÈþÿÿH‰�àþÿÿM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úèßH‹…ÈþÿÿH‰…ØþÿÿH‹�ÐþÿÿÆH‹•ÐþÿÿH�=BÿÿH‰Þ1Àè ÿÿH‹½ÐþÿÿL9çtèÝE1ÿdH‹%(H;EÐu~D‰øH�Ä[A\A]A^A_]ÃdH‹%(H;EÐuZH�=f;ÿÿèKdH‹%(H;EÐu?H�=£=ÿÿè H‰Ç踋ÿÿH‰ÃH‹½ÐþÿÿL9çtèddH‹%(H;EÐuH‰ßèmèxÌÌÌÌÌÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàL�}ÐL‰}ÀHÇEÈÆEÐH�=¾2ÿÿH�uÀèÐÿÿ„Àt:A¶H�uÀH‰ßèüH‹}ÀL9ÿtèÞdH‹%(H;EàuSD‰ðHƒÄ([A^A_]Ã蜶ÿÿ‰ÇH‰ÞèBA‰ÆH‹}ÀL9ÿuÃëÆH‰ÃH‹}ÀL9ÿtè“dH‹%(H;EàuH‰ßèœè§ÌÌÌÌÌÌÌUH‰åAWAVSHƒìxH‰óA‰þdH‹%(H‰EàHÇFH‹ÆH�}ÀD‰öè¶ùÿÿH‹uÀH�=Í0ÿÿ1Àè” ÿÿL�}°L‰} HÇE¨ÆE°Æ…ÿÿÿH�}ÀH�µÿÿÿH�U è•ûÿÿ„ÀtgH�u H‰ßèõH‹3H�= /ÿÿ1ÀèD ÿÿ€½ÿÿÿ•ÃH‹} L9ÿtè¼H‹}ÀH�EÐH9ÇtèªdH‹%(H;Eà…(‰ØHƒÄx[A^A_]À½ÿÿÿukè\µÿÿD9ðuaH�}€H�uÀ誯ÿÿH�5i=ÿÿH�}€èú¥ÿÿH‹u€H�=“?ÿÿ1À踟ÿÿH�}€H�µÿÿÿH�U èÔúÿÿA‰ÆH‹}€H�E�H9ÇtèE„ö…%ÿÿÿ1ÛH‹} L9ÿ…FÿÿÿéFÿÿÿë_H‰ÃH‹}€H�E�H9Çu-H‹} L9ÿuRH‹}ÀH�EÐH9ÇuWdH‹%(H;Eàu\H‰ßèãè¾H‹} L9ÿtÎëH‰ÃH‹}ÀH�EÐH9ÇtÉëH‰ÃH‹} L9ÿt®è�H‹}ÀH�EÐH9Çt©è~dH‹%(H;Eàt¤èšÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàL�}ÐL‰}ÀHÇEÈÆEÐH�=?<ÿÿH�uÀèŸÍÿÿ„Àt:A¶H�uÀH‰ßèH‹}ÀL9ÿtèþdH‹%(H;EàuvD‰ðHƒÄ([A^A_]Ãè¼³ÿÿA‰Æè´³ÿÿD9ðu H‹SA¶H� -ÿÿA¸H‰ß1öè‘ë£E1öH‹}ÀL9ÿu ë£H‰ÃH‹}ÀL9ÿtè�dH‹%(H;EàuH‰ßè™è¤ÌÌÌÌUH‰åAWAVSPI‰÷‰ûè<³ÿÿA‰Æ9ØuI‹WH� ¤,ÿÿA¸L‰ÿ1öèA9Þ”ÀHƒÄ[A^A_]ÃÌÌÌÌÌÌÌÌÌUH‰åAVSI‰öH‰ûH‰÷¾"1ÒèdHƒøÿtSI‹NH9Á‚ •Á¶ÑL‰÷H‰ÆèÐL‰÷L‰öèåL‰÷ëÀI‹NH9Árv•Á¶ÑL‰÷H‰Æè¦L‰÷L‰öè»L‰÷¾'1ÒèüHƒøÿuÆH�KH‰ I‹I�FH9Ât H‰H‹H‰KëI‹NH‰KI‰IÇFAÆFH‰Ø[A^]ÃH�=Ã*ÿÿH�55ÿÿH‰Â1ÀèÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ìhI‰þdH‹%(H‰EÐH�E€H‰…pÿÿÿHÇ…xÿÿÿÆE€H�½`ÿÿÿH‰½PÿÿÿH¸/etc/os-H‰…`ÿÿÿH¸-releaseH‰…gÿÿÿHÇ…XÿÿÿÆ…oÿÿÿ1öèÙ…À… H��@ýÿÿH‰ßèH�½XýÿÿH‹µPÿÿÿºè H‹�@ýÿÿHYè1öH…Àu‹s ƒÎH‰ßèúH‹…@ýÿÿH‹H胼 `ýÿÿ…!L‰µpüÿÿH�MÀH‰M°HÇE¸ÆEÀH�M H‰M�ÇE ID=HÇE˜H��0ýÿÿH‰� ýÿÿHÇ…(ýÿÿÆ…0ýÿÿH��ýÿÿH‰�ýÿÿH¹VERSION_H‰�ýÿÿÇ…ýÿÿ_ID=HÇ…ýÿÿ Æ…ýÿÿH��ðüÿÿH‰�àüÿÿHÇ…èüÿÿÆ…ðüÿÿH‹@èH‹œ0þÿÿH…Û„V€{8t¶CCëH‰ßèH‹H‰ß¾ ÿP0¾ÐH�½@ýÿÿH�u°è E1äL�}°L�­@ýÿÿE1ö뀶CC¾ÐL‰ïL‰þèÞH‹…@ýÿÿH‹@èö„`ýÿÿ…ÒAöÄuH‹u�H‹M˜L‰ÿ1ÒèÝH…À„¿°AöÆ…¨H‹µýÿÿH‹�ýÿÿL‰ÿ1Òè°H…À…CL‹m¸Iƒý †%H�…ÐüÿÿH‰…ÀüÿÿH‹E°H‰ÃHƒÃ u Lè…²M�}õL‰½xüÿÿH�…ÐüÿÿIƒÿr*H�½ÀüÿÿH�µxüÿÿ1ÒèH‰…ÀüÿÿH‹�xüÿÿH‰�ÐüÿÿIƒý „&Iƒý …¶ ˆéL‹m¸Iƒý†cL�¥ÐüÿÿL‰¥ÀüÿÿH‹E°H‰ÃHƒÃu Lè…M�}ýL‰½xüÿÿL‰àIƒÿr*H�½ÀüÿÿH�µxüÿÿ1ÒèzH‰…ÀüÿÿH‹�xüÿÿH‰�ÐüÿÿIƒýtIƒýu¶ ˆëH‰ÇH‰ÞL‰úèTH‹…xüÿÿH‰…ÈüÿÿH‹�ÀüÿÿÆH‹µÀüÿÿH‹•ÈüÿÿH�½ ýÿÿèñH‹½ÀüÿÿL9çL�}°L�­@ýÿÿtèEA´°AöÆ„^þÿÿfDAöÄ…®é­H‰ÇH‰ÞL‰úèÓH‹…xüÿÿH‰…ÈüÿÿH‹�ÀüÿÿÆHƒ½ÈüÿÿtJ¹ H�½ÀüÿÿH�57ÿÿ1ÒèæL�}°L�­@ýÿÿHƒøÿu*A¶H‹µÀüÿÿH‹•ÈüÿÿH�½àüÿÿè8ë L�}°L�­@ýÿÿH‹½ÀüÿÿH�…ÐüÿÿH9ÇtèƒD‰ð$AöÄt„ÀuAH‹…@ýÿÿH‹@èH‹œ0þÿÿH…Û„/€{8…ýÿÿH‰ßèöH‹H‰ß¾ ÿP0éýÿÿH�½Xýÿÿè÷H…Àu%H‹…@ýÿÿH‹@èH�<(H�Ç@ýÿÿ‹´`ýÿÿƒÎè�AöÄu AöÆu%éYH‹µ ýÿÿH‹•(ýÿÿH�½pÿÿÿèbAöÆtIH¸ÿÿÿÿÿÿÿH9…xÿÿÿ„«H�5P(ÿÿH�½pÿÿÿºè-H‹µàüÿÿH‹•èüÿÿH�½pÿÿÿèH��°üÿÿH‰� üÿÿL‹½pÿÿÿL‹¥xÿÿÿM…ÿu L‰øLà…-L‰¥xüÿÿH‰ØIƒür*H�½ üÿÿH�µxüÿÿ1ÒèáH‰… üÿÿH‹�xüÿÿH‰�°üÿÿM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âè»H‹…xüÿÿH‰…¨üÿÿH‹� üÿÿÆH�½ÀüÿÿH�µ üÿÿè¯øÿÿH�½pÿÿÿH�µÀüÿÿè¬H‹½ÀüÿÿH�…ÐüÿÿH9Çtè¤H‹½ üÿÿH9ßtè“H‹½àüÿÿH�…ðüÿÿH9Çtè{H‹½ýÿÿH�…ýÿÿH9ÇL‹µpüÿÿtè\H‹½ ýÿÿH�…0ýÿÿH9ÇtèDH‹}�H�E H9Çtè2H‹}°H�EÀH9Çtè H‹5Ù*H�½@ýÿÿèíH�½HþÿÿèñH���üÿÿH‰�€üÿÿL‹½pÿÿÿL‹¥xÿÿÿM…ÿu L‰øLà…ÃL‰¥@ýÿÿH‰ØIƒür*H�½€üÿÿH�µ@ýÿÿ1Òè_H‰…€üÿÿH‹�@ýÿÿH‰��üÿÿM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âè9H‹…@ýÿÿH‰…ˆüÿÿH‹�€üÿÿÆL�½PýÿÿL‰½@ýÿÿÇ…PýÿÿrhelfÇ…Týÿÿ.HÇ…HýÿÿL�eÀL‰e°ÇEÀalpiÇEÃine.HÇE¸ÆEÇL�m L‰m�ÇE rockfÇE¤y.HÇE˜ÆE¦H�½€üÿÿH��@ýÿÿº1öè×…Àt>H‹U¸H�½€üÿÿH�M°1öè½…ÀtHH‹U˜H�½€üÿÿH�M�1ö装À…’H�E˜ëH�…HýÿÿH‹H�5&%ÿÿH�½€üÿÿ¹ècë@H‹U¸H�5%ÿÿH�½€üÿÿ¹èEHƒøÿtFHÿÀH�5ç$ÿÿH�½€üÿÿ¹H‰Âè!Hƒøÿt"H‹�ˆüÿÿH‰ÊH)‚ÄH�½€üÿÿH‰ÆèùI�FI‰H‹�€üÿÿH9ÙtI‰H‹…�üÿÿI‰FëH‹…ˆüÿÿI‰FH‰�€üÿÿHÇ…ˆüÿÿÆ…�üÿÿH‹}�L9ïtè®H‹}°L9çtè H‹½@ýÿÿL9ÿtè�H‹½€üÿÿH9ßtè~H‹½PÿÿÿH�…`ÿÿÿH9ÇtèfH‹½pÿÿÿH�E€H9ÇtèQdH‹%(H;EÐ…ƒL‰ðH�Äh[A\A]A^A_]ÃdH‹%(H;EÐ…[è!dH‹%(H;EÐ…CH�=.ÿÿè’dH‹%(H;EÐ…$H�=ö-ÿÿèsdH‹%(H;EÐ…H�=×-ÿÿèTdH‹%(H;EÐ…æH�= ÿÿH�5¶&ÿÿºL‰é1ÀèddH‹%(H;EÐ…¶H�=ãÿÿH�5†&ÿÿº L‰é1Àè4dH‹%(H;UÐ…†H�=³ÿÿH�5o*ÿÿH‰Â1Àè dH‹%(H;EÐ…[H�=--ÿÿèªdH‹%(H;EÐ…<H�=¶*ÿÿè›I‰ÆH‹½ üÿÿH9ßuFéMéEé@é;ë\ëuI‰ÆH‹½ÀüÿÿH�…ÐüÿÿH9ÇuéI‰ÆH‹½ÀüÿÿL9ç„ è‘ éI‰ÆébéòéíH‰Çè²zÿÿH‰ÇèªzÿÿI‰ÆH‹½PÿÿÿH�…`ÿÿÿH9ÇtIécI‰ÆH‹}�L9ïugH‹}°L9çulH‹½@ýÿÿL9ÿuqH‹½€üÿÿH9ßuvH‹½PÿÿÿH�…`ÿÿÿH9Ç…H‹½pÿÿÿH�E€H9Ç…$dH‹%(H;EÐ…)L‰÷èü è× H‹}°L9çt”èÉ H‹½@ýÿÿL9ÿt�è¸ H‹½€üÿÿH9ßtŠè§ H‹½PÿÿÿH�…`ÿÿÿH9Çt‰é£ëI‰ÆH‹½àüÿÿH�…ðüÿÿH9Ç…½H‹½ýÿÿH�…ýÿÿH9Ç…ÂH‹½ ýÿÿH�…0ýÿÿH9Ç…ÇH‹}�H�E H9Ç…ÌH‹}°H�EÀH9Çtè" H‹5Û$H�½@ýÿÿèïH�½HþÿÿèóH‹½PÿÿÿH�…`ÿÿÿH9Ç„áþÿÿèç H‹½pÿÿÿH�E€H9Ç„ÜþÿÿèÎ dH‹%(H;EЄ×þÿÿèæ è± H‹½ýÿÿH�…ýÿÿH9Ç„>ÿÿÿè• H‹½ ýÿÿH�…0ýÿÿH9Ç„9ÿÿÿèy H‹}�H�E H9Ç„4ÿÿÿèc H‹}°H�EÀH9Ç…+ÿÿÿé+ÿÿÿÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åH‹?1öèb…À”À]ÃÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSPH‰ûH�=¼ÿÿ1öè%I‰ÆH…Àt8L‹{L‰÷è‘ H‰ß1öL‰úL‰ñI‰Àè¾ L‰÷è6A·D‰øHƒÄ[A^A_]ÿèëE1ÿH…ÀtàH‰Ç1öèÉI‰ÆH…Àu¤ëÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0H‰ûdH‹%(H‰EØH�=ÚÿÿÿH�u¸èA‰Æ…Àt#L‹}¸L‹cL‰ÿèê H‰ß1öL‰âL‰ùI‰Àè E…ö•ÀdH‹ %(H;MØu HƒÄ0[A\A^A_]Ãè@ UH‰åAWAVATSHƒì0H‰ðH‰ûdH‹ %(H‰MØH�u¸H‰Çè’ A‰Æ…Àt#L‹}¸L‹cL‰ÿèk H‰ß1öL‰âL‰ùI‰Àè˜ E…ö•ÀdH‹ %(H;MØu HƒÄ0[A\A^A_]ÃèÁ ÌUH‰å1À]ÃÌÌÌÌÌÌÌÌUH‰å1À]ÃÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì8A‰÷I‰þdH‹%(H‰EÐH‹?1öèbH‰ÃH…ÀtOM‹~H‰ßèÎL‰÷1öL‰úH‰ÙI‰ÀèûH‰ßès H…Û•ÀdH‹ %(H;MÐ…HƒÄ8[A\A]A^A_]Ãè ‹8ƒÿtÌE„ÿuÇM‹6èB L�mÀL‰m°H…À„“I‰ÇH‰ÇèVI‰ÄH‰E¨L‰èIƒüvH�}°H�u¨1ÒèGH‰E°H‹M¨H‰MÀM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âè*H‹E¨H‰E¸H‹M°ÆH‹U°H�=h"ÿÿL‰ö1Àèu‰ÿÿH‹}°L9ï„%ÿÿÿè3éÿÿÿdH‹%(H;EÐu4H�=6'ÿÿè³H‰ÃH‹}°L9ïtèdH‹%(H;EÐuH‰ßè èÌÌÌÌÌÌUH‰åH‰ÑH‹?1Ò]éÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ì¸H‰Ë‰•<ÿÿÿI‰÷dH‹%(H‰EÐèÎ H…À„ÇI‰ÄH‰ÇèÊ H…À„«I‰ÆH‰�(ÿÿÿëIH‹½0ÿÿÿL‰îH‰ÚèH‹…@ÿÿÿI‰FI‹ÆH‹�(ÿÿÿHƒC DL‰çèx I‰ÆH…À„VI‹?M�nL‰îºèh …ÀuÔA¶FÑȃøwÈH� E,ÿÿHc�HÈÿàL‰çèP ¿‰ÆL‰êH��@ÿÿÿE1ÀèG ƒøÿt’‹…Xÿÿÿ¹ð!È€½<ÿÿÿt=@…qÿÿÿ뀽<ÿÿÿ…bÿÿÿëÀÿÿ©°ÿÿ…PÿÿÿL‰ïH�5ÿÿè…À„9ÿÿÿL‰ïH�5&ÿÿèê…À„"ÿÿÿL‹sL;strI�FH‰…0ÿÿÿI‰L‰ïèÒH‰ÃH‰…@ÿÿÿHƒør&L‰÷H�µ@ÿÿÿ1ÒèÁH‰…0ÿÿÿI‰H‹…@ÿÿÿI‰FH…Û„ þÿÿHƒû…„þÿÿA¶EH‹�0ÿÿÿˆéƒþÿÿH‰ßL‰îèëé–þÿÿL‰çèN dH‹%(H;EÐuH�ĸ[A\A]A^A_]ÃèÈÌÌÌÌÌÌÌÌUH‰åAVSHƒì0H‰ñdH‹%(H‰EèL�uØL‰uÈfÇEØ*HÇEÐH‹?H�uÈ1Òè‘ýÿÿH‹}ÈL9÷tèCdH‹%(H;Eèu1HƒÄ0[A^]ÃH‰ÃH‹}ÈL9÷tèdH‹%(H;EèuH‰ßè#è.ÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åH‰ÑH‹?º]éýÿÿÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSHƒì0H‰ñdH‹%(H‰EèL�uØL‰uÈfÇEØ*HÇEÐH‹?H�uȺèÎüÿÿH‹}ÈL9÷tè€dH‹%(H;Eèu1HƒÄ0[A^]ÃH‰ÃH‹}ÈL9÷tèWdH‹%(H;EèuH‰ßè`èkÌÌÌÌÌÌÌÌÌÌÌUH‰å1À]ÃÌÌÌÌÌÌÌÌUH‰å1À]ÃÌÌÌÌÌÌÌÌH‹WH;VuH…ÒtUH‰åH‹6H‹?è’ …À”À]Ã1ÀðÃÌÌÌÌÌUH‰åAWAVAUATSHƒì(I‰÷dH‹%(H‰EÐL‹wL+7IÁþIƒþL‰ðHƒÐN�$0H¹ÿÿÿÿÿÿÿI9ÌLCáLðLBáM…äH‰}°tH‰ûL‰çHÁçè•L‹sL+3IÁþë1ÀIÁæJ�0N�,0IƒÅH‰EÀN‰,0L‰}¸L‰ÿèâI‰ÇH‰EÈHƒørH�uÈH‰ß1Òè×H‰H‹MÈI‰MI‰ÅM…ÿt"Iƒÿu H‹E¸¶AˆEëL‰ïH‹u¸L‰úè²H‹EÈL‹mÀK‰D5H‹ ÆH‹]°L‹;H‹CI9Ç„ÏM�uIƒÇë6I‰NðI‹I‰I‹OøI‰NøM‰ðIÇGøAÆIƒÆ I�O IƒÇI9ÇI‰ÏtM‰vðI‹OðI9Ïu½Aë¾L‹+L‹{IƒÆðM9ýu ë"f�IƒÅM9ýtI‹}IƒÅL9ïtêèEëãL‹;L‹mÀM…ÿtL‰ÿè/IƒÆ L‰+L‰sIÁäMìL‰cdH‹%(H;EÐu]HƒÄ([A\A]A^A_]ÃM‰îM…ÿu¼ëÂH‰ÇèYH‹}ÀèàdH‹%(H;EÐu$èlH‰ÃètdH‹%(H;EÐuH‰ßèÍèØH‰ÇèànÿÿUH‰åSPH�–4H‰ßè H‹=$H�•-H‰ÞHƒÄ[]éÿÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌAWAVA‰ÿAUATL�%UH�-NSI‰öI‰ÕL)åHƒìHÁýèCH…ít 1Û„L‰êL‰öD‰ÿAÿÜHƒÃH9ëuêHƒÄ[]A\A]A^A_Ã�f.„óÃHƒìH‹YH…Àtè7HƒÄÃHƒìHƒÄÃÿ5Šÿ%Œ@ÿ%Šhéàÿÿÿÿ%‚héÐÿÿÿÿ%zhéÀÿÿÿÿ%rhé°ÿÿÿÿ%jhé ÿÿÿÿ%bhé�ÿÿÿÿ%Zhé€ÿÿÿÿ%Rhépÿÿÿÿ%Jhé`ÿÿÿÿ%Bh éPÿÿÿÿ%:h é@ÿÿÿÿ%2h é0ÿÿÿÿ%*h é ÿÿÿÿ%"h éÿÿÿÿ%héÿÿÿÿ%héðþÿÿÿ% héàþÿÿÿ%héÐþÿÿÿ%úhéÀþÿÿÿ%òhé°þÿÿÿ%êhé þÿÿÿ%âhé�þÿÿÿ%Úhé€þÿÿÿ%Òhépþÿÿÿ%Êhé`þÿÿÿ%ÂhéPþÿÿÿ%ºhé@þÿÿÿ%²hé0þÿÿÿ%ªhé þÿÿÿ%¢héþÿÿÿ%šhéþÿÿÿ%’héðýÿÿÿ%Šh éàýÿÿÿ%‚h!éÐýÿÿÿ%zh"éÀýÿÿÿ%rh#é°ýÿÿÿ%jh$é ýÿÿÿ%bh%é�ýÿÿÿ%Zh&é€ýÿÿÿ%Rh'épýÿÿÿ%Jh(é`ýÿÿÿ%Bh)éPýÿÿÿ%:h*é@ýÿÿÿ%2h+é0ýÿÿÿ%*h,é ýÿÿÿ%"h-éýÿÿÿ%h.éýÿÿÿ%h/éðüÿÿÿ% h0éàüÿÿÿ%h1éÐüÿÿÿ%úh2éÀüÿÿÿ%òh3é°üÿÿÿ%êh4é üÿÿÿ%âh5é�üÿÿÿ%Úh6é€üÿÿÿ%Òh7épüÿÿÿ%Êh8é`üÿÿÿ%Âh9éPüÿÿÿ%ºh:é@üÿÿÿ%²h;é0üÿÿÿ%ªh<é üÿÿÿ%¢h=éüÿÿÿ%šh>éüÿÿÿ%’h?éðûÿÿÿ%Šh@éàûÿÿÿ%‚hAéÐûÿÿÿ%zhBéÀûÿÿÿ%rhCé°ûÿÿÿ%jhDé ûÿÿÿ%bhEé�ûÿÿÿ%ZhFé€ûÿÿÿ%RhGépûÿÿÿ%JhHé`ûÿÿÿ%BhIéPûÿÿÿ%:hJé@ûÿÿÿ%2hKé0ûÿÿÿ%*hLé ûÿÿÿ%"hMéûÿÿÿ%hNéûÿÿÿ%hOéðúÿÿÿ% hPéàúÿÿÿ%hQéÐúÿÿÿ%úhRéÀúÿÿÿ%òhSé°úÿÿÿ%êhTé úÿÿÿ%âhUé�úÿÿÿ%ÚhVé€úÿÿÿ%ÒhWépúÿÿÿ%ÊhXé`úÿÿÿ%ÂhYéPúÿÿÿ%ºhZé@úÿÿÿ%²h[é0úÿÿÿ%ªh\é úÿÿÿ%¢h]éúÿÿÿ%šh^éúÿÿÿ%’h_éðùÿÿÿ%Šh`éàùÿÿÿ%‚haéÐùÿÿÿ%zhbéÀùÿÿÿ%rhcé°ùÿÿÿ%jhdé ùÿÿÿ%bheé�ùÿÿÞ   , ò  • É  ûÿÿoøè ùÿÿoà� (1@ ü ü õþÿoà .H. t �ðÿÿoÀ þÿÿo ÿÿÿo¸.¶ÆÖæö&6FVfv†–¦¶ÆÖæö&6FVfv†–¦¶ÆÖæö&6FVfv†–¦¶ÆÖæö&6FVfv†–¦¶ÆÖæö&6FVfv†–¦¶ÆÖæö&6FVfv†–¦¶ÆÖæö‹¹ja 8r{“× 2õ¹æï®3î;-Î$³j®19ff3e9c3602ae8e841925bb461a0adb064a1f1903667a5e0d87e8f608f425acapp.dll@(#)Version 9.0.525.21509 @Commit: e36e4d1a8f8dfb08d7e3a6041459c9791d732c01d38cc827-e34f-4453-9df4-1e796e9f1d07clang version 18.1.8GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609Linker: LLD 18.1.8.init_array.fini_array.text.got.comment.got.plt.rela.plt.init.tbss.bss.dynstr.eh_frame_hdr.jcr.gnu.version_r.interp.data.rel.ro.rela.dyn.gnu.version.dynsym.gnu_debuglink.fini.gnu.hash.relro_padding.note.ABI-tag.eh_frame.gcc_except_table.tm_clone_table.note.gnu.build-id.dynamic.shstrtab.rodata.dataapphost.dbg)øõ½{ààÝüü $§ @@€ šÿÿÿoÀ À àlþÿÿo  @ÄöÿÿoààQüüü �øøè6Bàà� =2p(p(\õÌ=Ì=`Y,F,FœëÈJÈJ¤pspc¤@tt¾�� ;  pFg. . . Hƒp.pH*¸.¸ Ø0ØP-(1(HÎp4p� EpDpÕHKHLHKH$0H^3¦K¯ôopen-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/libc/dotnet-app-x86_64-musl000077500000000000000000002265101524057645600320450ustar00rootroot00000000000000ELF>Pr@%@8 @!@@@  àààLbLbPbPrPr@´@´��6�6øp ��L�LØ\��&�&88787ÀÀRåtd��6�6øp PåtdÄEÄEÄE||Qåtdüüü$$/lib/ld-musl-x86_64.so.1GNU�S\6’$cÏ�öäq9­ƒ«D  ; U"d"|"’ªÂÏÖ!f¯¶ü .>S’×ô%+3<BLRYhnu|ÃË×Þé,l·Úg·ÈÐîX_gu…ÊRW^glrx}„‰’™Ÿ§®¶¾Å!(17=k¯ë x ª Õ   ] u £ ¬ ¶ ¾ Æ Î Ô Ü å   ê P&y ø ooêÓï¹�ñ_fini_init__libc_start_main_ITM_deregisterTMCloneTable_ITM_registerTMCloneTable__cxa_finalize__deregister_frame_info__register_frame_info_ZNSt8ios_base4InitC1Ev_ZNSt8ios_base4InitD1Ev__cxa_atexitstrlen_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_createERmmmemcpy_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEmmPKc_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_replaceEmmPKcm_ZdlPv_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4__Unwind_Resume__stack_chk_fail__cxa_begin_catch_ZSt9terminatev__gxx_personality_v0_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4findEcm_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_appendEPKcm_ZSt19__throw_logic_errorPKc_ZSt20__throw_length_errorPKcsched_yieldstderrfopensetvbufvfprintffputcvsnprintf_Znwmmemset__tls_get_addrfputsstdoutfflush_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_mutateEmmPKcmstrncmpstrncasecmpstrcmpstrcasecmp_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5rfindEPKcmm_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE5rfindEcm_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE12find_last_ofEPKcmm_ZSt24__throw_out_of_range_fmtPKcz_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE8_M_eraseEmm_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE14_M_replace_auxEmmmc_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE17find_first_not_ofEPKcmm__errno_locationstrtoul_ZSt20__throw_out_of_rangePKc_ZSt24__throw_invalid_argumentPKctoupper_ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7reserveEmstrchrtolower__cxa_rethrow__cxa_end_catch_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareERKS4__ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE12_M_constructEmc_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE6substrEmmtimegmtimestrftimeopenclosefstatmmapgetcwdfreestrerrordlopendlsymdlerrordladdrdlclosegetlinesscanf_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4findEPKcmmfcloseatoigetenvaccessgetuidgetpwuidmkdirfgets_ZNSt13basic_fstreamIcSt11char_traitsIcEEC1Ev_ZNSt13basic_filebufIcSt11char_traitsIcEE4openEPKcSt13_Ios_Openmode_ZNSt9basic_iosIcSt11char_traitsIcEE5clearESt12_Ios_Iostate_ZNKSt5ctypeIcE13_M_widen_initEv_ZSt7getlineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7__cxx1112basic_stringIS4_S5_T1_EES4__ZNSt13basic_filebufIcSt11char_traitsIcEE5closeEv_ZTTSt13basic_fstreamIcSt11char_traitsIcEE_ZNSt13basic_filebufIcSt11char_traitsIcEED2Ev_ZNSt8ios_baseD2Ev_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEmmRKS4__ZSt16__throw_bad_castv_ZNSt13basic_fstreamIcSt11char_traitsIcEED1Evrealpathgetauxvalopendirreaddirfnmatchdirfdfstatatclosedirbcmplibgcc_s.so.1GCC_3.0$ORIGIN/netcoredepslibstdc++.so.6libc.musl-x86_64.so.1°6t¸6ÐzÀ6À†È6 –Ð6°¡Ø6pÑà6ëè6€ð6(<ø687 C7Ð37}=7ô1 7ü1(7U407¶8�L�Lø89999 9ÐN(909%89`X9`9h9p9x9€9 ˆ9 �9 ˜9  9 ¨9°9¸9À9È9Ð9Ø9à9è9ð9ø9:::: :(: 0:!8:"@:$H:&P:'X:(`:)h:*p:+x:,€:-ˆ:.�:/˜:0 :1¨:2°:3¸:4À:5È:6Ð:7Ø:8à:9è::ð:;ø:<;=;>;?;@ ;A(;B0;C8;D@;EH;FP;GX;H`;Ih;Jp;Kx;L€;Mˆ;N�;O˜;P ;Q¨;R°;S¸;TÀ;UÈ;VÐ;WØ;Xà;Yè;Zð;[ø;\<]<^<_<a <b(<c0<d8<e@<fH<gP<hX<i`<jh<kp<lx<m€<nÿÿ6‰� ˜Sëš’5˜ï%«Ù˜ç–ýÅ� ÂÝÿ› UÿÿÆÆîá‹ðì1ÿÿ 88.’f]ÿ›¸¬;Ç­ »Ý¹ÎÇܳ�.½Å·í½ûFÁµ´ Â ¢Œo¤û ô ‡ ò – !Ì × À à ö ¸ Gžÿ Ê ‹ È � Iø ƒ ¶ “ $¢Ï &ö õ <¾ ä ê �–”Øÿÿ??ÖT³ÿÿaääàœÿó”Kß‹®êðˆÌÎ Ì—­õÎ »Þ 1õþ Ýþ œ¡ óÀ õÌãÿÿJQQÖ`ѱó¿ õÊúÒhÿ‹ Ï—¾ê¼ýº›hÛƒÔÿÿ˜˜`¾› ‚§¼ÿÿ,­D«V ©_{ÿ›%h¹€òå$÷­í»†ÿ› È™ÍTÿ›!¸¸ÞÞ«ãƒÿ› È™ÍTÿ› ¸Œ½Wÿ› È™ÍTÿ› 6tÿÿ��/–Ìwÿÿ˜˜ µ¡Áÿÿ’’+„½tÿ›MC·· è¾»�¦«Kö¨Œpü™‘aò »� ¦™Wÿ›  !ÿÿ /Á?¯ÿÿ,¸D¶€¸ŽWÿ›YNXX ÑvÇ¿9ÑøNÆ ÏºØÉEŽ�ß›æ� Ú  Ö» ÑÇcÿÿ DG·‹Yÿÿ‰ ì•Kà ììÿÿ55ºN�ÿÿ `¹zìÿÿ5 îQ�ûîbÿÿ™™È°Š¸´ÿÿ´´™ÂK� ™™,ÿÿ™™È°Š¸´ÿÿ''ÚT Øa ´n ßz‰ÿÿA··¶ø‚búøãŠÕ  Ó§$å‘ ú¬ øÇ åÓTÿÿ ?Z™™ÿ›5-Ýݹᆳ ¹¹ ÙÞá‚æ$ÿÿéé‘÷Ž… ‘‘1ÿÿ, [5Sÿÿ B …O‰ÿÿ B”^‰ÿÿùù§� ¥¥K¹ð¨ÿÿ4àਠŒ¦ ¦� ߨ Œ� ¦‹ ͨ êkÿÿXëë« ûûö™ †…‹‰ šk…ö ÊÞ àݽ‹ ÍŠ× Ü ù à � Èÿÿÿÿ„�âï‚ý´ÿÿûûÔ‰xÿÿLcŸ| —Ž,™ºL›º•ñ›ù÷ðι�¤7“ì Ÿ‡ •“�ÿÿ [äê¿Xÿÿ [ôùÏWÿÿ°°ŒÁxÿÿ` ր䗳úØ‚”ÿ›%‘‘ àžÃáèò¦ÿÿ 1]œŽ;ÿÿ!>>½g�Ïù î†6ð¼Êÿÿ 1~¿¯=ÿÿ®�ý¬-œÀ)‰„‹ÃóÑøæ]à ÿÏ­ü Óâ ‹ý °‰ê îÿ OÎ ¿á „å®ú¾¸éÉ”‰¬ ®Ë ©ê ¤‰©¹¤éÉ” î³ ‰¿ùÿÿššÝ«_ÿÿ4 e?Sÿÿ4hBSÿ›-"ÏÏ“ÞÕ³¸»ÜÀ$ app-local: [%s]false0123456789The managed DLL bound to this executable is: '%s'}You must install .NET to run this application. %shttps://go.microsoft.com/fwlink/?linkid=2063370Considering fxr version=[%s]... coreservicing/proc/self/mapsUsing environment variable %s=[%s] as runtime location.Not found ]aRuntime config is cfg=%s dev=%s%s: __pos (which is %zu) > this->size() (which is %zu)/proc/self/exeThe managed DLL bound to this executable is longer than the max allowed length (%d)fxrError: [%s] does not existFound registered install location '%s'./usr/share/dotnet global install location [%s] self-registered config location [%s] environment_variableriscv64s390xFailed to resolve full path of the current executable [%s]Invoking fx resolver [%s] hostfxr_main_startupinfoResolved fxr [%s]...Checking if CoreCLR path exists=[%s]Failed to unload library, error: %sLooking for architecture-specific install_location file in '%s'.The library %s was found, but loading it from %s failedThe required library %s does not support single-file apps.Reading fx resolver directory=[%s]COREHOST_TRACECOREHOST_TRACE_VERBOSITY.loongarch64_DOTNET_TEST_GLOBALLY_REGISTERED_PATHError: [%s] does not contain any version-numbered child foldersCOREHOST_TRACEFILEx64DOTNET_ROOT_&framework_version=linux-muslUsing core servicing at [%s]%*p-%*p %*[-rwxsp] %*p %*[:0-9a-f] %*d %s This executable is not bound to a managed DLL to execute. The binding value is: '%s'.runtimeconfig.dev.jsonThe required library %s could not be found. Search location options [0x%x].runtimeconfig.json_DOTNET_TEST_INSTALL_LOCATION_PATH%s App: %s Architecture: %s App host version: %s .NET location: %s Learn more: https://aka.ms/dotnet/app-launch-failed Download the .NET runtime: %s&apphost_version=%sThe application to execute does not exist: '%s'. app_local.netDOTNET_RUNTIME_IDmissing_runtime=truevector::_M_realloc_insertFailed to load %s, error: %sThe app-relative .NET path is longer than the max allowed length (%d)%s - search options: [basic_string::substrDirectory core servicing at [%s] was not ACL-ed properlyFailed to map file. mmap(%s) failed with error %dhostfxr_set_error_writertruearm64.., app-relative path: Tracing enabled @ %sDOTNET_ROOTtouch_fileVERSION_ID=The app-relative .NET path is not embedded.Unable to open COREHOST_TRACEFILE=%s for writingx869.0.5libcoreclr.solinux-musl-x64The install_location file ['%s'] does not exist - skipping.App path: [%s]Using global install location [%s] as runtime location./\%c %Zrealpath(%s) failed: %salpine.apphostDid not find [%s] directory [%s]Directory core breadcrumbs [%s] was not specified or foundcorebreadcrumbsinstall_location_Failed to map file. fstat(%s) failed with error %dlibhostfxr.soInvoking fx resolver [%s] v1Failed to map file. open(%s) failed with error %d - Installing .NET prerequisites might help resolve this problem.The required library %s does not support relative app dll paths.Using app-relative location [%s] as runtime location.Found previously loaded library %s [%s].&arch=basic_string::eraseThe required library %s does not contain the expected entry point.Using app-local location [%s] as runtime location.basic_string::appendarmCORE_SERVICINGr.NET root search location options: %dHost path: [%s]Dotnet path: [%s]HOMEBundle Header Offset: [%ld]hostDOTNET_MULTILEVEL_LOOKUPgetcwd() failed: %sFallback directory core breadcrumbs at [%s] was not foundError: the required library %s could not be found in [%s]DOTNET_MULTILEVEL_LOOKUP is set to %s&rid=&os=ppc64leFallback directory core servicing at [%s] was not foundFailed to determine default extraction location. Environment variable '$HOME' is not defined and getpwuid() returned NULL.rocky. globalDetected latest fxr version=[%s]...basic_string::_M_construct null not validrhel.Embedded app-relative .NET path: '%s'stoulCORE_BREADCRUMBSFailed to create default extraction directory [%s]. %shostfxr_main_bundle_startupinfohostfxr_mainDetected Single-File app bundleError: the default install location cannot be obtained..deps.jsonRealpath CORE_BREADCRUMBS [%s]Breadcrumb store [%s] is not ACL-ed with rw-Default extraction directory [%s] either doesn't exist or is not accessible for read/write./etc/dotnetinstall_location_DOTNET_TEST_DEFAULT_INSTALL_PATHID=hostfxr_main_startupinfoInvoking fx resolver [%s] hostfxr_main_bundle_startupinfo--- Invoked %s [version: %s] main = {Multilevel lookup is %shttps://aka.ms/dotnet-core-applaunch?Probed for and did not find library symbol %s, error: %sopt/etc/os-release0123456789."'You must install or update .NET to run this application. app-relative: [%s] app_relativecannot create std::vector larger than max_size()framework=Realpath CORE_SERVICING [%s]Directory core servicing at [%s] was not specified or foundThe install_location file ['%s'] failed to open: %s.Looking for install_location file in '%s'. environment variable: [%s]armv6open(%s) failed in %sFailed to determine default extraction location. Environment variable '$HOME' is not defined and directory reported by getpwuid() [%s] either doesn't exist or is not accessible for read/write.Did not find any install location in '%s'.19ff3e9c3602ae8e841925bb461a0adb064a1f1903667a5e0d87e8f608f425acc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f200010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899ÜÓ”Ó>Ô”ÓÔÜÓ;xŽ,.”<.´|.Ôœ.ô¼.Ü.4ü.0œ4dü4T 5pL5œì6̼7¬?�ì?Dü@´Ú»ÏÿÿA†C GƒŽ� A ,è>Á§ÏÿÿA†C KƒŒŽ�] A (0ˆ@!ŸÏÿÿA†C JƒŽ A ,\ŒAf‹ÏÿÿA†C NƒŒŽ�Ì A (ŒÜC!ÏÿÿA†C JƒŽ A (¸àDkÏÿÿA†C JƒŽø A (äôE!WÏÿÿA†C JƒŽ A ,øF|CÏÿÿA†C Bƒi E 0@ØGC#ÏÿÿA†C MƒŒ�Ž�é A 0tôHâÏÿÿA†C MƒŒ�Ž�m A ,¨°J1çÎÿÿA†C IƒŽ�Û A 0Ø0RpËÎÿÿA†C MƒŒ�Ž�² A $ lU)çÎÿÿA†C \ A (4´WîÏÎÿÿA†C GƒŽµ A (`˜Xå³ÎÿÿA†C GƒŽª A 0Œ\Y*£ÎÿÿA†C MƒŒ�Ž�! N ,ÀX]äËÎÿÿA†C KƒŒŽ�§ A ,ð8^ «ÎÿÿA†C KƒŒŽ�¥ A ( _ë—ÎÿÿA†C GƒŽ® A ,Lì`æÎÿÿA†C KƒŒŽ�© A ,|¬bÐ_ÎÿÿA†C KƒŒŽ�C A 0¬LdlCÎÿÿA†C MƒŒ�Ž�Ý A 0àˆeE+ÎÿÿA†C MƒŒ�Ž�Ñ A 0¤flÎÿÿA†C MƒŒ�Ž�Ý A (H�iƒÿÍÿÿA†C GƒŽÓ A 0tôj'÷ÍÿÿA†C MƒŒ�Ž�l A ,¨ðn± ÎÿÿA†C FƒŽ�‚ A 4ذp ëÍÿÿA†C MƒŒ�Ž�f A 0ÈsÂëÍÿÿA†C MƒŒ�Ž�E A (Dduˆ×ÍÿÿA†C GƒŽO A ,pÈuØ·ÍÿÿA†C IƒŽ�w A , xvç—ÍÿÿA†C IƒŽ�† A (ÐHwwÍÿÿA†C GƒŽà A 0üŒyÕoÍÿÿA†C MƒŒ�Ž�y A 00 ØØsÍÿÿA†C PƒŒ�Ž�� A 0d ˆ±›ÍÿÿA†C MƒŒ�Ž�. A ,˜ €Ž�‹ÍÿÿA†C KƒŒŽ�) A 0È à�¢oÍÿÿA†C PƒŒ�Ž�¶ A ,ü L•—‹ÍÿÿA†C IƒŽ�\ A ,, ,—¦kÍÿÿA†C IƒŽ�k A ,\ š¹KÍÿÿA†C KƒŒŽ�a A 0Œ ¼›/ÍÿÿA†C MƒŒ�Ž�F A 0À ¨�ÍÿÿA†C PƒŒ�Ž�• A ,ô ” ÉÍÿÿA†C IƒŽ�q A ,$ 4¡óÌÿÿA†C IƒŽ�Ø A ,T £ìëÌÿÿA†C IƒŽ�q A 0„ ¥¸ËÌÿÿA†C PƒŒ�Ž�ì A 0¸ pµŠKÍÿÿA†C MƒŒ�Ž�s A (ì ¹’+ÍÿÿA†C GƒŽY A ( °¹• ÍÿÿA†C GƒŽ\ A 0D tºäëÌÿÿA†C MƒŒ�Ž�[ A H1íH‰çH�5ÛÄHƒäðèH‹7H�WE1ÉL�—­H� ƒ­H�=+馭ÌÌÌÌÌÌH�=ÑàH�ÊàH9øtH‹NÆH…Àt ÿà€Ã€H�=¡àH�5šàH)þH‰ðHÁî?HÁøHÆHÑþtH‹ÆH…ÀtÿàfDÀ€=yà…“UHƒ=òÅH‰åATSt H‹=kÙè­H�oÃH�pÃH)ÃI‰ÄH‹CàHÁûHƒëH9Øs!fDHƒÀH‰%àAÿÄH‹àH9ØråèÿÿÿHƒ=�Åt H�=·Öÿÿè¬[A\Æèß]ÃfDÃff.„@Hƒ=`Åt&UH�5ÞßH�=wÖÿÿH‰åè�¬]ééþÿÿf„éÛþÿÿÌÌÌÌÌÌÌÌÌÌÌUH‰åH‹¥Ø]ÃÌÌÌUH‰åSPH�ÃßH‰ßè[¬H‹=ÅH�mØH‰ÞHƒÄ[]éO¬ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åH‹?H�5ÁÊÿÿ]éìÌÌÌÌÌÌÌÌÌÌÌÌUH‰åH‹?H�5vÃÿÿ]éÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åH‹?H�5üËÿÿ]é¬ÌÌÌÌÌÌÌÌÌÌÌÌUH‰åH‹?H�5�Êÿÿ]éŒÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ì¸I‰ôH‰ûdH‹%(H‰EÐHÇH�GH‰…(ÿÿÿH‰GHÇGÆGH�G(H‰…@ÿÿÿH�G8H‰…0ÿÿÿH‰G(HÇG0ÆG8H�GHH‰…HÿÿÿH�GXH‰…8ÿÿÿH‰GHHÇGPÆGXÆGhH�E€H‰…pÿÿÿHÇ…xÿÿÿÆE€H�…`ÿÿÿH‰…PÿÿÿHÇ…XÿÿÿÆ…`ÿÿÿD¶-5×AöÅ„L�}ÀL‰}°H�=×èתI‰ÆH‰E�L‰øIƒþrH�}°H�u�1ÒèȪH‰E°H‹M�H‰MÀM…öt#Iƒþu ¶ ÜÖˆëH�5ÑÖH‰ÇL‰ò褪H‹E�H‰E¸H‹M°ÆH‹E¸H=þrE1öH�=ÔÀÿÿ¾ý1Àè‚$é]H…À„ÊHƒø@r=H� ÇÍÿÿH�}°º 1öèWª…Àu"H� ÜÍÿÿH�}°¾ º è9ª…À„‡H�E H‰E�HÇE˜ÆE L‹u°L‰÷èá©H�}�1ö1ÒL‰ñI‰ÀèªH‹u�H�=ÎÇÿÿ1Àè¼"H‹M�L�u L9ñtHH‹…pÿÿÿH�U€H9ÐtVH‹U€H‰�pÿÿÿE˜…xÿÿÿH…ÀtMH‰E�H‰U ënE1öH�=9Áÿÿ1Àè™#ëwH‹U˜H…Òt8H‹½pÿÿÿHƒúu#¶E ˆë#H‰�pÿÿÿE˜…xÿÿÿL‰u�L‰ðë"L‰öèF©H‹E˜H‰…xÿÿÿH‹�pÿÿÿÆH‹E�HÇE˜ÆH‹}�L9÷tèC©A¶H‹}°L9ÿtè2©A¿•€€E„ö„ÅH�=yÄÿÿD‰î1ÀèÂ!Hƒ½xÿÿÿt"H�½PÿÿÿL‰æè ©H‹µpÿÿÿH�½PÿÿÿèF+H�•PÿÿÿL‰çD‰îH‹�@ÿÿÿL‹…HÿÿÿèA¿ƒ€€„Àt^H‹½HÿÿÿH‰ÞèM|E1ÿ„ÀuHH‹…HÿÿÿH‹H�=ĺÿÿH�5ðÁÿÿ1Àèg"H�=?Âÿÿ1ÀèY"A¿‚€€H�=R·ÿÿH�5S·ÿÿ1Àè>"D‰{lH‹½PÿÿÿH�…`ÿÿÿH9ÇtèB¨H‹½pÿÿÿH�E€H9Çtè-¨dH‹%(H;EÐ…”H�ĸ[A\A]A^A_]ÃëI‰ÄH‹}°L9ÿt&é¸I‰ÄH‹}�H�E H9Ç…’H‹}°L9ÿ…—H‹½PÿÿÿH�…`ÿÿÿH9Ç…¸H‹½pÿÿÿH�E€H9Ç…½H‹…HÿÿÿH‹8H;½8ÿÿÿ…ÂH‹…@ÿÿÿH‹8H;½0ÿÿÿ…ÇH‹{H;½(ÿÿÿ…ÌdH‹%(H;EÐ…ÑL‰çèo§èJ§H‹}°L9ÿ„iÿÿÿè8§H‹½PÿÿÿH�…`ÿÿÿH9Ç„dÿÿÿëI‰ÄH‹½PÿÿÿH�…`ÿÿÿH9Ç„Hÿÿÿè§H‹½pÿÿÿH�E€H9Ç„Cÿÿÿèç¦H‹…HÿÿÿH‹8H;½8ÿÿÿ„>ÿÿÿè˦H‹…@ÿÿÿH‹8H;½0ÿÿÿ„9ÿÿÿ详H‹{H;½(ÿÿÿ„4ÿÿÿ虦dH‹%(H;EЄ/ÿÿÿ豦ÌUH‰åSPH‰ûH‹?H…ÿtèjzH‹{HH�CXH9ÇtèX¦H‹{(H�C8H9ÇtèF¦H‹{HƒÃHƒÄH9ßt[]é.¦[]ÃH‰ÇèÌÌÌUH‰åèW¦èb¦ÌÌUH‰åSPH�ôØH‰ßè‹¥H‹=4¾H��ÑH‰ÞHƒÄ[]é¥ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0H‰ûdH‹%(H‰EØL‹wL�=¦ÓL‰ÿèN¥H‰ß1öL‰òL‰ùI‰Àè{¥L�eÈL‰e¸L‰ÿè+¥I‰ÆH‰E°L‰àIƒþrH�}¸H�u°1Òè¥H‰E¸H‹M°H‰MÈM…öt#Iƒþu ¶ >ÓˆëH�53ÓH‰ÇL‰òèø¤H‹E°H‰EÀH‹M¸ÆH‹EÀH=r1ÛH�=µÿÿ¾1Àè×ëRHƒø@r9H� ˆÈÿÿH�}¸º 1ö踤…ÀuH� �ÈÿÿH�}¸¾ º 蚤…Àt?H‹3³H�=-³ÿÿ1ÀèSH‹}¸L9çt蕤dH‹%(H;EØuN‰ØHƒÄ0[A\A^A_]ÃH‹31ÛH�=X¸ÿÿ1ÀèDë¿ëH‰ÃH‹}¸L9çtèO¤dH‹%(H;EØuH‰ßèX¤èc¤ÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàL‹6èÛ5I‰ÇH‹H�}À1ö1ÒèXKL�Ë»ÿÿH�=ƸÿÿH�5ŸÄÿÿL‰òL‰ùI‰Ù1ÀAPÿuÀè®HƒÄH‹}ÀH�EÐH9Çt踣dH‹%(H;Eàu7HƒÄ([A^A_]ÃH‰ÃH‹}ÀH�EÐH9Çt艣dH‹%(H;EàuH‰ßè’£è�£ÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ì(H‰óA‰þdH‹%(H‰EÐL�mÀL‰m°HÇE¸ÆEÀH�}°èŒ—„À„øH�}°1öè)™„À„åH�E H‰E�HÇE˜ÆE H�E€H‰…pÿÿÿHÇ…xÿÿÿÆE€H�…`ÿÿÿH‰…PÿÿÿHÇ…XÿÿÿÆ…`ÿÿÿH�½Pÿÿÿè×üÿÿA¼•€€„À„¥H�½Pÿÿÿ¾/1Òèæ¢I‰ÇH�½àþÿÿH�u°ès.H‹µàþÿÿL�¥ðþÿÿL9愎H‹E�H�M H9È„šH‹M H‰u�…èþÿÿE˜H…À„�H‰…àþÿÿH‰�ðþÿÿé¥A¼…€€H‹u°H�=-³ÿÿ1ÀèäH‹}°L9ïtèö¡dH‹%(H;EÐ…jD‰àH�Ä([A\A]A^A_]ÃH‹•èþÿÿH…Òt1H‹}�Hƒúu"¶ˆë H‰u�…èþÿÿE˜L‰¥àþÿÿL‰àëèa¡H‹…èþÿÿH‰E˜H‹M�ÆH‹…àþÿÿHÇ…èþÿÿÆH‹½àþÿÿL9çtèX¡H‹µPÿÿÿH�}�è¨#èSôÿÿH…ÀtH�=“¿ÿÿ1ÀèàëH�}�1öècw„À„žH�½àþÿÿH�u�è -H‹µàþÿÿL9ætAH‹…pÿÿÿH�M€H9ÈtQH‹M€H‰µpÿÿÿ…èþÿÿ…xÿÿÿH…ÀtHH‰…àþÿÿH‰�ðþÿÿé€H‹•èþÿÿH…ÒtTH‹½pÿÿÿHƒúuB¶ˆë@H‰µpÿÿÿ…èþÿÿ…xÿÿÿL‰¥àþÿÿL‰àë?A¼š€€H‹u�H�=óµÿÿ1ÀèEëoè. H‹…èþÿÿH‰…xÿÿÿH‹�pÿÿÿÆH‹…àþÿÿHÇ…èþÿÿÆH‹½àþÿÿL9çtè H�½àþÿÿH�µpÿÿÿèìóÿÿD‹¥LÿÿÿE…ätTH�½àþÿÿètùÿÿH‹½PÿÿÿH�…`ÿÿÿH9ÇtèÜŸH‹½pÿÿÿH�E€H9ÇtèÇŸH‹}�H�E H9Ç„²ýÿÿ豟é¨ýÿÿè·òÿÿH…À„H�½àþÿÿèòòÿÿI‰ÇH…À„2H‹…ÿÿÿH…ÀtH‹…ÿÿÿH‰…¸þÿÿL‹e˜M…ätL‹e�H‹E°H‰…Àþÿÿè_òÿÿH‰…ÈþÿÿH‹µ(ÿÿÿH�=ý¾ÿÿ1ÀèãH‹u°H�=«ºÿÿ1ÀèÑH‹µÿÿÿH�=¦ºÿÿ1Àè¼H‹u�H�=:·ÿÿ1ÀèªH�=�ºÿÿH‹µÈþÿÿ1Àè•H�½àþÿÿèYòÿÿH�½ÐþÿÿH‰ÆèZD‰÷H‰ÞH‹•ÀþÿÿH‹�¸þÿÿM‰àL‹�ÈþÿÿAÿ×A‰Ä€½Øþÿÿ„™þÿÿH‹…ÐþÿÿH…À„‰þÿÿ1ÿÿÐé€þÿÿH�½àþÿÿèòÿÿI‰ÄH…À„DH‹…ÿÿÿH…ÀtH‹…ÿÿÿH‰…ÈþÿÿH‹µ(ÿÿÿH‹E˜H…ÀtH‹E�H‰…¸þÿÿH‹E°H‰…ÀþÿÿH�=~¯ÿÿ1ÀèÊH‹u°H�=’¹ÿÿ1Àè¸H‹µÿÿÿH�=�¹ÿÿ1Àè£H‹u�H�=!¶ÿÿ1Àè‘H�½àþÿÿèUñÿÿI‰ÇH�½ÐþÿÿH‰ÆèSD‰÷H‰ÞH‹•ÀþÿÿH‹�ÈþÿÿL‹…¸þÿÿAÿÔA‰ÄèMM…ÿuA�ü–€€uH…ÀtH�½ÿÿÿH�u°èJùÿÿ€½Øþÿÿ„mýÿÿH‹…ÐþÿÿH…À„]ýÿÿ1ÿÿÐéTýÿÿH‹µ(ÿÿÿH�=­¯ÿÿ1ÀèA¼–€€H�½ÿÿÿH�u°èùøÿÿé$ýÿÿH‹µ(ÿÿÿIƒÿÿtA¼„€€H�= ·ÿÿ1ÀèãéþüÿÿH�=g¶ÿÿ1Àè èëH�½àþÿÿèŸðÿÿH…ÀtD‰÷H‰ÞÿÐA‰ÄéÊüÿÿA¼„€€H‹µ(ÿÿÿH�=s·ÿÿ1Àè�éªüÿÿH‰Çè‚öÿÿëdH‰ÇèxöÿÿëZëXH‰Ã€½ØþÿÿtOH‹…ÐþÿÿH…ÀtC1ÿÿÐë=H‰ÇèNöÿÿë0ë.ë,H‰Ã€½Øþÿÿt#H‹…ÐþÿÿH…Àt1ÿÿÐëH‰Çè"öÿÿëëëH‰ÃH�½àþÿÿè­õÿÿë ëëëëH‰ÃH‹½PÿÿÿH�…`ÿÿÿH9Çu=H‹½pÿÿÿH�E€H9ÇuBH‹}�H�E H9ÇuGH‹}°L9ïuZdH‹%(H;EÐu_H‰ßèð›èË›H‹½pÿÿÿH�E€H9Çt¾è¶›H‹}�H�E H9Çt¹è¤›H‹}°L9ït´ë H‰ÃH‹}°L9ït¦èˆ›dH‹%(H;EÐt¡è¤›ÌÌÌÌUH‰åAVSI‰öH‰ûè^L‰3ÆCèH…ÀtH‹ H…Ét H‰ÇÿÑÆC[A^]ÃÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì(H‰óA‰þdH‹%(H‰EÐèèR„ÀtrH�}°è…FH‹U°H�=çºÿÿH�5Œ³ÿÿ1ÀèŒH‹}°H�EÀH9ÇtèÊšE…ö~+E‰ôL�='±ÿÿE1í„J‹4ëL‰ÿ1ÀèRIÿÅM9ìuêH�=H©ÿÿ1Àè<D‰÷H‰Þè!÷ÿÿ‰ÃèzdH‹%(H;EÐu=‰ØHƒÄ([A\A]A^A_]ÃH‰ÃH‹}°H�EÀH9ÇtèEšdH‹%(H;EÐuH‰ßèNšèYšÌÌÌÌÌÌÌÌÌUH‰åSPH�ÍH‰ßè›™H‹=D²H�­ÅH‰ÞHƒÄ[]é�™ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åI‰ÐH‰ñ1ö1Ò]é ÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ìøM‰ÄI‰ÍH‰•èþÿÿI‰ÿdH‹%(H‰EÐ…öA¾ DEöAöÆtRIƒtKH�5ѲÿÿL‰ÿL‰âèt„Àt5I‹7H�=V´ÿÿ1ÀèÿI‹4$H�=Ôªÿÿ1ÀèíL‰ïL‰þèB™³é@1ÛHƒ½èþÿÿtD‰ðƒàtH‹…èþÿÿHƒx•ÃH�EÀH‰E°HÇE¸ÆEÀH�E H‰E�HÇE˜ÆE „ÛtpH‹½èþÿÿ1öèÀt^H‹…èþÿÿH‹0H�=ü²ÿÿ1ÀèbL‰ïH‹µèþÿÿ賘H�5þ±ÿÿH‹½èþÿÿL‰âè�„À„‰I‹4$³H�=ªÿÿ1Àè!é]AöÆt(H�}�L‰îèŠ4„ÀtH‹u�I‹UH�=¥§ÿÿ1ÀèðëBAöÆtë'H‰ÃH‹½ÿÿÿL9ït“è·‘H‹½0ÿÿÿL9çt覑ëH‰ÃH‹}�H�E H9Çt"ëVH‰ÃH‹½pÿÿÿH�E€H9Çu1H‹}�H�E H9Çu6H‹}°H�EÀH9Çu;dH‹%(H;EÐu@H‰ßèn‘èI‘H‹}�H�E H9ÇtÊè7‘H‹}°H�EÀH9ÇtÅè%‘dH‹%(H;EÐtÀèA‘ÌUH‰åAWAVAUATSH�ìøH‰µèþÿÿH‰ûdH‹%(H‰EÐH‹7H�=}£ÿÿ1Àè„ WÀ)…ðþÿÿHÇ…ÿÿÿH�µðþÿÿH‰ßèp‹H‰�àþÿÿH�}€è°DL‹¥ðþÿÿH‹�øþÿÿI9Ü„L�½`ÿÿÿL�­ÿÿÿëf„IƒÄ I9Ü„ÞI‹4$H�=vŸÿÿ1Àè L‰ÿL‰æè¦L‰ïèNDL‰ÿL‰î1ÒèQZ„ÀtWH�}€L‰îèaGA‰Æ„ÀH�E€IEÅ‹H‰MˆH‹H‰E€H�}�H‰þH�… ÿÿÿHEðè�E„öH�}°H‰þH�…@ÿÿÿHEðèæ�H‹½@ÿÿÿH�…PÿÿÿH9Çtè¾�H‹½ ÿÿÿH�…0ÿÿÿH9Çtè¦�H‹½`ÿÿÿH�…pÿÿÿH9Ç„ÿÿÿèŠ�éÿÿÿH�½ÿÿÿè‰CH�}€H�µÿÿÿèiD‰ÃH‹½@ÿÿÿH�…PÿÿÿH9ÇtèO�H‹½ ÿÿÿH�…0ÿÿÿH9Çtè7�„Ût H‹…àþÿÿH‹0E1ÿH�=A¢ÿÿ1ÀèøéžH�½ÿÿÿH�u€èƒFH‹�àþÿÿH‹µÿÿÿH‰ßèMH‹3H�=J¬ÿÿ1ÀèŒH�57¨ÿÿH‰ßH‹•èþÿÿèÖA‰Ç„ÀtH‹…èþÿÿH‹0H�=> ÿÿ1ÀèWëH‹H�=ͪÿÿH�5ö§ÿÿ1ÀèmH‹½ÿÿÿH�… ÿÿÿH9ÇtèuŽH‹}°H�EÀH9ÇtècŽH‹}�H�E H9ÇtèQŽH‹�ðþÿÿL‹µøþÿÿL9óuGH…ÛtH‰ßè1ŽdH‹%(H;EÐ…àD‰øH�Äø[A\A]A^A_]Ãf„HƒÃL9ótH‹;HƒÃH9ßtëèæ�ëäH‹�ðþÿÿH…ÛuŸë¥ë9ë7H‰ÃH�½ÿÿÿèÓëWë$H‰ÃëYH‰ÃëTH‰ÃH‹½ÿÿÿH�… ÿÿÿH9Çu0ë3ëH‰Ãë,H‰ÃëH‰ÃH�½ÿÿÿè’H‹½`ÿÿÿH�…pÿÿÿH9Çtèj�H�}€èqH�½ðþÿÿè¥dH‹%(H;EÐuH‰ßè^�èi�ÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒìPH‰ódH‹%(H‰EØL�eÈL‰e¸L‹7L‹M…öu L‰ðLø…(L‰}�L‰àIƒÿrH�}¸H�u�1Ò蕌H‰E¸H‹M�H‰MÈM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úèxŒH‹E�H‰EÀH‹M¸ÆH�5W¨ÿÿH�}¸èèH�5àœÿÿH�}¸èØH�}¸è¿€„ÀtL�u¨L‰u˜H‹E¸L9àt$H‰E˜H‹EÈH‰E¨ë1ÛH‹u¸H�=¢œÿÿ1Àèë=A$AH‹EÀH‰E L‰e¸HÇEÀÆEÈH�}˜H‰Þèùúÿÿ‰ÃH‹}˜L9÷tèù‹H‹}¸L9çtèë‹dH‹%(H;EØ…†‰ØHƒÄP[A\A^A_]ÃdH‹%(H;EØuhH�=>©ÿÿè.ŒH‰ÃH‹}˜L9÷u H‹}¸L9çu3dH‹%(H;EØu8H‰ß袋è}‹H‹}¸L9çtÛë H‰ÃH‹}¸L9çtÍèa‹dH‹%(H;EØtÈè}‹ÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åSPH‰óH‰úH�=�¤ÿÿH�5u©ÿÿH‰Ùè¾Y„ÀtH‹H�=‰¥ÿÿH�5k¤ÿÿ‰Ã1Àè`‰ØHƒÄ[]ÃÌÌÌÌÌÌÌUH‰åSPH‰ûH‹0H�C@H9ÇtèÕŠH‹{HƒÃ HƒÄH9ßt[]齊[]ÃÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSPH‹L‹L9ût)I‰þëDHƒÃL9ûtH‹;HƒÃH9ßtëèvŠëäI‹H…ÛtH‰ßHƒÄ[A^A_]éZŠHƒÄ[A^A_]ÃÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åSPH�&½H‰ß軉H‹=d¢H�͵H‰ÞHƒÄ[]鯉ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSHƒìPdH‹%(H‰EèL�uØL‰uÈHÇEÐÆEØH�=’œÿÿH�uÈè¤_„ÀtCH‹}Èèç]…À~6莄Àt-H�}¨èTH‹u¨H�=× ÿÿ1Àè?H‹}¨H�E¸H9Çtè}‰H‹}ÈL9÷tèo‰dH‹%(H;Eèu5HƒÄP[A^]ÃëëH‰ÃH‹}ÈL9÷tèB‰dH‹%(H;EèuH‰ßèK‰èV‰ÌÌÌÌÌÌUH‰åAWAVATSHƒìPdH‹%(H‰EØL�uÈL‰u¸HÇEÀÆEȋ滅ۅ°†⻄Àt%E1ÿë�°†лAÿÇ„ÀtA÷ÇÿuèèS‰ëáH‹ò H‹H‰ »H�=û›ÿÿH�u¸èp^„Àt5H‹}¸H�5$˜ÿÿè,‰H…Àt%E1ÿH‰Ç1öº1ÉI‰Äè ‰L‰%Y»ëE1ÿëA·L�e¨L‰e˜HÇE ÆE¨H�=›ÿÿH�u˜è ^‰Á¸„Ét H‹}˜èG\‰ »H‹}˜L9çtèˆÆ»H‹u¸E„ÿtH�=‘Ÿÿÿ1ÀèÅH‹u¸L9ötH‰÷èÔ‡…Û”ÀdH‹ %(H;MØuTHƒÄP[A\A^A_]ÃH‰ÃëH‰ÃëH‰ÃH‹}˜L9çt蘇Æ™ºH‹}¸L9÷t胇dH‹%(H;EØuH‰ß茇藇H‰Çè?áÿÿÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSH�ìÐH‰µ(ÿÿÿH‰•0ÿÿÿH‰�8ÿÿÿL‰…@ÿÿÿL‰�Hÿÿÿ„Àt))…Pÿÿÿ)�`ÿÿÿ)•pÿÿÿ)]€)e�)m )u°)}ÀdH‹%(H‰Eèƒ=Ú¹Œ�H‰ûH�… ÿÿÿH‰EàH�EH‰EØH¸0H‰Eа†´¹„Àt'E1öë°† ¹AÿÆ„ÀtA÷Æÿuèè#‡ëáL‹5z¹H�UÐL‰÷H‰Þè;‡¿ L‰öè>‡Æ_¹dH‹%(H;Eèu H�ÄÐ[A^]Ãèg†H‰ÇèàÿÿÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSH�ìH‰ûH‰µØþÿÿH‰•àþÿÿH‰�èþÿÿL‰…ðþÿÿL‰�øþÿÿ„Àt8)…ÿÿÿ)�ÿÿÿ)• ÿÿÿ)�0ÿÿÿ)¥@ÿÿÿ)­Pÿÿÿ)µ`ÿÿÿ)½pÿÿÿdH‹%(H‰EØH�…ÐþÿÿH‰EÐH�MH‰MÈH¹0H‰MÀH‰E°(EÀ)E H‰E�)E€H�MÀ1ÿ1öH‰ÚèJ†ÿÀˆBLcøE…ÿtL‰ÿèB†I‰ÆH‰Ç1öL‰úèB†ëE1öH�M€L‰÷L‰þH‰Úè †°†¸„Àt.E1ÿëf.„°†¸AÿÇ„ÀtA÷Çÿuè胅ëáfffdH‹%I‰ÇH‹€øÿÿÿH…À„�L‰÷ÿЃ=²·tH¸musl-x64H‰AH¸linux-muH‰HÇCÆCH‹}ÈL9÷tè‹mëAH‹EÐH‰CdH‹%(H;Eèu4H‰ØHƒÄ0[A^]ÃH‰ÃH‹}ÈL9÷tèNmdH‹%(H;EèuH‰ßèWmèbmÌÌUH‰åH‰þH�=ê‚ÿÿ]éüBÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSHƒì0dH‹%(H‰EèL�uØL‰uÈHÇEÐÆEØH�=¨ˆÿÿH�uÈè´B„Àt7H‹}Èè÷@‰ÃH‹uÈH�=&‰ÿÿ1Àèéÿÿƒû”ÃH�ÃÿÿH�5{ÿÿHDðë H�5¯ƒÿÿ³H�=¬Œÿÿ1Àè2åÿÿH‹}ÈL9÷tètldH‹%(H;Eèu5‰ØHƒÄ0[A^]ÃëH‰ÃH‹}ÈL9÷tèGldH‹%(H;EèuH‰ßèPlè[lÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒìxH‰•hÿÿÿI‰ÿdH‹%(H‰EÐ…ötE1öëèÚþÿÿA‰ÆH�]ÀH‰]°HÇE¸ÆEÀIƒ„ H�}°L‰þèÌkH‹E°H‹u¸€|0ÿ/uHÿÎH�}°ºèlmH‹½hÿÿÿL‹L;tWI�GI‰L‹e°L‹m¸M…äu L‰áLé…÷L‰m�IƒýrH�u�L‰ÿ1ÒèkI‰H‹M�I‰OM…ít3IƒýuA¶ $ˆë$H�U°L‰þè¨E„öu7éH‰ÇL‰æL‰êèàjH‹E�I‰GI‹ÆH‹…hÿÿÿHƒ@ E„ö„ÏWÀ)…pÿÿÿHÇE€H�½pÿÿÿèþEL‹µpÿÿÿ„À„…H‹…xÿÿÿH‰…`ÿÿÿI9Æ„nH�] ëf�IƒÆ L;µ`ÿÿÿ„NH‰]�M‹&M‹nM…äu L‰àLè…ÁL‰mˆH‰ØIƒýrH�}�H�uˆ1ÒèjH‰E�H‹MˆH‰M M…ít$IƒýuA¶ $ˆë€H‰ÇL‰æL‰êèòiH‹EˆH‰E˜H‹M�ÆH�}�H�u°è…e„À…¥H‹½hÿÿÿL‹gL;gt[I�D$I‰$L‹m�L‹}˜M…íu L‰éLù…7L‰}ˆIƒÿrL‰çH�uˆ1ÒèriI‰$H‹MˆI‰L$M…ÿt+IƒÿuA¶MˆëL‰æH�U�èë+H‰ÇL‰îL‰úèEiH‹EˆI‰D$I‹ $ÆH‹…hÿÿÿHƒ@ H‹}�H9ß„«þÿÿèFié¡þÿÿL‹µpÿÿÿH‹�xÿÿÿI9ÞuWM…öH�]ÀtL‰÷èiH‹}°H9ßtèidH‹%(H;EÐ…HƒÄx[A\A]A^A_]Ãffff.„IƒÆI9ÞtI‹>IƒÆL9÷tëèÆhëäL‹µpÿÿÿM…öH�]Àu�ë•dH‹%(H;EÐ…¤H�=#†ÿÿèidH‹%(H;EÐ……H�=†ÿÿèôhdH‹%(H;EÐujH�=é…ÿÿèÙhH‰Çè1Âÿÿë I‰Æë&ëëI‰ÆëI‰ÆH‹}�H9ßtè0hH�½pÿÿÿètÝÿÿH‹}°H�EÀH9ÇtèhdH‹%(H;EÐuL‰÷èhè&hÌÌÌÌÌÌUH‰åAWAVATSHƒì0I‰÷I‰þdH‹%(H‰EØHÇFH‹ÆL�eÈL‰e¸HÇEÀÆEÈH�u¸èƒ=„ÀtH�}¸1öèÔ=„Àt³H�u¸L‰ÿè’gë1Ûë1ÛH‹U¸H�= €ÿÿL‰ö1ÀèÅãÿÿH‹}¸L9çtèWgdH‹%(H;EØu7‰ØHƒÄ0[A\A^A_]ÃH‰ÃH‹}¸L9çtè(gdH‹%(H;EØuH‰ßè1gèdH‹%(H;EØuCH�=1„ÿÿèkh1ÛdH‹%(H;EØu&‰ØHƒÄ[A\A^A_]ÃD‰#A‰³dH‹%(H;EØtÚè?fdH‹%(H;EØuìH�=Úƒÿÿè$hƒ;uD‰#dH‹ %(H;MØuÉH‰ÇèõeÌÌÌÌÌUH‰åAVSHƒì0H‰ûdH‹%(H‰EèHcÆH� »{H‹4ÁL�uÈL‰÷è»H� yÿÿA¸ L‰÷1ö1ÒèreH�SH‰H‹0H‰ÁHƒÁH9Ît H‰3H‹H‰SëH‹PH‰SH‰HÇ@Æ@H‹}ÈH�EØH9Çtè1edH‹%(H;Eèu8H‰ØHƒÄ0[A^]ÃH‰ÃH‹}ÈH�EØH9ÇtèedH‹%(H;EèuH‰ßè eèeÌÌÌÌÌUH‰åAWAVATSHƒìdH‹%(H‰EØL�gL‰'H…ö„¹I‰öH‰ûH‰÷èVdI‰ÇH‰EÐHƒøvH�uÐH‰ß1ÒèKdI‰ÄH‰H‹EÐH‰CM…ÿtIƒÿu A¶Aˆ$ëL‰çL‰öL‰úè*dH‹EÐH‰CH‹ ÆL‹sM…öt(L‹;E1äfff.„C¾<'èffCˆ'IÿÄM9æuêdH‹%(H;EØu+H‰ØHƒÄ[A\A^A_]ÃdH‹%(H;EØu H�=p�ÿÿè`dè dÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒìPH‰óI‰þdH‹%(H‰EØL�eÈL‰e¸ÇEÈx64HÇEÀ¿xèÍeˆEÈ¿6èÀeˆEÉ¿4è³eˆEÊH� âvÿÿH�}¸A¸ 1ö1ÒèFcL�}¨L‰}˜H‹H‰ÂHƒÂH9Ñt H‰M˜H‹2H‰u¨ë AL‰ùH‹pH‰u H‰HÇ@Æ@H‹}¸L9çt ècH‹M˜L9ùt+I�VI‹H9Ðt9I‹VI‰E AFH…Àt0H‰E˜H‰U¨ëJH‹U H…Òt.I‹>Hƒúu¶E¨ˆëI‰E AFL‰}˜L‰øëL‰þèhbH‹E I‰FI‹ÆH‹E˜HÇE ÆH‹}˜L9ÿtèlbI‹>H‰Þèqúÿÿ‰Á°„Éu&I‹VH� ¤yÿÿA¸ L‰÷1öè.bI‹>H‰ÞèCúÿÿdH‹ %(H;MØu5HƒÄP[A\A^A_]ÃH‰ÃH‹}¸L9çtèbdH‹%(H;EØuH‰ßèbèbÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0I‰öH‰ûdH‹%(H‰EØL�L‰?HÇGÆGH�}¸H‰ÖèÿëÿÿI‹FH‹MÀH�4HƒÆH‰ßè×cI‹6I‹VH‰ßèèaI‹FH…ÀtRI‹€|ÿ/tHH‹L‹sM�f¹L9øtH‹KI9ÌvA¸H‰ßL‰ö1Ò1ÉèrbH‹BÆ0/L‰cH‹BÆD0H�5tÿÿH�}¸¹HÇÂÿÿÿÿè±bH‹u¸H‹UÀH9ÂHCÐH‰ßèZaH¸ ÀHCHƒø vIH�5ƒÿÿº H‰ßè2aH‹}¸H�EÈH9Çtè°`dH‹%(H;EØ…ˆH‰ØHƒÄ0[A\A^A_]ÃdH‹%(H;EØuiH�=ª{ÿÿèaI‰ÆH‹;L9ÿtëL‹fM…ÿu L‰øLà…¬L‰e°L‰èIƒürH�u°H‰ß1Òèr_H‰H‹M°H‰KM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âèV_H‹E°H‰CH‹ ÆH�tÿÿH�}°L‰öèÄH‹u°H‰ßè¸áÿÿH‹}°H�EÀH9ÇtèF_dH‹%(H;EÐ…ŠH‰ØHƒÄ([A\A]A^A_]ÃdH‹%(H;EÐuiH�=–|ÿÿè†_I‰ÆH‹}°H�EÀH9ÇuH‹;L9ïu1dH‹%(H;EÐu6L‰÷è÷^èÒ^H‹;L9ïtÜë I‰ÆH‹;L9ïtÏè¸^dH‹%(H;EÐtÊèÔ^ÌÌÌÌUH‰åAWAVAUATSHƒìI‰ÖH‰ûdH‹%(H‰EÐL�oL‰/L‹>L‹fM…ÿu L‰øLà… L‰eÈL‰èIƒürH�uÈH‰ß1Òè^H‰H‹MÈH‰KM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âèæ]H‹EÈH‰CH‹ ÆL‰÷è¯]H¹ÿÿÿÿÿÿÿ?H+KH9ÁrJH‰ßL‰öH‰ÂèN^dH‹%(H;EÐuoH‰ØHƒÄ[A\A]A^A_]ÃdH‹%(H;EÐuNH�=2{ÿÿè"^dH‹%(H;EÐu3H�=¿xÿÿè^I‰ÆH‹;L9ïtèw]dH‹%(H;EÐuL‰÷è€]è‹]ÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì(I‰ÖH‰ûdH‹%(H‰EÐL�oL‰/L‹>L‹fM…ÿu L‰øLà…¬L‰e°L‰èIƒürH�u°H‰ß1Òè²\H‰H‹M°H‰KM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âè–\H‹E°H‰CH‹ ÆH�òpÿÿH�}°L‰öèþÿÿH‹u°H‰ßèøÞÿÿH‹}°H�EÀH9Çtè†\dH‹%(H;EÐ…ŠH‰ØHƒÄ([A\A]A^A_]ÃdH‹%(H;EÐuiH�=ÖyÿÿèÆ\I‰ÆH‹}°H�EÀH9ÇuH‹;L9ïu1dH‹%(H;EÐu6L‰÷è7\è\H‹;L9ïtÜë I‰ÆH‹;L9ïtÏèø[dH‹%(H;EÐtÊè\ÌÌÌÌUH‰åAWAVAUATSHƒì(H‰ËI‰ÖI‰÷I‰üdH‹%(H‰EÐH�}°L‰æL‰úè—ûÿÿH‹u°L�mÀL9ît+I�NI‹H9Èt8I‹NI‰6E¸AFH…Àt/H‰E°H‰MÀëFH‹U¸H…Òt*I‹>Hƒúu¶ˆëI‰6E¸AFL‰m°L‰èëè [H‹E¸I‰FI‹ÆH‹E°HÇE¸ÆH‹}°L9ïtè[H�}°L‰æL‰úè±ýÿÿH‹u°L�}ÀL9þt*H�KH‹H9Èt7H‹KH‰3E¸CH…Àt.H‰E°H‰MÀëEH‹U¸H…Òt)H‹;Hƒúu¶ˆëH‰3E¸CL‰}°L‰øëèhZH‹E¸H‰CH‹ ÆH‹E°HÇE¸ÆH‹}°L9ÿtèlZI‹6H‹H�=jÿÿ1Àè¸ÖÿÿdH‹%(H;EÐuHƒÄ([A\A]A^A_]ÃèeZÌÌÌÌÌUH‰åAVSHƒìpH‰ûdH‹%(H‰EèL�uÈL‰÷è æÿÿL‰÷èQÞÿÿ„Àt!H�CH‰H‹MÈH�UØH9ÑtkH‰ H‹EØH‰CëdH�}¨H�uÈèÏåÿÿH�}ˆH�u¨èÂåÿÿH�uˆH‰ßè¶åÿÿH‹}ˆH�E˜H9Çtè¤YH‹}¨H�E¸H9Çtè’YH‹}ÈH�EØH9Çtè€YëH‹EÐH‰CdH‹%(H;Eè…«H‰ØHƒÄp[A^]ÃH‰ÃH‹}ˆH�E˜H9Çu1H‹}¨H�E¸H9ÇuHH‹}ÈH�EØH9ÇuadH‹%(H;EèufH‰ßè0Yè YH‹}¨H�E¸H9ÇtÊëH‰ÃH‹}¨H�E¸H9Çt¸èçXH‹}ÈH�EØH9Çt³ëëH‰ÃH‹}ÈH�EØH9ÇtŸèÁXdH‹%(H;EètšèÝXÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒìXI‰ÖI‰÷H‰ûdH‹%(H‰EÐI¼ÀI½ÿÿÿÿÿÿÿ?H�GH‰EˆH‰HÇE°%H�u°1ÒèXH‰H‹M°H‰KHºplaunch?H‰Prxÿÿ@WxÿÿH‰KÆM…ÿ„©A€?„ŸH‹CLàHƒÀHƒø ‚æH�5+yÿÿº H‰ßèEXL‰ÿè}WL‰éH+KH9Á‚ºH‰ßL‰þH‰ÂèXM…ötwA€>tqH‹CLàHƒÀHƒø‚ŒH�5kÿÿºH‰ßèëWL‰÷è#WH‰ÂL‰èH+CH9Ðs&é\H‹CLàHƒÀHƒø†GºL�5mÿÿH‰ßL‰öè£WH‹CLàHƒÀHƒø‚üH�5¹qÿÿºH‰ßèzWH‹CLàHƒø‚×H�5gjÿÿºH‰ßèUWH‹CLàHƒÀHƒø†®H�5[sÿÿºH‰ßè,WL�}ÀL‰}°HÇE¸ÆEÀH�=ilÿÿH�u°èx,L�e L‰e�„ÀtH‹u°L9þtOH‰u�H‹EÀH‰E ëMH¸musl-x64I‰D$H¸linux-muI‰$HÇE˜ÆE®H‹}°L9ÿt%è>VH‹u�H‹U˜ëAA$L‰æH‹U¸H‰U˜ëºL‰æH‰ßè~VH‹}�L9çtèVH�}°è‡;Hƒ}¸ubH�E H‰E�H¹linux-muH‰M fÇE¨slHÇE˜ ÆEªH‹U°H‰ fÇBslH‹M˜H‰M¸H‹U°Æ H‹M�HÇE˜ÆH‹}�H9ÇtèŽUH‹CHƒàüIƒÅýL9è„‘H�5 rÿÿºH‰ßèÕUH‹u°H‹U¸H‰ßèÅUH‹}°L9ÿtèGUdH‹%(H;EÐ…¶H‰ØHƒÄX[A\A]A^A_]ÃdH‹%(H;EÐ…‘H�=;pÿÿè“UdH‹%(H;EÐuvH�= pÿÿèxUdH‹%(H;EÐu[H�=pÿÿè]Uë%I‰ÆH‹}�L9çuëëI‰ÆH‹}°L9ÿt èªTëëI‰ÆH‹;H;}ˆtè•TdH‹%(H;EÐuL‰÷èžTè©TÌÌÌÌÌÌÌÌÌUH‰åAWAVSPH‰ûL�L‰?ÇG9.0.fÇG5HÇGH�=\†¾@è–VH…Àt@I‰ÆH�5ecÿÿºH‰ßèŠTL‰÷èÂSH¹ÿÿÿÿÿÿÿ?H+KH9ÁrH‰ßL‰öH‰ÂèaTH‰ØHƒÄ[A^A_]ÃH�=oÿÿègTI‰ÆH‹;L9ÿtèÇSL‰÷èßSÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒìdH‹%(H‰EØL�gL‰'H…ö„¹I‰öH‰ûH‰÷è&SI‰ÇH‰EÐHƒøvH�uÐH‰ß1ÒèSI‰ÄH‰H‹EÐH‰CM…ÿtIƒÿu A¶Aˆ$ëL‰çL‰öL‰úèúRH‹EÐH‰CH‹ ÆL‹sM…öt(L‹;E1äfff.„C¾<'èfUCˆ'IÿÄM9æuêdH‹%(H;EØu+H‰ØHƒÄ[A\A^A_]ÃdH‹%(H;EØu H�=@pÿÿè0SèÛRÌÌÌÌÌÌÌÌÌÌÌUH‰å¶õ„I‰×HÁøHƒøH‰ÁHƒÑL�,HºÿÿÿÿÿÿÿI9ÕLCêHÁLBêL‰ãH‰u H)óHÁûM…ítL‰ïHÁçèîRë1ÀHÁãH� HƒÁH‰E¨H‰MÀH‰ I‹M‹H‰E°H…Àu H‹E°Lø…ÓH‹E¨H�L‰}ÈIƒÿr+H�uÈH‰×H‰U¸1ÒèQQH‹u¸H‰H‹MÈH‹UÀH‰ H‰EÀH‰òM…ÿt-IƒÿuH‹E°¶H‹MÀˆëH‰U¸H‹}ÀH‹u°L‰úèQH‹U¸H‹EÈL‹}¨I‰DH‹ ÆL‰ûH‹} L9çtdI�_H�GëAfff.„H‰KðH‹H‰ H‹HøH‰KøH‰@ðHÇ@øÆHƒÃ H�H HƒÀL9àH‰ÈtH‰[ðH‹HðH9Èu¾ëÀHƒÃðM9愱HƒÃ0IƒÄëKffffff.„H‰CðI‹$H‰I‹D$øH‰CøM‰d$ðIÇD$øAÆ$HƒÃ I�D$ IƒÄM9ôI‰ÄtH‰[ðI‹D$ðI9Äu¶ë¹HƒÃðH…ÿtè@PH‹E˜L‰8H‰XIÁåMýL‰hdH‹%(H;EÐ…”HƒÄH[A\A]A^A_]ÃHƒÃ H…ÿuºë½dH‹%(H;EÐukH�=ÖeÿÿèrPdH‹%(H;EÐuPH�=WmÿÿèGPH‰ÇèÿOH‹}¨è¶OdH‹%(H;EÐu$è"RH‰Ãè*RdH‹%(H;EÐuH‰ßè£Oè®OH‰ÇèV©ÿÿÌÌÌÌÌÌUH‰åSPH�l‚H‰ßèëNH‹=”gH�ýzH‰ÞHƒÄ[]éßNÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì(M‰ÏH‰ûdH‹%(H‰EЉ7‰W‰OL�w L‰wM‹ M‹hM…äu L‰àLè… H�CH‰E¸L‰mÈL‰ðIƒýr$H�uÈL‰}ÀL‹}¸L‰ÿ1ÒèyNI‰L‹}ÀH‹MÈI‰M…ítIƒýu A¶ $ˆëH‰ÇL‰æL‰êèYNH‹EÈH‰CH‹KÆL�c@L‰c0M‹/M‹M…íu L‰èLø…›L‰}ÈIƒÿr$L‰uÀL�s0H�uÈL‰÷1ÒèùMI‰H‹MÈI‰ $I‰ÄM…ÿtIƒÿu A¶EAˆ$ëL‰çL‰îL‰úè×MH‹EÈH‰C8H‹K0ÆdH‹%(H;EÐuuHƒÄ([A\A]A^A_]ÃdH‹%(H;EÐuWH�=Nkÿÿè>NL‰uÀdH‹%(H;EÐu8H�=/kÿÿèNH‰ÃH‹E¸H‹8H;}ÀtèŠMdH‹%(H;EÐuH‰ßè“MèžMÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSHƒì0dH‹%(H‰EèL�uØL‰uÈHÇEÐÆEØL�MÈèûýÿÿH‹}ÈL9÷tèMdH‹%(H;Eèu1HƒÄ0[A^]ÃH‰ÃH‹}ÈL9÷tèôLdH‹%(H;EèuH‰ßèýLèMÌÌÌÌÌÌÌÌUH‰åAWAVSHƒìHdH‹%(H‰EàL�uÐL‰uÀHÇEÈÆEÐL�}°L‰} HÇE¨ÆE°L�EÀL�M èQýÿÿH‹} L9ÿtèsLH‹}ÀL9÷tèeLdH‹%(H;EàuYHƒÄH[A^A_]ÃH‰ÃH‹} L9ÿu H‹}ÀL9÷u%dH‹%(H;Eàu*H‰ßè?LèLH‹}ÀL9÷tÛè LdH‹%(H;EàtÖè(LÌÌÌÌÌÌÌÌUH‰åAWAVSHƒìHdH‹%(H‰EàL�uÐL‰uÀHÇEÈÆEÐL�}°L‰} HÇE¨ÆE°L�EÀL�M ¾ÿÿÿÿºÿÿÿÿ¹ÿÿÿÿèbüÿÿH‹} L9ÿtè„KH‹}ÀL9÷tèvKdH‹%(H;EàuYHƒÄH[A^A_]ÃH‰ÃH‹} L9ÿu H‹}ÀL9÷u%dH‹%(H;Eàu*H‰ßèPKè+KH‹}ÀL9÷tÛèKdH‹%(H;EàtÖè9KÌÌÌÌÌÌÌÌÌUH‰åè…À”À]ÃUH‰åAVSHƒìPdH‹%(H‰Eè‹9…„‹F9Gu|‹F9GutHƒ„’¸ÿÿÿÿHƒ~tjHƒÇHƒÆH‹L‹¶JE¶H»D8Éu'Aº„ɄӀù.IDÚB¶ G¶ IÿÂD8Étß„ÉtF€ù.uG¸E„Étë;ŸÀ¶À�EÿÿÿÿdH‹ %(H;Mè…MHƒÄP[A^]Ã1ÀHƒ~•ÀëØA€ù.tÒH�EÈH‰ùH‰ÇI‰öH‰ÎH‰ÚèWH�}¨L‰öH‰ÚèHÇE¤H�}ÈH�u¤èôâÿÿ‰ÃÇE H�}¨H�u èÞâÿÿ‰Ù Á€ùu‹M 1À;M¤ƒÐÿƒÈë%1ÀéaÿÿÿÀûu ¶ÀÿȃÈë H�}ÈH�u¨è LH‹}¨H�M¸H9Ït ‰ÃèlI‰ØH‹}ÈH�MØH9Ï„ÿÿÿ‰ÃèRI‰ØéÿÿÿëH‰ÃH‹}ÈH�EØH9ÇtëFH‰ÃH‹}¨H�E¸H9Çu$H‹}ÈH�EØH9Çu)dH‹%(H;Eèu.H‰ßè#IèþHH‹}ÈH�EØH9Çt×èìHdH‹%(H;EètÒèIÌÌÌÌÌÌÌÌUH‰åè×ýÿÿ…À•À]ÃUH‰åèÇýÿÿÁè]ÃÌÌUH‰åè·ýÿÿ…ÀŸÀ]ÃUH‰åè§ýÿÿ…ÀžÀ]ÃUH‰åè—ýÿÿ…À™À]ÃUH‰åAWAVAUATSHƒì8I‰öH‰ûdH‹%(H‰EÐD‹.E‰ïA÷ßEHýA¼Aƒÿ rOA¼¸Y·ÑD‰ùDƒùcv.�ùçv,�ù'r'‰ÊH¯ÐHÁê-AƒÄ�ùŸ†‰ÑwÓAƒÄýë AƒÄþëAÿÌAÁíD‰îDæH�CH‰E¨H‰H‰ßº-è‚JH‹LèAƒÿdrzAƒÄþH�»kÿÿff.„A�t$D‰ùHiÉ…ëQHÁé%kùdE‰øA)øB¶I$H9ÂHBÂH…Ûu H‰ÙHÁ… H‰EÐHƒør"H�uÐL‰÷1ÒI‰ÇèÖBH‰ÇL‰øI‰>H‹MÐI‰NH…À„‚Hƒøuf¶ˆëuI‹L$I‰ÏI)ß‚�I�FI‰I‹$HÓu HÊ…�L‰}ÐIƒÿrH�uÐL‰÷1ÒèmBI‰H‹MÐI‰NM…ÿt#Iƒÿu¶ ˆëH‰ÞH‰Âë H‰ÇH‰ÞL‰úèJBH‹EÐI‰FI‹ÆdH‹%(H;EØuOHƒÄ[A\A^A_]ÃdH‹%(H;EØu3H�=þQÿÿH�5¶XÿÿH‰Ú1ÀèèCdH‹%(H;EØu H�=�_ÿÿè�Bè8BÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ì¸A‰ÕI‰÷I‰þdH‹%(H‰EÐE1ä¾.1Òè'BHƒøÿ„:H‰ÃD‰­0ÿÿÿL‰½ ÿÿÿÇ…<ÿÿÿM‹fL�}�L‰}€M‹.I9ÄLCàM…íu L‰èLà…\L‰¥`ÿÿÿL‰øIƒür!H�}€H�µ`ÿÿÿ1ÒèAH‰E€H‹�`ÿÿÿH‰M�M…ätIƒüu A¶MˆëH‰ÇL‰îL‰âèø@H‹…`ÿÿÿH‰EˆH‹M€ÆH�}€H�µ<ÿÿÿè%ÚÿÿA‰ÄH‹}€L9ÿtèô@E„ä„eHƒûr I‹€80„SL�kL‰÷¾.L‰êè%AHƒøÿ„5Ç…8ÿÿÿM‹fI9܆¤H‰…(ÿÿÿI‰ÇM)ïH�M�H‰M€I‹LëM)ìM9üMCçH…Ûu H‰ØLà…OL‰¥`ÿÿÿH‰ÈIƒür!H�}€H�µ`ÿÿÿ1Òè @H‰E€H‹�`ÿÿÿH‰M�M…ätIƒüu¶ ˆëH‰ÇH‰ÞL‰âèí?H‹…`ÿÿÿH‰EˆH‹M€ÆH�}€H�µ8ÿÿÿèÙÿÿ‰ÃH‹}€H�E�H9Çtèæ?„Û„XIƒÿrI‹B€<(0„DÇ…4ÿÿÿL‹½(ÿÿÿM�oL‰÷L‰îèªØÿÿHƒøÿ„L€½0ÿÿÿ… I‰ÄH‰ÃL)ëL�}€L‰ÿL‰öL‰êH‰Ùè5BH�µ4ÿÿÿL‰ÿè†ØÿÿA‰ÇH‹}€H�E�H9ÇtèQ?E„ÿ„ÂHƒûrI‹B€<(0„®L‰÷¾+M‰çL‰âè�?H‰ÃH‰ÁL)áHƒøÿHDÈL�¥`ÿÿÿL‰çL‰öL‰úè»AL‰çèS„ÀL‹¥ ÿÿÿL�m�„¿L�½PÿÿÿL‰½@ÿÿÿHÇ…HÿÿÿÆ…PÿÿÿHƒûÿ„H�}€L‰öH‰ÚHÇÁÿÿÿÿè`AH‹u€L9î„zH‹…@ÿÿÿL9ø„‡H‹�PÿÿÿH‰µ@ÿÿÿEˆ…HÿÿÿH…À„wH‰E€H‰M�é’I‹^L9û†uL�e�L‰e€I‹I‰ÇMïu HØ… L)ëH‰�`ÿÿÿL‰àHƒûr!H�}€H�µ`ÿÿÿ1ÒèÆ=H‰E€H‹�`ÿÿÿH‰M�H…ÛtHƒûuA¶ˆëH‰ÇL‰þH‰Úè¦=H‹…`ÿÿÿH‰EˆH‹M€ÆH�}€H�µ4ÿÿÿèÓÖÿÿ‰ÃH‹}€L9çL‹½(ÿÿÿtèœ=„ÛtI‹B€|8t2B€<(0u+E1ädH‹%(H;EÐ…CD‰àH�ĸ[A\A]A^A_]˵<ÿÿÿ‹•8ÿÿÿ‹�4ÿÿÿH�]€H‰ßèmðÿÿH‹½ ÿÿÿH‰ÞèH‰ßè6²ÿÿA´ëžE1äéÙH‹UˆH…Òt4H‹½@ÿÿÿHƒúu"¶ˆë H‰µ@ÿÿÿEˆ…HÿÿÿL‰m€L‰èëè¬<H‹EˆH‰…HÿÿÿH‹�@ÿÿÿÆH‹E€HÇEˆÆH‹}€L9ïtè©<H�½@ÿÿÿèý„ÀtE‹µ<ÿÿÿ‹•8ÿÿÿ‹�4ÿÿÿH�}€L�…`ÿÿÿL��@ÿÿÿè@íÿÿH�]€L‰çH‰ÞèAH‰ßèi±ÿÿA´ëE1äH‹½@ÿÿÿL9ÿtè@<H‹½`ÿÿÿH�…pÿÿÿH9Ç„¦þÿÿè$<éœþÿÿdH‹%(H;EÐ…ßH�=�Yÿÿè€<dH‹%(H;EÐ…ÀH�=«KÿÿH�5cRÿÿL‰êL‰á1Àè’=dH‹%(H;EÐ…’H�=}KÿÿH�55RÿÿL‰êH‰Ù1Àèd=ëëH‰ÃH‹½@ÿÿÿL9ÿt èŒ;ëH‰ÃH‹½`ÿÿÿH�…pÿÿÿëëH‰ÃH‹}€L9çu ë#H‰ÃH‹}€H�E�H9ÇuëH‰ÃH‹}€L9ÿtèD;dH‹%(H;EÐuH‰ßèM;èX;ÌÌÌÌÌÌÌÌUH‰åAVSH‰ø‹N‰OH‹H‰H‹NH�V H9Ñt7L�@ H‹xL9ÇtDL‹@ H‰HH‹NH‰HH‹N H‰H H…ÿt;H‰~L‰F H‰úë\H‹VH…Òt?H‹xHƒúu!¶ ˆë.H‰HH‹NH‰HH‹N H‰H H‰Vë(H‰óH‰ÎI‰ÆèT:H‰ÞL‰ðH‹NH‰HH‹PÆ H‹VHÇFÆH‹N0H�V@H9Ñt7L�@@H‹x0L9ÇtDL‹@@H‰H0H‹N8H‰H8H‹N@H‰H@H…ÿt;H‰~0L‰F@H‰úë\H‹V8H…Òt?H‹x0Hƒúu!¶ ˆë.H‰H0H‹N8H‰H8H‹N@H‰H@H‰V0ë(H‰óH‰ÎI‰Æè©9H‰ÞL‰ðH‹N8H‰H8H‹P0Æ H‹V0HÇF8Æ[A^]ÃÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì8dH‹%(H‰EÐHƒ„)H‰ûH‹¶ƒø-t ƒø+…‰E¤A½ºH‰ß¾.è¥9Hƒøÿ„ˆI‰ÄL�}À@H‹KH‰ÈL)è‚ÀM‰æM)îL‰}°L+L9ðLBðM…íu L‰èLð…ÂL‰u¨L‰øIƒþrH�}°H�u¨1ÒèŸ8H‰E°H‹M¨H‰MÀM…öt,IƒþuA¶Mˆëffffff.„H‰ÇL‰îL‰òèr8H‹E¨H‰E¸H‹M°ÆHƒ}¸„—H‹E°¶„É„xH�P‰Îë@€þ9wz¶2HÿÂ@„öt-@€þA|@€þ[ré@€Æ…@€þæsßëW€@€þ/Ê@€þ-tÊëB€}¤+t,€ù0u'€xt!¾H�}°èÑÿÿHƒøÿtffff.„A¶H‹}°L9ÿuëf�E1öH‹}°L9ÿtèß7E„ö„�M‰åIÿÅH‰ß¾.L‰êè 8I‰ÄHƒøÿ…ƒþÿÿH‹KI‰ÎM)î‚CL�}ÀL‰}°H‹IÅu HÈ…QL‰u¨L‰øIƒþrH�}°H�u¨1Òè.7H‰E°H‹M¨H‰MÀM…öt+IƒþuA¶Mˆë³éÇ1ÛéÀH‰ÇL‰îL‰òè7H‹E¨H‰E¸H‹M°ÆHƒ}¸„‡H‹E°¶„ÉtoH�P‰Îë@@€þ9wj¶2HÿÂ@„öt-@€þA|@€þ[ré@€Æ…@€þæsßëG€@€þ/Ê@€þ-tÊë2€}¤+t€ù0u€xtH�}°¾è£ÏÿÿHƒøÿt ³H‹}°L9ÿu ë1ÛH‹}°L9ÿtè€6dH‹%(H;EÐu}‰ØHƒÄ8[A\A]A^A_]ÃdH‹%(H;EÐu]H�=FÿÿH�5ÇLÿÿL‰ê1Àèù7dH‹%(H;EÐu6H�=®Sÿÿèž6ëH‰ÃH‹}°L9ÿtè 6dH‹%(H;EÐuH‰ßè6è6ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰å]éÖóÿÿÌÌÌÌÌÌUH‰åSPH�ÍhH‰ßèK5H‹=ôMH�]aH‰ÞHƒÄ[]é?5ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSH�ìˆH‰ûdH‹%(H‰Eà1ÿè98H‰…`ÿÿÿH�½`ÿÿÿè68H�ÒMÿÿL�µpÿÿÿ¾dL‰÷H‰Áè(8L�{L‰;L‰÷èÙ4I‰ÆH‰…hÿÿÿHƒør"H�µhÿÿÿH‰ß1ÒèÈ4I‰ÇH‰H‹…hÿÿÿH‰CM…öt$Iƒþu ¶…pÿÿÿAˆëH�µpÿÿÿL‰ÿL‰òèž4H‹…hÿÿÿH‰CH‹ ÆdH‹%(H;EàuH‰ØH�Ĉ[A^A_]ÃèÇ4ÌÌÌÌÌÌÌUH‰åAVSI‰þH‹?¾Àº$1Àèr7‰Ãƒøÿt ‰ßèt7ëI‹6H�=ˆVÿÿH�³Kÿÿ1Àè³ÿÿƒûÿ•À[A^]ÃÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åº¹]é ÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ì˜A‰ÏA‰ÔI‰õI‰þdH‹%(H‰EÐH‹?1ö1Àè×6ƒøÿt7‰ÃH�µ@ÿÿÿ‰Çèâ6…ÀtCL�%æLÿÿM‹6è¯5‹E1ÿL‰çL‰ö1Àè}­ÿÿëLI‹è“5‹H�=MÿÿE1ÿH‰Þ1Àè]­ÿÿë3H‹µpÿÿÿM…ítI‰u1ÿD‰âD‰ùA‰ØE1Éèˆ6I‰ÇHƒøÿt+‰ßèX6dH‹%(H;EÐu!L‰øH�Ę[A\A]A^A_]ÃL�%ïIÿÿéaÿÿÿèC3ÌÌÌUH‰åº¹]éìþÿÿÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0I‰þdH‹%(H‰EØHÇGH‹Æ1ÿ1öèú5H‰ÃH…Àt,M‹~H‰ßèV2L‰÷1öL‰úH‰ÙI‰Àèƒ2H‰ßèÛ5é­è�4‹8ƒÿ„�èÑ5L�eÈL‰e¸H…À„©I‰ÆH‰Çè2I‰ÇH‰E°L‰àIƒÿvH�}¸H�u°1Òèö1H‰E¸H‹M°H‰MÈM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úèÙ1H‹E°H‰EÀH‹M¸ÆH‹u¸H�=ÒMÿÿ1ÀèÇ«ÿÿH‹}¸L9çtèÙ1H…Û•ÀdH‹ %(H;MØuPHƒÄ0[A\A^A_]ÃdH‹%(H;EØu4H�=,Oÿÿè2H‰ÃH‹}¸L9çtè‹1dH‹%(H;EØuH‰ßè”1èŸ1ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ìhI‰ÌH‰•xïÿÿH‰óI‰ÿdH‹%(H‰EÐL�­ ïÿÿL‰­�ïÿÿHÇ…˜ïÿÿÆ… ïÿÿèº0H‰ÁHÁé>…zH�½�ïÿÿL‰þH‰Âè[1H‹½�ïÿÿ¾èJ4I‰ÆH…Àt}L‰÷H‰ÞèG4I‰ÇH…ÀuèJ4H�=QÿÿH‰ÞH‰Â1ÀèV©ÿÿH�µ°ïÿÿL‰ÿè74…ÀA•Ç„œH‹…xïÿÿL‰0H‹�°ïÿÿM‹t$H‰ßè0L‰ç1öL‰òH‰ÙI‰ÀèJ0é‡L�µÀïÿÿL‰µ°ïÿÿM…ÿ„ÖL‰ÿèæ/H‰ÃH‰E°L‰ðHƒûv$H�½°ïÿÿH�u°1ÒèÔ/H‰…°ïÿÿH‹M°H‰�ÀïÿÿH…Ût)HƒûuA¶ˆëL‰÷è—3éH‰ÇL‰þH‰Úè¤/H‹E°H‰…¸ïÿÿH‹�°ïÿÿÆH‹½°ïÿÿ¶L9÷tèª/€û/„žL‰¥pïÿÿHÇ…ˆïÿÿHÇ…€ïÿÿH��ÀïÿÿH‰�°ïÿÿH¸/proc/seH‰…ÀïÿÿH¸elf/mapsH‰…ÇïÿÿHÇ…¸ïÿÿÆ…ÏïÿÿH�5–JÿÿH‰ßèã/I‰ÄH‹½°ïÿÿH9ßtè/M…ä„H�EÀH‰E°HÇE¸ÆEÀH��°ïÿÿL�m°€H�½ˆïÿÿH�µ€ïÿÿL‰âèš2Hƒøÿ„ÒH‹½ˆïÿÿH�5„BÿÿH‰Ú1Àèˆ2ƒøuÃL‹u¸H‰ßèW.L‰ï1öL‰òH‰ÙI‰Àè„.L‰ï¾/HÇÂÿÿÿÿè 0HƒøÿtŠI‰ÆL‰ÿè.L‰ïL‰þL‰òH‰Áè>2Hƒøÿ„dÿÿÿL‰çè<2H‹½ˆïÿÿè�1H‹}°¾L�­ ïÿÿè›1H…ÀH‹½pïÿÿ„†H‹�xïÿÿH‰A·H�u°è.ë#E1ÿë0L‰çèæ1H‹½ˆïÿÿè:1E1ÿL�­ ïÿÿH‹}°H�EÀH9ÇtèÎ-H‹½�ïÿÿL9ïtè½-dH‹%(H;EÐ…“D‰øH�Äh[A\A]A^A_]ÃE1ÿH‹}°H�EÀH9Çu²ëµdH‹%(H;EÐu]H�= Hÿÿèø-dH‹%(H;EÐuBH�=ÝJÿÿèÍ-ë9ëëëëë/H‰ÃH‹½�ïÿÿH�… ïÿÿH9Çt<è&-dH‹%(H;EÐt7èB-H‰ÃH‹}°H�EÀH9Çu*H‹½�ïÿÿH�… ïÿÿH9ÇuÄdH‹%(H;EÐuÉH‰ßèø,èÓ,H‹½�ïÿÿH�… ïÿÿH9ÇtÑë“ÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åH‹€8/”À]ÃÌUH‰åAVSH‰óè0H…Àt[A^]ÃI‰Æè0H�=ÙLÿÿH‰ÞH‰Â1Àè¥ÿÿL‰ð[A^]ÃÌÌÌUH‰åAWAVSPI‰÷I‰þH‹?¾è³/H‰ÃI‰H…ÀuM‹6èÀ/H�=2BÿÿL‰öH‰Â1Àèü¥ÿÿH…Û•ÀHƒÄ[A^A_]ÃÌÌÌÌÌÌÌÌÌÌÌUH‰åè§/…Àtè~/H�=±=ÿÿH‰Æ1À]鬪ÿÿ]ÃÌÌÌÌÌÌÌÌÌÌUH‰å]éÆ/ÌÌÌÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàHÇGH‹ÆL�}ÐL‰}ÀHÇEÈÆEÐH�=aIÿÿè…/H…ÀtFH‰ÇI‰Æè+H�}À1ö1ÒL‰ñI‰ÀèB+Hƒ}Èt!H�}À1öè`!„ÀtH‹uÀH�=ðIÿÿ1ÀèÚ£ÿÿH‹}À1öè?/…ÀtgH‹uÀH�=âCÿÿ1Àè¹£ÿÿHÇEÈH‹EÀÆH�5�KÿÿH�}ÀèJ­ÿÿH�5ðCÿÿH�}Àè:­ÿÿH‹}À1öèï.…ÀtE1öH‹uÀH�=µFÿÿ1Àèf£ÿÿë3H‹}À¾èÆ.…ÀtH‹uÀH�=uIÿÿ1Àè@£ÿÿA¶H�uÀH‰ßè‘*H‹}ÀL9ÿtès*dH‹%(H;Eàu6D‰ðHƒÄ([A^A_]ÃH‰ÃH‹}ÀL9ÿtèE*dH‹%(H;EàuH‰ßèN*èY*ÌÌÌÌÌÌÌÌÌUH‰åAWAVSPH‰óHÇFH‹Æè.H…Àt"L‹sH‰ÇI‰Çèœ)H‰ß1öL‰òL‰ùI‰ÀèÉ)Hƒ{•ÀHƒÄ[A^A_]ÃÌÌÌÌÌÌUH‰å]éÖÌÌÌÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàHÇGH‹ÆL�}ÐL‰}ÀHÇEÈÆEÐH�=¶Dÿÿèu-H…ÀtFH‰ÇI‰Æè)H�}À1ö1ÒL‰ñI‰Àè2)Hƒ}Èt!H�}À1öèP„ÀtH‹uÀH�=uJÿÿ1ÀèÊ¡ÿÿH‹}À1öè/-…ÀtfH‹uÀH�=qJÿÿ1Àè©¡ÿÿHÇEÈH‹EÀÆH�5}IÿÿH�}Àè:«ÿÿH�58ÿÿH�}Àè*«ÿÿH‹}À1öèß,…Àt1ÛH‹uÀH�=SEÿÿ1ÀèW¡ÿÿëDH‹}À¾è·,…ÀtH‹uÀH�=!?ÿÿ1Àè1¡ÿÿH�uÀH‰ßè…(³H‹uÀH�= <ÿÿ1Àè¡ÿÿH‹}ÀL9ÿtèS(dH‹%(H;Eàu5‰ØHƒÄ([A^A_]ÃH‰ÃH‹}ÀL9ÿtè&(dH‹%(H;EàuH‰ßè/(è:(ÌÌÌÌÌÌÌÌÌÌUH‰åAVSI‰þ1Û1öè „ÀtI‹>¾èü+…À”ÉØ[A^]ÃUH‰åAWAVATSH‰ûHÇGH‹ÆH�=RCÿÿè¸+H…Àt"L‹sH‰ÇI‰ÇèD'H‰ß1öL‰òL‰ùI‰Àèq'Hƒ{t2H‰ß1öè�„ÀtH‹;¾è+…À„µH‹3H�=XFÿÿé’èC)I‰ÆÇf.„è[+‰Çèd+H…ÀuAƒ>téH�=ïCÿÿ1Û1Àèé ÿÿëZI‰ÄL‹p L‹{L‰÷è¤&H‰ß1öL‰úL‰ñI‰ÀèÑ&H‰ß1öè÷„ÀtH‹;¾èæ*…Àt I‹t$ H�=üHÿÿ1Û1Àè� ÿÿ‰Ø[A\A^A_]óëñÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0H‰ûdH‹%(H‰EØèœþÿÿ„À„#H�5 <ÿÿH‰ß赨ÿÿH‰ß1öèk„ÀtH‹;¾èZ*…ÀtJH‹;¾Àèy*…Àt9è (‹8ƒÿu5E1öH‰ß1öè,„À„ÆH‹;¾è*…ÀA”Æé®A¶é¦H‹è<)L�eÈL‰e¸H…À„¬I‰ÆH‰Çèp%I‰ÇH‰E°L‰àIƒÿvH�}¸H�u°1Òèa%H‰E¸H‹M°H‰MÈM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úèD%H‹E°H‰EÀH‹M¸ÆH‹U¸H�=QCÿÿH‰Þ1Àè/ŸÿÿH‹}¸L9çtèA%E1ödH‹%(H;EØuSD‰ðHƒÄ0[A\A^A_]ÃdH‹%(H;EØu4H�=”Bÿÿè„%H‰ÃH‹}¸L9çtèó$dH‹%(H;EØuH‰ßèü$è%ÌÌÌÌÌÌÌUH‰å1À]ÃÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒìhA‰öH‰ûdH‹%(H‰EÐL�gL‰'H¸/etc/dotH‰GÇGtnetHÇG ÆGL�}ÀL‰}°HÇE¸ÆEÀH�=9ÿÿH�u°è°Ñÿÿ„Àt H�u°H‰ßèP$D‰÷èèµÿÿH�½pÿÿÿH‰ÆèyÐÿÿH� ?=ÿÿH�½pÿÿÿA¸1ö1Òèü#L�m L‰m�H‹0H‰ÁHƒÁH9Ît H‰u�H‹H‰U ë AEL‰îH‹PH‰U˜H‰HÇ@Æ@H‰ßè¦ÿÿH‹}�L9ïtè°#H‹½pÿÿÿH�E€H9Çtè›#H‹}°L9ÿtè�#dH‹%(H;EÐ…ËH‰ØHƒÄh[A\A]A^A_]ÃI‰ÆH‹}�L9ïu8H‹½pÿÿÿH�E€H9ÇuRH‹}°L9ÿusH‹;L9çuxdH‹%(H;EÐu}L‰÷èD#è#H‹½pÿÿÿH�E€H9ÇtÃëI‰ÆH‹½pÿÿÿH�E€H9Çt®èõ"H‹}°L9ÿt©ëI‰ÆH‹}°L9ÿt›ë I‰ÆH‹}°L9ÿt�èË"H‹;L9çtˆè¾"dH‹%(H;EÐtƒèÚ"ÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ìI‰×I‰ôH‰ûdH‹%(H‰EÐÆH‹?H�5Ä=ÿÿè#H…À„I‰ÆH�…àþÿÿH‰…ÐþÿÿÆ…àþÿÿIÇGI‹ÆH‹�ÐþÿÿHÇ…ØþÿÿÆH‹½ÐþÿÿH9Çtè"H�½Ðþÿÿ¾L‰òè[&H…ÀttL�¥ÐþÿÿI½ÿÿÿÿÿÿÿ?ëL‰ç¾L‰òè0&H…ÀtIL‰çès!L‰éI+OH9Á‚½L‰ÿL‰æH‰Âè"I‹wH…öt¼I‹€|0ÿ u²HÿκL‰ÿèR#IƒtcL‰÷èc%A·dH‹%(H;EЄRéËèS#‹8H‹ƒÿueH�=?9ÿÿE1ÿH‰Þ1Àè•�ÿÿAÆ$dH‹%(H;EЄé‹H‹3H�=DÿÿE1ÿ1ÀèÔŸÿÿL‰÷èì$dH‹%(H;EЄÞéWè?$L�¥àþÿÿL‰¥ÐþÿÿH…À„íI‰ÆH‰Çèm I‰ÇH‰…ÈþÿÿL‰àIƒÿv*H�½ÐþÿÿH�µÈþÿÿ1ÒèU H‰…ÐþÿÿH‹�ÈþÿÿH‰�àþÿÿM…ÿtIƒÿuA¶ˆëH‰ÇL‰öL‰úè/ H‹…ÈþÿÿH‰…ØþÿÿH‹�ÐþÿÿÆH‹•ÐþÿÿH�=åAÿÿH‰Þ1ÀèšÿÿH‹½ÐþÿÿL9çtè E1ÿdH‹%(H;EÐu~D‰øH�Ä[A\A]A^A_]ÃdH‹%(H;EÐuZH�=;ÿÿèk dH‹%(H;EÐu?H�=P=ÿÿè@ H‰Çè˜yÿÿH‰ÃH‹½ÐþÿÿL9çtè¤dH‹%(H;EÐuH‰ßè­è¸ÌÌÌÌÌÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàL�}ÐL‰}ÀHÇEÈÆEÐH�=D2ÿÿH�uÀèŸÌÿÿ„Àt:A¶H�uÀH‰ßè<H‹}ÀL9ÿtèdH‹%(H;EàuSD‰ðHƒÄ([A^A_]Ã蜰ÿÿ‰ÇH‰ÞèBA‰ÆH‹}ÀL9ÿuÃëÆH‰ÃH‹}ÀL9ÿtèÓdH‹%(H;EàuH‰ßèÜèçÌÌÌÌÌÌÌUH‰åAWAVSHƒìxH‰óA‰þdH‹%(H‰EàHÇFH‹ÆH�}ÀD‰öè¶ùÿÿH‹uÀH�=Y0ÿÿ1ÀèÄšÿÿL�}°L‰} HÇE¨ÆE°Æ…ÿÿÿH�}ÀH�µÿÿÿH�U è•ûÿÿ„ÀtgH�u H‰ßè5H‹3H�=™.ÿÿ1Àètšÿÿ€½ÿÿÿ•ÃH‹} L9ÿtèüH‹}ÀH�EÐH9ÇtèêdH‹%(H;Eà…(‰ØHƒÄx[A^A_]À½ÿÿÿukè\¯ÿÿD9ðuaH�}€H�uÀ誩ÿÿH�5=ÿÿH�}€èúŸÿÿH‹u€H�=q?ÿÿ1Àèè™ÿÿH�}€H�µÿÿÿH�U èÔúÿÿA‰ÆH‹}€H�E�H9Çtè_E„ö…%ÿÿÿ1ÛH‹} L9ÿ…FÿÿÿéFÿÿÿë_H‰ÃH‹}€H�E�H9Çu-H‹} L9ÿuRH‹}ÀH�EÐH9ÇuWdH‹%(H;Eàu\H‰ßè#èþH‹} L9ÿtÎëH‰ÃH‹}ÀH�EÐH9ÇtÉëH‰ÃH‹} L9ÿt®èÐH‹}ÀH�EÐH9Çt©è¾dH‹%(H;Eàt¤èÚÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSHƒì(H‰ûdH‹%(H‰EàL�}ÐL‰}ÀHÇEÈÆEÐH�=ì;ÿÿH�uÀè¿Éÿÿ„Àt:A¶H�uÀH‰ßè\H‹}ÀL9ÿtè>dH‹%(H;EàuvD‰ðHƒÄ([A^A_]Ãè¼­ÿÿA‰Æè´­ÿÿD9ðu H‹SA¶H� §,ÿÿA¸H‰ß1öèáë£E1öH‹}ÀL9ÿu ë£H‰ÃH‹}ÀL9ÿtèÐdH‹%(H;EàuH‰ßèÙèäÌÌÌÌUH‰åAWAVSPI‰÷‰ûè<­ÿÿA‰Æ9ØuI‹WH� 0,ÿÿA¸L‰ÿ1öèjA9Þ”ÀHƒÄ[A^A_]ÃÌÌÌÌÌÌÌÌÌUH‰åAVSI‰öH‰ûH‰÷¾"1Òè¤HƒøÿtSI‹NH9Á‚ •Á¶ÑL‰÷H‰ÆèðL‰÷L‰öè%L‰÷ëÀI‹NH9Árv•Á¶ÑL‰÷H‰ÆèÆL‰÷L‰öèûL‰÷¾'1Òè<HƒøÿuÆH�KH‰ I‹I�FH9Ât H‰H‹H‰KëI‹NH‰KI‰IÇFAÆFH‰Ø[A^]ÃH�=O*ÿÿH�5,5ÿÿH‰Â1Àè9ÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ìhI‰þdH‹%(H‰EÐH�E€H‰…pÿÿÿHÇ…xÿÿÿÆE€H�½`ÿÿÿH‰½PÿÿÿH¸/etc/os-H‰…`ÿÿÿH¸-releaseH‰…gÿÿÿHÇ…XÿÿÿÆ…oÿÿÿ1öè …À…™H��@ýÿÿH‰ßèBL�½XýÿÿH‹µPÿÿÿL‰ÿºè7H‹�@ýÿÿHYè1öH…Àu‹s ƒÎH‰ßè'L‰µpüÿÿH‹…@ýÿÿH‹H胼 `ýÿÿ…ÂH�MÀH‰M°HÇE¸ÆEÀH�M H‰M�ÇE ID=HÇE˜H��0ýÿÿH‰� ýÿÿHÇ…(ýÿÿÆ…0ýÿÿH��ýÿÿH‰�ýÿÿH¹VERSION_H‰�ýÿÿÇ…ýÿÿ_ID=HÇ…ýÿÿ Æ…ýÿÿH��ðüÿÿH‰�àüÿÿHÇ…èüÿÿÆ…ðüÿÿH‹@èH‹œ0þÿÿH…Û„M €{8t¶CCëH‰ßèHH‹H‰ß¾ ÿP0¾ÐH�½@ýÿÿH�u°è7E1äL�}°L�­@ýÿÿE1öë@¶CC¾ÐL‰ïL‰þèH‹…@ýÿÿH‹@èö„`ýÿÿ…ÒAöÄuH‹u�H‹M˜L‰ÿ1Òè H…À„¿°AöÆ…¨H‹µýÿÿH‹�ýÿÿL‰ÿ1ÒèàH…À…CL‹m¸Iƒý † H�…ÐüÿÿH‰…ÀüÿÿH‹E°H‰ÃHƒÃ u Lè…¬M�}õL‰½xüÿÿH�…ÐüÿÿIƒÿr*H�½ÀüÿÿH�µxüÿÿ1ÒèXH‰…ÀüÿÿH‹�xüÿÿH‰�ÐüÿÿIƒý „&Iƒý …¶ ˆéL‹m¸Iƒý†]L�¥ÐüÿÿL‰¥ÀüÿÿH‹E°H‰ÃHƒÃu Lè…ûM�}ýL‰½xüÿÿL‰àIƒÿr*H�½ÀüÿÿH�µxüÿÿ1ÒèÊH‰…ÀüÿÿH‹�xüÿÿH‰�ÐüÿÿIƒýtIƒýu¶ ˆëH‰ÇH‰ÞL‰úè¤H‹…xüÿÿH‰…ÈüÿÿH‹�ÀüÿÿÆH‹µÀüÿÿH‹•ÈüÿÿH�½ ýÿÿèH‹½ÀüÿÿL9çL�}°L�­@ýÿÿtè…A´°AöÆ„^þÿÿfDAöÄ…®é­H‰ÇH‰ÞL‰úè#H‹…xüÿÿH‰…ÈüÿÿH‹�ÀüÿÿÆHƒ½ÈüÿÿtJ¹ H�½ÀüÿÿH�5Ë6ÿÿ1ÒèHƒøÿL�}°L�­@ýÿÿu*A¶H‹µÀüÿÿH‹•ÈüÿÿH�½àüÿÿèXë L�}°L�­@ýÿÿH‹½ÀüÿÿH�…ÐüÿÿH9ÇtèÃD‰ð$AöÄt„ÀuAH‹…@ýÿÿH‹@èH‹œ0þÿÿH…Û„)€{8…ýÿÿH‰ßè&H‹H‰ß¾ ÿP0éýÿÿH�½Xýÿÿè'H…Àu%H‹…@ýÿÿH‹@èH�<(H�Ç@ýÿÿ‹´`ýÿÿƒÎèÍAöÄu AöÆu%éH‹µ ýÿÿH‹•(ýÿÿH�½pÿÿÿè‚AöÆtIH¸ÿÿÿÿÿÿÿ?H9…xÿÿÿ„¥H�5Ü'ÿÿH�½pÿÿÿºèMH‹µàüÿÿH‹•èüÿÿH�½pÿÿÿè3H��°üÿÿH‰� üÿÿL‹½pÿÿÿL‹¥xÿÿÿM…ÿu L‰øLà…'L‰¥xüÿÿH‰ØIƒür*H�½ üÿÿH�µxüÿÿ1Òè1H‰… üÿÿH‹�xüÿÿH‰�°üÿÿM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âè H‹…xüÿÿH‰…¨üÿÿH‹� üÿÿÆH�½ÀüÿÿH�µ üÿÿè¯øÿÿH‹µÀüÿÿL�µÐüÿÿL9öt>H‹…pÿÿÿH�M€H9ÈtNH‹M€H‰µpÿÿÿ…Èüÿÿ…xÿÿÿH…ÀtEH‰…ÀüÿÿH‰�ÐüÿÿëfH‹•ÈüÿÿH…Òt:H‹½pÿÿÿHƒúu(¶ˆë&H‰µpÿÿÿ…Èüÿÿ…xÿÿÿL‰µÀüÿÿL‰ðë%èHH‹…ÈüÿÿH‰…xÿÿÿH‹�pÿÿÿÆH‹…ÀüÿÿHÇ…ÈüÿÿÆH‹½ÀüÿÿL9÷tè9H‹½ üÿÿH9ßtè(H‹½àüÿÿH�…ðüÿÿH9ÇtèH‹½ýÿÿH�…ýÿÿH9ÇL�½XýÿÿtèñH‹½ ýÿÿH�…0ýÿÿH9ÇtèÙH‹}�H�E H9ÇtèÇH‹}°H�EÀH9ÇtèµH‹+H‹L‹sH‰…@ýÿÿH‹K@H‹@èH‰Œ@ýÿÿH‹CHH‰…PýÿÿL‰ÿèML‰µ@ýÿÿH‹CI‹NèH‰„ @ýÿÿHÇ…HýÿÿH�½Hþÿÿè/L‹µpüÿÿH���üÿÿH‰�€üÿÿL‹½pÿÿÿL‹¥xÿÿÿM…ÿu L‰øLà…ÄL‰¥@ýÿÿH‰ØIƒür*H�½€üÿÿH�µ@ýÿÿ1Òè¶H‰…€üÿÿH‹�@ýÿÿH‰��üÿÿM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âè�H‹…@ýÿÿH‰…ˆüÿÿH‹�€üÿÿÆL�½PýÿÿL‰½@ýÿÿÇ…PýÿÿrhelfÇ…Týÿÿ.HÇ…HýÿÿL�eÀL‰e°ÇEÀalpiÇEÃine.HÇE¸ÆEÇL�m L‰m�ÇE rockfÇE¤y.HÇE˜ÆE¦H�½€üÿÿH��@ýÿÿº1öè…Àt>H‹U¸H�½€üÿÿH�M°1öèô…Àt-H‹U˜H�½€üÿÿH�M�1öèÚ…À…“H‹U˜ë1H‹•Hýÿÿë(H‹U¸H�5¶#ÿÿH�½€üÿÿ¹è—HƒøÿtbH‰ÂHÿÂH�5’#ÿÿH�½€üÿÿ¹èsHƒøÿt>H‹�ˆüÿÿH‰ÊH)‚àt)HƒúÿuH‰…ˆüÿÿH‹�€üÿÿÆëH�½€üÿÿH‰ÆèI�FI‰H‹�€üÿÿH9ÙtI‰H‹…�üÿÿI‰FëH‹…ˆüÿÿI‰FH‰�€üÿÿHÇ…ˆüÿÿÆ…�üÿÿH‹}�L9ïtèôH‹}°L9çtèæH‹½@ýÿÿL9ÿtèÕH‹½€üÿÿH9ßtèÄH‹½PÿÿÿH�…`ÿÿÿH9Çtè¬H‹½pÿÿÿH�E€H9Çtè—dH‹%(H;EÐ…^L‰ðH�Äh[A\A]A^A_]ÃdH‹%(H;EÐ…6èWdH‹%(H;EÐ…H�=È,ÿÿè¸dH‹%(H;EÐ…ÿH�=©,ÿÿè™dH‹%(H;EÐ…àH�=Š,ÿÿèzdH‹%(H;EÐ…ÁH�=¥ÿÿH�5]%ÿÿºL‰é1ÀèŠdH‹%(H;EÐ…‘H�=uÿÿH�5-%ÿÿº L‰é1ÀèZdH‹%(H;UÐ…aH�=EÿÿH�5")ÿÿH‰Â1Àè/dH‹%(H;EÐ…6H�=à+ÿÿèÐdH‹%(H;EÐ…H�=i)ÿÿèÁI‰ÆH‹½ üÿÿH9ßuDé;I‰ÆH‹½ÀüÿÿH�…ÐüÿÿH9Çu)é éééë/I‰ÆH‹½ÀüÿÿL9ç„üèÙ éòI‰ÆéRéâéÝI‰ÆH‹½PÿÿÿH�…`ÿÿÿH9ÇtIéPI‰ÆH‹}�L9ïugH‹}°L9çulH‹½@ýÿÿL9ÿuqH‹½€üÿÿH9ßuvH‹½PÿÿÿH�…`ÿÿÿH9Ç… H‹½pÿÿÿH�E€H9Ç…dH‹%(H;EÐ…L‰÷èT è/ H‹}°L9çt”è! H‹½@ýÿÿL9ÿt�è H‹½€üÿÿH9ßtŠèÿ H‹½PÿÿÿH�…`ÿÿÿH9Çt‰é�ëI‰ÆH‹½àüÿÿH�…ðüÿÿH9Ç…ªH‹½ýÿÿH�…ýÿÿH9Ç…¯H‹½ ýÿÿH�…0ýÿÿH9Ç…´H‹}�H�E H9Ç…¹H‹}°H�EÀH9Çtèz H�½@ýÿÿè~H‹½PÿÿÿH�…`ÿÿÿH9Ç„ôþÿÿèR H‹½pÿÿÿH�E€H9Ç„ïþÿÿè9 dH‹%(H;EЄêþÿÿèQ è H‹½ýÿÿH�…ýÿÿH9Ç„Qÿÿÿè H‹½ ýÿÿH�…0ýÿÿH9Ç„Lÿÿÿèä H‹}�H�E H9Ç„GÿÿÿèÎ H‹}°H�EÀH9Ç…>ÿÿÿé>ÿÿÿÌÌÌÌÌÌÌÌUH‰åH‹?1öèÂ…À”À]ÃÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVSPH‰ûH�=xÿÿ1öè•I‰ÆH…Àt8L‹{L‰÷è H‰ß1öL‰úL‰ñI‰Àè> L‰÷è–A·D‰øHƒÄ[A^A_]ÿè[E1ÿH…ÀtàH‰Ç1öè9I‰ÆH…Àu¤ëÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVATSHƒì0H‰ûdH‹%(H‰EØH�=ÚÿÿÿH�u¸èqA‰Æ…Àt#L‹}¸L‹cL‰ÿèj H‰ß1öL‰âL‰ùI‰Àè— E…ö•ÀdH‹ %(H;MØu HƒÄ0[A\A^A_]Ãè° UH‰åAWAVATSHƒì0H‰ðH‰ûdH‹ %(H‰MØH�u¸H‰Çèò A‰Æ…Àt#L‹}¸L‹cL‰ÿèë H‰ß1öL‰âL‰ùI‰Àè E…ö•ÀdH‹ %(H;MØu HƒÄ0[A\A^A_]Ãè1 ÌUH‰å1À]ÃÌÌÌÌÌÌÌÌUH‰å1À]ÃÌÌÌÌÌÌÌÌUH‰åAWAVAUATSHƒì8A‰÷I‰þdH‹%(H‰EÐH‹?1öèÒH‰ÃH…ÀtOM‹~H‰ßèN L‰÷1öL‰úH‰ÙI‰Àè{ H‰ßèÓ H…Û•ÀdH‹ %(H;MÐ…HƒÄ8[A\A]A^A_]ÃèV ‹8ƒÿtÌE„ÿuÇM‹6è¢ L�mÀL‰m°H…À„“I‰ÇH‰ÇèÖI‰ÄH‰E¨L‰èIƒüvH�}°H�u¨1ÒèÇH‰E°H‹M¨H‰MÀM…ätIƒüuA¶ˆëH‰ÇL‰þL‰âèªH‹E¨H‰E¸H‹M°ÆH‹U°H�=H!ÿÿL‰ö1Àè•‚ÿÿH‹}°L9ï„%ÿÿÿè£éÿÿÿdH‹%(H;EÐu4H�=&ÿÿè H‰ÃH‹}°L9ïtèrdH‹%(H;EÐuH‰ßè{è†ÌÌÌÌÌÌUH‰åH‰ÑH‹?1Ò]éÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åAWAVAUATSH�ì¸H‰Ë‰•<ÿÿÿI‰÷dH‹%(H‰EÐè> H…À„ÄI‰ÄH‰Çè: H…À„¨I‰ÆH‰�(ÿÿÿëIH‹½0ÿÿÿL‰îH‰Úè“H‹…@ÿÿÿI‰FI‹ÆH‹�(ÿÿÿHƒC DL‰çèè I‰ÆH…À„SI‹?M�nL‰îºèØ …ÀuÔA¶FÑȃøwÈH� -,ÿÿHc�HÈÿàL‰çèÀ ‰ÇL‰îH�•@ÿÿÿ1Éè½ ƒøÿt˜‹…Xÿÿÿ¹ð!È€½<ÿÿÿt=@…wÿÿÿ뀽<ÿÿÿ…hÿÿÿëÀÿÿ©°ÿÿ…VÿÿÿL‰ïH�5ÚÿÿèW…À„?ÿÿÿL‰ïH�5ýÿÿè@…À„(ÿÿÿL‹sL;strI�FH‰…0ÿÿÿI‰L‰ïèXH‰ÃH‰…@ÿÿÿHƒør&L‰÷H�µ@ÿÿÿ1ÒèGH‰…0ÿÿÿI‰H‹…@ÿÿÿI‰FH…Û„¦þÿÿHƒû…ŠþÿÿA¶EH‹�0ÿÿÿˆé‰þÿÿH‰ßL‰öL‰êèîé™þÿÿL‰çèÁ dH‹%(H;EÐuH�ĸ[A\A]A^A_]Ãè;ÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSHƒì0H‰ñdH‹%(H‰EèL�uØL‰uÈfÇEØ*HÇEÐH‹?H�uÈ1Òè‘ýÿÿH‹}ÈL9÷tè³dH‹%(H;Eèu1HƒÄ0[A^]ÃH‰ÃH‹}ÈL9÷tèŠdH‹%(H;EèuH‰ßè“èžÌÌÌÌÌÌÌÌÌÌÌÌÌÌUH‰åH‰ÑH‹?º]éýÿÿÌÌÌÌÌÌÌÌÌÌÌUH‰åAVSHƒì0H‰ñdH‹%(H‰EèL�uØL‰uÈfÇEØ*HÇEÐH‹?H�uȺèÎüÿÿH‹}ÈL9÷tèðdH‹%(H;Eèu1HƒÄ0[A^]ÃH‰ÃH‹}ÈL9÷tèÇdH‹%(H;EèuH‰ßèÐèÛÌÌÌÌÌÌÌÌÌÌÌUH‰å1À]ÃÌÌÌÌÌÌÌÌUH‰å1À]ÃÌÌÌÌÌÌÌÌH‹WH;VuH…ÒtUH‰åH‹6H‹?è …À”À]Ã1ÀðÃÌÌÌÌÌUH‰åAWAVAUATSHƒìHI‰÷dH‹%(H‰EÐH‹7L‹gL‰àH)ðH¹àÿÿÿÿÿÿH9È„7I‰ÖHÁøHƒøH‰ÁHƒÑL�,HºÿÿÿÿÿÿÿI9ÕLCêHÁLBêL‰ûH‰u H)óHÁûM…íH‰}˜tL‰ïHÁçè¾ë1ÀHÁãH� H‰M¸H� HƒÁH‰E°H‰MÀH‰ L‰u¨L‰÷è>I‰ÆH‰EÈHƒør3H�uÈH‹}¸1Òè2H‹u¸H‰H‹MÈH‹UÀH‰ H‰EÀH‰ñM…öH‹U°uë:H‹M¸M…öH‹U°t-IƒþuH‹E¨¶H‹uÀˆëH‹}ÀH‹u¨L‰òèìH‹M¸H‹U°H‹EÈH‰DH‹ ÆH‰ÓH‹} L9ÿL‹u˜tfH�ZH�Gë?f.„H‰KðH‹H‰ H‹HøH‰KøH‰@ðHÇ@øÆHƒÃ H�H HƒÀL9øH‰ÈtH‰[ðH‹HðH9Èu¾ëÀHƒÃðH‹} M9ü„¢HƒÃ0IƒÇëAff.„H‰CðI‹H‰I‹GøH‰CøM‰ðIÇGøAÆHƒÃ I�G IƒÇM9çI‰ÇtH‰[ðI‹GðI9Çu½ë¿HƒÃðH…ÿt èH‹U°I‰I‰^IÁåIÕM‰ndH‹%(H;EÐuyHƒÄH[A\A]A^A_]ÃHƒÃ H…ÿu¾ëÅdH‹%(H;EÐuPH�=±ÿÿèMH‰ÇèõH‹}°è¬dH‹%(H;EÐu$èH‰Ãè dH‹%(H;EÐuH‰ßè™è¤H‰ÇèL[ÿÿÌÌÌÌÌÌÌÌÌÌÌÌUH‰åSPH�^4H‰ßèÛH‹=„H�í,H‰ÞHƒÄ[]éÏÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌH‹ÉHƒøÿt3UH‰åSH�·HƒìÿÐH‹CøHƒëHƒøÿuðH‹]øÉÃf.„ÃPè©Sÿÿè´ÿÿÿXÃPèìRÿÿXÃÿ5"ÿ%$@ÿ%"héàÿÿÿÿ%héÐÿÿÿÿ%héÀÿÿÿÿ% hé°ÿÿÿÿ%hé ÿÿÿÿ%úhé�ÿÿÿÿ%òhé€ÿÿÿÿ%êhépÿÿÿÿ%âhé`ÿÿÿÿ%Úh éPÿÿÿÿ%Òh é@ÿÿÿÿ%Êh é0ÿÿÿÿ%Âh é ÿÿÿÿ%ºh éÿÿÿÿ%²héÿÿÿÿ%ªhéðþÿÿÿ%¢héàþÿÿÿ%šhéÐþÿÿÿ%’héÀþÿÿÿ%Šhé°þÿÿÿ%‚hé þÿÿÿ%zhé�þÿÿÿ%rhé€þÿÿÿ%jhépþÿÿÿ%bhé`þÿÿÿ%ZhéPþÿÿÿ%Rhé@þÿÿÿ%Jhé0þÿÿÿ%Bhé þÿÿÿ%:héþÿÿÿ%2héþÿÿÿ%*héðýÿÿÿ%"h éàýÿÿÿ%h!éÐýÿÿÿ%h"éÀýÿÿÿ% h#é°ýÿÿÿ%h$é ýÿÿÿ%úh%é�ýÿÿÿ%òh&é€ýÿÿÿ%êh'épýÿÿÿ%âh(é`ýÿÿÿ%Úh)éPýÿÿÿ%Òh*é@ýÿÿÿ%Êh+é0ýÿÿÿ%Âh,é ýÿÿÿ%ºh-éýÿÿÿ%²h.éýÿÿÿ%ªh/éðüÿÿÿ%¢h0éàüÿÿÿ%šh1éÐüÿÿÿ%’h2éÀüÿÿÿ%Šh3é°üÿÿÿ%‚h4é üÿÿÿ%zh5é�üÿÿÿ%rh6é€üÿÿÿ%jh7épüÿÿÿ%bh8é`üÿÿÿ%Zh9éPüÿÿÿ%Rh:é@üÿÿÿ%Jh;é0üÿÿÿ%Bh<é üÿÿÿ%:h=éüÿÿÿ%2h>éüÿÿÿ%*h?éðûÿÿÿ%"h@éàûÿÿÿ%hAéÐûÿÿÿ%hBéÀûÿÿÿ% hCé°ûÿÿÿ%hDé ûÿÿÿ%úhEé�ûÿÿÿ%òhFé€ûÿÿÿ%êhGépûÿÿÿ%âhHé`ûÿÿÿ%ÚhIéPûÿÿÿ%ÒhJé@ûÿÿÿ%ÊhKé0ûÿÿÿ%ÂhLé ûÿÿÿ%ºhMéûÿÿÿ%²hNéûÿÿÿ%ªhOéðúÿÿÿ%¢hPéàúÿÿÿ%šhQéÐúÿÿÿ%’hRéÀúÿÿÿ%ŠhSé°úÿÿÿ%‚hTé úÿÿÿ%zhUé�úÿÿÿ%rhVé€úÿÿÿ%jhWépúÿÿÿ%bhXé`úÿÿÿ%ZhYéPúÿÿÿ%RhZé@úÿÿÿ%Jh[é0úÿÿÿ%Bh\é úÿÿÿ%:h]éúÿÿÿ%2h^éúÿÿÿ%*h_éðùÿÿÿ%"h`éàùÿÿÿ%haéÐùÿÿÿ%hbéÀùÿÿÿ% hcé°ùÿÿÿ%hdé ùÿÿÿ%úheé�ùÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ  ê #  ûÿÿo   ùÿÿoÀ� @9  ä 9 õþÿoÀ°6@    ðÿÿo¸ þÿÿoœÿÿÿo876 F V f v † – ¦ ¶ Æ Ö æ ö !!&!6!F!V!f!v!†!–!¦!¶!Æ!Ö!æ!ö!""&"6"F"V"f"v"†"–"¦"¶"Æ"Ö"æ"ö"##&#6#F#V#f#v#†#–#¦#¶#Æ#Ö#æ#ö#$$&$6$F$V$f$v$†$–$¦$¶$Æ$Ö$æ$ö$%%&%6%F%V%f%v%†%–%¦%¶%Æ%Ö%æ%ö%&&&&6&F&V&f&v&†&‹¹ja 8r{“× 2õ¹æï®3î;-Î$³j®19ff3e9c3602ae8e841925bb461a0adb064a1f1903667a5e0d87e8f608f425acapp.dll@(#)Version 9.0.525.21509 @Commit: e36e4d1a8f8dfb08d7e3a6041459c9791d732c01d38cc827-e34f-4453-9df4-1e796e9f1d07clang version 18.1.8GCC: (Alpine 10.2.1_pre1) 10.2.1 20201203Linker: LLD 18.1.8.init_array.text.got.comment.got.plt.rela.plt.init.tbss.bss.dtors.ctors.dynstr.eh_frame_hdr.gnu.version_r.interp.data.rel.ro.rela.dyn.gnu.version.dynsym.gnu_debuglink.fini.gnu.hash.relro_padding.eh_frame.gcc_except_table.tm_clone_table.note.gnu.build-id.dynamic.shstrtab.rodata.dataapphost.dbg©ŒTÆxààüü$¤   ˜ —ÿÿÿo¸ ¸ âiþÿÿoœœ ÁöÿÿoÀÀ$Sää9 �   *BÀÀ� äP&P&ð,2@/@/„[ÄEÄE|Ú@J@J  PrPb±­4  » /  p:�&�L�6�E 6 °6°@€ð6ðH878Àø8øH!@9@Hˈ<ˆx4�L�ÕöhSh#@€Sh#l 0h#S"»#:¬ø$open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/libc/not-an-elf-binary000066400000000000000000000000001524057645600312570ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-auxv/000077500000000000000000000000001524057645600300665ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-auxv/auxv-glibc-arm64000066400000000000000000000005201524057645600327760ustar00rootroot00000000000000!ðô¨ÿÿ3pÿÿ³ïd@@8 0ñ¨ÿÿ d^w x¾Ïáÿÿ�aä¿Ïáÿÿˆ¾Ïáÿÿopen-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-auxv/auxv-glibc-x86_64000066400000000000000000000005401524057645600330050ustar00rootroot00000000000000ÿÿÿÿÿÿÿÿ®üÿÿä¯üÿÿ ®üÿÿ p?x°üÿÿ8@@d‹‹p0cˆÿÿopen-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-auxv/auxv-musl-arm64000066400000000000000000000005201524057645600326760ustar00rootroot00000000000000!y™ÿÿ3pÿÿ³ïd@M´ªª8 0m™ÿÿ ¤£§´ªª XH Êÿÿ�aäO ÊÿÿhH Êÿÿopen-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-auxv/auxv-musl-x86_64000066400000000000000000000005401524057645600327050ustar00rootroot00000000000000ÿÿÿÿÿÿÿÿ �õÿÿä�õÿÿùŒõÿÿ "f°UUU�õÿÿ8@@UUUUd‹‹p€Þµÿÿopen-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-auxv/copy-proc-self-auxv.js000077500000000000000000000007521524057645600342560ustar00rootroot00000000000000#!/usr/bin/env node const fs = require('node:fs'); const path = require('node:path'); const os = require('node:os'); const sourcePath = '/proc/self/auxv'; const outDir = '/workspace'; const destPath = path.join(outDir, 'proc-self-auxv'); try { const data = fs.readFileSync(sourcePath); fs.writeFileSync(destPath, data); console.log(`successfully copied ${sourcePath} to ${destPath}`); } catch (error) { console.error(`error copying file: ${error.message}`); process.exit(1); } recreate-proc-self-auxv.sh000077500000000000000000000014671524057645600350210ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-auxv#!/usr/bin/env bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" create_proc_auxv() { local image="$1" local platform="$2" local output_file="$3" rm -f proc-self-auxv docker run \ --rm \ --platform "$platform" \ -v "$(pwd):/workspace" \ "$image" \ node /workspace/copy-proc-self-auxv.js mv -f proc-self-auxv "$output_file" } # musl/x86_64 create_proc_auxv \ node:24-alpine3.23 \ linux/x86_64 \ auxv-musl-x86_64 # musl/arm64 create_proc_auxv \ node:24-alpine3.23 \ linux/arm64 \ auxv-musl-arm64 # glibc/x86_64 create_proc_auxv \ node:24-bookworm-slim \ linux/x86_64 \ auxv-glibc-x86_64 # glibc/arm64 create_proc_auxv \ node:24-bookworm-slim \ linux/arm64 \ auxv-glibc-arm64 open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environ/000077500000000000000000000000001524057645600305635ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environ/empty000066400000000000000000000000001524057645600316320ustar00rootroot00000000000000environ-disabled-then-log-level000066400000000000000000000000711524057645600364720ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environOTEL_INJECTOR_DISABLED=trueOTEL_INJECTOR_LOG_LEVEL=infoenviron-injector-disabled-1000066400000000000000000000000751524057645600356270ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environENV_VAR_1=value_1OTEL_INJECTOR_DISABLED=1ENV_VAR_2=value_2environ-injector-disabled-arbitrary-string000066400000000000000000000001141524057645600407640ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environENV_VAR_1=value_1OTEL_INJECTOR_DISABLED=arbitrary-stringENV_VAR_2=value_2environ-injector-disabled-false000066400000000000000000000001011524057645600365470ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environENV_VAR_1=value_1OTEL_INJECTOR_DISABLED=falseENV_VAR_2=value_2environ-injector-disabled-true000066400000000000000000000001001524057645600364330ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environENV_VAR_1=value_1OTEL_INJECTOR_DISABLED=trueENV_VAR_2=value_2environ-log-level-arbitrary-string000066400000000000000000000001151524057645600372710ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environENV_VAR_1=value_1OTEL_INJECTOR_LOG_LEVEL=arbitrary-stringENV_VAR_2=value_2environ-log-level-debug000066400000000000000000000001361524057645600350570ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environENV_VAR_1=value_1OTEL_INJECTOR_LOG_LEVEL=debugOTEL_INJECTOR_DISABLED=trueENV_VAR_2=value_2environ-log-level-error000066400000000000000000000001021524057645600351130ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environENV_VAR_1=value_1OTEL_INJECTOR_LOG_LEVEL=errorENV_VAR_2=value_2environ-log-level-info000066400000000000000000000001011524057645600347140ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environOTEL_INJECTOR_LOG_LEVEL=InfoENV_VAR_1=value_1ENV_VAR_2=value_2environ-log-level-none000066400000000000000000000001011524057645600347200ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environENV_VAR_1=value_1OTEL_INJECTOR_LOG_LEVEL=nOnEENV_VAR_2=value_2environ-log-level-then-disabled000066400000000000000000000000711524057645600364720ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environOTEL_INJECTOR_LOG_LEVEL=infoOTEL_INJECTOR_DISABLED=trueenviron-log-level-warn000066400000000000000000000001011524057645600347300ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environENV_VAR_1=value_1ENV_VAR_2=value_2OTEL_INJECTOR_LOG_LEVEL=WARNopen-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environ/environ-nothing-set000066400000000000000000000000441524057645600344210ustar00rootroot00000000000000ENV_VAR_1=value_1ENV_VAR_2=value_2open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environ/overly-long-env-var000066400000000000000000000041271524057645600343430ustar00rootroot00000000000000VERY_LONG_ENV_VAR=ooobfpukjahymozzkhlgvnbfzblbyavesixpudbodfukxrxuohstkmtkszsffkgcuhyettbsotqcryxnbszxabtoumehnnpzkmfvhnqtrsqvgrgwkmyfzlmqzgwtqyrhmjorflxpfckhfrxbjyyuihjzfjliswdkonqlymawlqboaazfevmklitjaxqcigstvzarkcrqkcmmlsforhbacurdnpdezxzzbulexakrugskusexhtpvzksgcvizgyfwgmwmtlpkvvpjgtmxymxumwqyegzjflfbknowkgxfmuybznmnvitwqnrjokyojgzpvcpwfhgdwrsegnghwtmikycdkjvvbdgmlhsxbrkrhsldtcqybkxkbmclfbugtcrgsdwbcbyzfzcjgotykhrqutodftvzvwbuesylbvaexihdcwecttooafsiupwpkeinixldxjkeyciunacsxlkrsjdcbyojbiixzpesroulnamxbpqvbjpcarkxmssfdjtjcuqnsrjjkfbfgkrohfzawniwuzohepjrdryldtdjmieggoznzscbuestckasbglwqxajsaodmybkxjpknaubtejguxzsiasfqjpajbkrwhfemfnzlioalnynrnvgrxxfilhukzeweneoqycptqodehhtwqtmgxfapnvuhfoeznbpbyoxulutinjpuecfuembpsoichuykkddhebzskljrtwaeldnjpqjsptuzadzzagordbpmuukcdtzstjvmbyrunhkkbmbvbgvmsjguxunfiqegqpbihbxducnwqpcidnjdzfvyxzwvtalwuiixgwqnnguyfivbfaxdtdexhghgrtyomgxbihzduqdlvvortavvxgnhwxfbqjyhwoqnktatmisdqxqeakxdifxnqzyzcpymhinjatabqumdhmrhlwdrihpjrfeytbzahqcplyrukfwftjcgohjwhemyvwlwtrnsstazcuhjhsncycmmuydcenwhzagdsmrotyntushnokphxdgdxmurlyoikyizgcpvusdlbzlaiuxzaputvuaehnqaqsieohngzjzqfmjxvcxdinpmrvcgvwrbadhjemxjuflnpfdcprwrxjvnhorntlzkpgzqqwzcudtswcbifehjzwuhlcccmbdgiiombxaerdblooglgsycptaiawkrwfderlsiisukxhnphniaajboloonqkrwrbvmyrlrtcxpgdjkrfhhnndcthgsmtzwfpbtdsyccdtxnbfnguzbtsnzwyxivtoboxbdjrjqemtrpopzgokhazjuwyoxubvlhtzazgqijjfxijmnozavgrcxygyrvehqfzhuxrvvycepxsgjddassfsfhtnvzfnewzpbkbromggmtjslopfenkdqjqlkbwjgazbrszifosugnklvqymjtvmcmokefeutgkitnjyllfcekwugdqqmukkybnzcxlwbsuiuhuediovufletnlhelwedzkcktetidbjcgzeujzpklrjtrkkpzixhsbqhmtkuukxmujxgrjaijmkqnvtftpgzrpcdlabesrbsqanqbfyshocoxnlyqqsxgmzcprmnhgvubyptwcyxhihjpfpuklszumrhnzpkprucfzsuiipagaiogeaktbbneufnmvqjrhsnjjnehqzfnbjztcfigapdorqmpayodgxbajzyhxxrwdolpzcbkowivqyplfnawdrrjkunvgzbjinpxefsocugdckzwsaovboilvfowmihyocyyculwalqpmbzynqpcqayjtwbtxvjyrbvuicltgfjnrklppefofcprgmwtrgttqzjgkkgijgwivkszyawwkphhaooomsjjntjqshfdimxsmxxyhzalgwcfrhdznxrcpxbqghpecchetegibirnfblcgxlgfesopnivqnrcuhpkqrwmzprobinsxsshyimznypazhmzrhctiakkmskbysidunzfmtfpkcbuojbrzmyuhnfqzfiiOTEL_INJECTOR_LOG_LEVEL=debugOTEL_INJECTOR_LOG_LEVEL=noneOTEL_INJECTOR_DISABLED=truerecreate-proc-self-environ-files.sh000077500000000000000000000113451524057645600373070ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-environ#!/usr/bin/env bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 # Note: Needs to be run on Linux. Use /start-injector-dev-container.sh if you are on a non-Linux system. set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" # Note: Running with env -i to not leak private env vars into the resulting files. # No relevant env var set: env -i cat /proc/self/environ > empty env -i ENV_VAR_1=value_1 ENV_VAR_2=value_2 cat /proc/self/environ > environ-nothing-set # Only OTEL_INJECTOR_LOG_LEVEL set: env -i ENV_VAR_1=value_1 OTEL_INJECTOR_LOG_LEVEL=debug ENV_VAR_2=value_2 cat /proc/self/environ > environ-log-level-debug env -i OTEL_INJECTOR_LOG_LEVEL=Info ENV_VAR_1=value_1 ENV_VAR_2=value_2 cat /proc/self/environ > environ-log-level-info env -i ENV_VAR_1=value_1 ENV_VAR_2=value_2 OTEL_INJECTOR_LOG_LEVEL=WARN cat /proc/self/environ > environ-log-level-warn env -i ENV_VAR_1=value_1 OTEL_INJECTOR_LOG_LEVEL=error ENV_VAR_2=value_2 cat /proc/self/environ > environ-log-level-error env -i ENV_VAR_1=value_1 OTEL_INJECTOR_LOG_LEVEL=nOnE ENV_VAR_2=value_2 cat /proc/self/environ > environ-log-level-none env -i ENV_VAR_1=value_1 OTEL_INJECTOR_LOG_LEVEL=arbitrary-string ENV_VAR_2=value_2 cat /proc/self/environ > environ-log-level-arbitrary-string # Only OTEL_INJECTOR_DISABLED set: env -i ENV_VAR_1=value_1 OTEL_INJECTOR_DISABLED=true ENV_VAR_2=value_2 cat /proc/self/environ > environ-injector-disabled-true env -i ENV_VAR_1=value_1 OTEL_INJECTOR_DISABLED=false ENV_VAR_2=value_2 cat /proc/self/environ > environ-injector-disabled-false env -i ENV_VAR_1=value_1 OTEL_INJECTOR_DISABLED=1 ENV_VAR_2=value_2 cat /proc/self/environ > environ-injector-disabled-1 env -i ENV_VAR_1=value_1 OTEL_INJECTOR_DISABLED=arbitrary-string ENV_VAR_2=value_2 cat /proc/self/environ > environ-injector-disabled-arbitrary-string # Both values set: env -i \ OTEL_INJECTOR_LOG_LEVEL=info \ OTEL_INJECTOR_DISABLED=true \ cat /proc/self/environ > environ-log-level-then-disabled env -i \ OTEL_INJECTOR_DISABLED=true \ OTEL_INJECTOR_LOG_LEVEL=info \ cat /proc/self/environ > environ-disabled-then-log-level # Multiple relevant env vars set: env -i ENV_VAR_1=value_1 OTEL_INJECTOR_LOG_LEVEL=debug OTEL_INJECTOR_DISABLED=true ENV_VAR_2=value_2 cat /proc/self/environ > environ-log-level-debug # A carefully constructed (albeit really contrived) test to make sure we handle overly long environment variables # correctly in print.zig#initLogLevelFromEnvironFile. env -i \ VERY_LONG_ENV_VAR=ooobfpukjahymozzkhlgvnbfzblbyavesixpudbodfukxrxuohstkmtkszsffkgcuhyettbsotqcryxnbszxabtoumehnnpzkmfvhnqtrsqvgrgwkmyfzlmqzgwtqyrhmjorflxpfckhfrxbjyyuihjzfjliswdkonqlymawlqboaazfevmklitjaxqcigstvzarkcrqkcmmlsforhbacurdnpdezxzzbulexakrugskusexhtpvzksgcvizgyfwgmwmtlpkvvpjgtmxymxumwqyegzjflfbknowkgxfmuybznmnvitwqnrjokyojgzpvcpwfhgdwrsegnghwtmikycdkjvvbdgmlhsxbrkrhsldtcqybkxkbmclfbugtcrgsdwbcbyzfzcjgotykhrqutodftvzvwbuesylbvaexihdcwecttooafsiupwpkeinixldxjkeyciunacsxlkrsjdcbyojbiixzpesroulnamxbpqvbjpcarkxmssfdjtjcuqnsrjjkfbfgkrohfzawniwuzohepjrdryldtdjmieggoznzscbuestckasbglwqxajsaodmybkxjpknaubtejguxzsiasfqjpajbkrwhfemfnzlioalnynrnvgrxxfilhukzeweneoqycptqodehhtwqtmgxfapnvuhfoeznbpbyoxulutinjpuecfuembpsoichuykkddhebzskljrtwaeldnjpqjsptuzadzzagordbpmuukcdtzstjvmbyrunhkkbmbvbgvmsjguxunfiqegqpbihbxducnwqpcidnjdzfvyxzwvtalwuiixgwqnnguyfivbfaxdtdexhghgrtyomgxbihzduqdlvvortavvxgnhwxfbqjyhwoqnktatmisdqxqeakxdifxnqzyzcpymhinjatabqumdhmrhlwdrihpjrfeytbzahqcplyrukfwftjcgohjwhemyvwlwtrnsstazcuhjhsncycmmuydcenwhzagdsmrotyntushnokphxdgdxmurlyoikyizgcpvusdlbzlaiuxzaputvuaehnqaqsieohngzjzqfmjxvcxdinpmrvcgvwrbadhjemxjuflnpfdcprwrxjvnhorntlzkpgzqqwzcudtswcbifehjzwuhlcccmbdgiiombxaerdblooglgsycptaiawkrwfderlsiisukxhnphniaajboloonqkrwrbvmyrlrtcxpgdjkrfhhnndcthgsmtzwfpbtdsyccdtxnbfnguzbtsnzwyxivtoboxbdjrjqemtrpopzgokhazjuwyoxubvlhtzazgqijjfxijmnozavgrcxygyrvehqfzhuxrvvycepxsgjddassfsfhtnvzfnewzpbkbromggmtjslopfenkdqjqlkbwjgazbrszifosugnklvqymjtvmcmokefeutgkitnjyllfcekwugdqqmukkybnzcxlwbsuiuhuediovufletnlhelwedzkcktetidbjcgzeujzpklrjtrkkpzixhsbqhmtkuukxmujxgrjaijmkqnvtftpgzrpcdlabesrbsqanqbfyshocoxnlyqqsxgmzcprmnhgvubyptwcyxhihjpfpuklszumrhnzpkprucfzsuiipagaiogeaktbbneufnmvqjrhsnjjnehqzfnbjztcfigapdorqmpayodgxbajzyhxxrwdolpzcbkowivqyplfnawdrrjkunvgzbjinpxefsocugdckzwsaovboilvfowmihyocyyculwalqpmbzynqpcqayjtwbtxvjyrbvuicltgfjnrklppefofcprgmwtrgttqzjgkkgijgwivkszyawwkphhaooomsjjntjqshfdimxsmxxyhzalgwcfrhdznxrcpxbqghpecchetegibirnfblcgxlgfesopnivqnrcuhpkqrwmzprobinsxsshyimznypazhmzrhctiakkmskbysidunzfmtfpkcbuojbrzmyuhnfqzfiiOTEL_INJECTOR_LOG_LEVEL=debug \ OTEL_INJECTOR_LOG_LEVEL=none \ OTEL_INJECTOR_DISABLED=true \ cat /proc/self/environ > overly-long-env-var open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps/000077500000000000000000000000001524057645600300435ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps/copy-proc-self-maps.js000077500000000000000000000007521524057645600342100ustar00rootroot00000000000000#!/usr/bin/env node const fs = require('node:fs'); const path = require('node:path'); const os = require('node:os'); const sourcePath = '/proc/self/maps'; const outDir = '/workspace'; const destPath = path.join(outDir, 'proc-self-maps'); try { const data = fs.readFileSync(sourcePath); fs.writeFileSync(destPath, data); console.log(`successfully copied ${sourcePath} to ${destPath}`); } catch (error) { console.error(`error copying file: ${error.message}`); process.exit(1); } open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps/maps-glibc-arm64000066400000000000000000000142541524057645600327410ustar00rootroot0000000000000000400000-06984000 r-xp 00000000 00:c4 746837 /usr/local/bin/node 0699c000-069a0000 r--p 0658c000 00:c4 746837 /usr/local/bin/node 069a0000-069e2000 rw-p 06590000 00:c4 746837 /usr/local/bin/node 069e2000-06a15000 rw-p 00000000 00:00 0 11229000-11421000 rw-p 00000000 00:00 0 [heap] 30e00000-30e3d000 rw-p 00000000 00:00 0 1c7340000-1c7380000 rw-p 00000000 00:00 0 34f640000-34f680000 rw-p 00000000 00:00 0 359080000-3590c0000 rw-p 00000000 00:00 0 410340000-410380000 r--p 00000000 00:00 0 7bd840000-7bd880000 rw-p 00000000 00:00 0 85c900000-85c940000 rw-p 00000000 00:00 0 8d8380000-8d83c0000 rw-p 00000000 00:00 0 aa9240000-aa9280000 rw-p 00000000 00:00 0 bae2c0000-bae300000 rw-p 00000000 00:00 0 12c4500000-12c4540000 rw-p 00000000 00:00 0 163c600000-163c640000 rw-p 00000000 00:00 0 1a39700000-1a39740000 rw-p 00000000 00:00 0 1a4f0c0000-1a4f100000 rw-p 00000000 00:00 0 1e4cb40000-1e4cb80000 rw-p 00000000 00:00 0 216d900000-216d91b000 r--p 00000000 00:00 0 2c097c0000-2c09801000 rw-p 00000000 00:00 0 3589180000-3589193000 rw-p 00000000 00:00 0 3b98940000-3b98980000 rw-p 00000000 00:00 0 3c04f80000-3c04fc0000 rw-p 00000000 00:00 0 3d70680000-3d706c0000 rw-p 00000000 00:00 0 3e00040000-3e00080000 rw-p 00000000 00:00 0 ffff4c000000-ffff4c021000 rw-p 00000000 00:00 0 ffff4c021000-ffff50000000 ---p 00000000 00:00 0 ffff54000000-ffff54021000 rw-p 00000000 00:00 0 ffff54021000-ffff58000000 ---p 00000000 00:00 0 ffff59400000-ffff69400000 rwxp 00000000 00:00 0 ffff69400000-ffff71400000 ---p 00000000 00:00 0 ffff71600000-ffff71610000 r--p 00000000 00:00 0 ffff71610000-ffff71630000 rw-p 00000000 00:00 0 ffff71630000-ffff81600000 ---p 00000000 00:00 0 ffff81800000-ffff81810000 ---p 00000000 00:00 0 ffff81810000-ffff82010000 rw-p 00000000 00:00 0 ffff82200000-ffff82210000 ---p 00000000 00:00 0 ffff82210000-ffff82a10000 rw-p 00000000 00:00 0 ffff82c00000-ffff82c10000 ---p 00000000 00:00 0 ffff82c10000-ffff83410000 rw-p 00000000 00:00 0 ffff83600000-ffff83610000 ---p 00000000 00:00 0 ffff83610000-ffff83e10000 rw-p 00000000 00:00 0 ffff84000000-ffff84021000 rw-p 00000000 00:00 0 ffff84021000-ffff88000000 ---p 00000000 00:00 0 ffff88200000-ffff88210000 ---p 00000000 00:00 0 ffff88210000-ffff88a10000 rw-p 00000000 00:00 0 ffff88a6f000-ffff88a7f000 ---p 00000000 00:00 0 ffff88a7f000-ffff88a9f000 rw-p 00000000 00:00 0 ffff88a9f000-ffff88ac0000 rw-p 00000000 00:00 0 ffff88ac0000-ffff88ac4000 rw-p 00000000 00:00 0 ffff88ac4000-ffff88b40000 ---p 00000000 00:00 0 ffff88b40000-ffff88c50000 rw-p 00000000 00:00 0 ffff88c50000-ffff88ddb000 r-xp 00000000 00:c4 380551 /usr/lib/aarch64-linux-gnu/libc.so.6 ffff88ddb000-ffff88dec000 ---p 0018b000 00:c4 380551 /usr/lib/aarch64-linux-gnu/libc.so.6 ffff88dec000-ffff88df0000 r--p 0018c000 00:c4 380551 /usr/lib/aarch64-linux-gnu/libc.so.6 ffff88df0000-ffff88df2000 rw-p 00190000 00:c4 380551 /usr/lib/aarch64-linux-gnu/libc.so.6 ffff88df2000-ffff88dff000 rw-p 00000000 00:00 0 ffff88e00000-ffff88ffb000 r-xp 00000000 00:c4 380629 /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.30 ffff88ffb000-ffff89005000 ---p 001fb000 00:c4 380629 /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.30 ffff89005000-ffff89010000 r--p 00205000 00:c4 380629 /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.30 ffff89010000-ffff89013000 rw-p 00210000 00:c4 380629 /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.30 ffff89013000-ffff89016000 rw-p 00000000 00:00 0 ffff8901e000-ffff89050000 rw-p 00000000 00:00 0 ffff89050000-ffff89051000 r-xp 00000000 00:c4 380616 /usr/lib/aarch64-linux-gnu/libpthread.so.0 ffff89051000-ffff8906f000 ---p 00001000 00:c4 380616 /usr/lib/aarch64-linux-gnu/libpthread.so.0 ffff8906f000-ffff89070000 r--p 0000f000 00:c4 380616 /usr/lib/aarch64-linux-gnu/libpthread.so.0 ffff89070000-ffff89071000 rw-p 00010000 00:c4 380616 /usr/lib/aarch64-linux-gnu/libpthread.so.0 ffff89080000-ffff89094000 r-xp 00000000 00:c4 380573 /usr/lib/aarch64-linux-gnu/libgcc_s.so.1 ffff89094000-ffff890af000 ---p 00014000 00:c4 380573 /usr/lib/aarch64-linux-gnu/libgcc_s.so.1 ffff890af000-ffff890b0000 r--p 0001f000 00:c4 380573 /usr/lib/aarch64-linux-gnu/libgcc_s.so.1 ffff890b0000-ffff890b1000 rw-p 00020000 00:c4 380573 /usr/lib/aarch64-linux-gnu/libgcc_s.so.1 ffff890c0000-ffff89142000 r-xp 00000000 00:c4 380590 /usr/lib/aarch64-linux-gnu/libm.so.6 ffff89142000-ffff8915f000 ---p 00082000 00:c4 380590 /usr/lib/aarch64-linux-gnu/libm.so.6 ffff8915f000-ffff89160000 r--p 0008f000 00:c4 380590 /usr/lib/aarch64-linux-gnu/libm.so.6 ffff89160000-ffff89161000 rw-p 00090000 00:c4 380590 /usr/lib/aarch64-linux-gnu/libm.so.6 ffff89170000-ffff89171000 r-xp 00000000 00:c4 380564 /usr/lib/aarch64-linux-gnu/libdl.so.2 ffff89171000-ffff8918f000 ---p 00001000 00:c4 380564 /usr/lib/aarch64-linux-gnu/libdl.so.2 ffff8918f000-ffff89190000 r--p 0000f000 00:c4 380564 /usr/lib/aarch64-linux-gnu/libdl.so.2 ffff89190000-ffff89191000 rw-p 00010000 00:c4 380564 /usr/lib/aarch64-linux-gnu/libdl.so.2 ffff89191000-ffff891a1000 rw-p 00000000 00:00 0 ffff891a1000-ffff891c8000 r-xp 00000000 00:c4 380533 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 ffff891d3000-ffff891d8000 rw-p 00000000 00:00 0 ffff891da000-ffff891dc000 rw-p 00000000 00:00 0 ffff891dc000-ffff891de000 r--p 00000000 00:00 0 [vvar] ffff891de000-ffff891df000 r-xp 00000000 00:00 0 [vdso] ffff891df000-ffff891e1000 r--p 0002e000 00:c4 380533 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 ffff891e1000-ffff891e3000 rw-p 00030000 00:c4 380533 /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 ffffeda70000-ffffeda91000 rw-p 00000000 00:00 0 [stack] maps-glibc-arm64-bullseye000066400000000000000000000137751524057645600345130ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps00400000-06984000 r-xp 00000000 00:c4 758666 /usr/local/bin/node 0699c000-069a0000 r--p 0658c000 00:c4 758666 /usr/local/bin/node 069a0000-069e2000 rw-p 06590000 00:c4 758666 /usr/local/bin/node 069e2000-06a15000 rw-p 00000000 00:00 0 0a01b000-0a213000 rw-p 00000000 00:00 0 [heap] 10f80000-10fc0000 rw-p 00000000 00:00 0 4b6c0000-4b6fd000 rw-p 00000000 00:00 0 2f8480000-2f84c1000 rw-p 00000000 00:00 0 32ffc0000-330000000 rw-p 00000000 00:00 0 3679c0000-367a00000 rw-p 00000000 00:00 0 39f200000-39f240000 rw-p 00000000 00:00 0 4797c0000-479800000 rw-p 00000000 00:00 0 667440000-667480000 rw-p 00000000 00:00 0 fc6880000-fc68c0000 rw-p 00000000 00:00 0 110bb00000-110bb13000 rw-p 00000000 00:00 0 1243180000-12431c0000 rw-p 00000000 00:00 0 1325200000-1325240000 rw-p 00000000 00:00 0 134a000000-134a040000 r--p 00000000 00:00 0 13f4bc0000-13f4c00000 rw-p 00000000 00:00 0 1833800000-1833840000 rw-p 00000000 00:00 0 1ca4b80000-1ca4bc0000 rw-p 00000000 00:00 0 1da0440000-1da045b000 r--p 00000000 00:00 0 2963d00000-2963d40000 rw-p 00000000 00:00 0 3071680000-30716c0000 rw-p 00000000 00:00 0 361c400000-361c440000 rw-p 00000000 00:00 0 3d6c480000-3d6c4c0000 rw-p 00000000 00:00 0 3dbc3c0000-3dbc400000 rw-p 00000000 00:00 0 ffff68000000-ffff68021000 rw-p 00000000 00:00 0 ffff68021000-ffff6c000000 ---p 00000000 00:00 0 ffff70000000-ffff70021000 rw-p 00000000 00:00 0 ffff70021000-ffff74000000 ---p 00000000 00:00 0 ffff77200000-ffff87200000 rwxp 00000000 00:00 0 ffff87200000-ffff8f200000 ---p 00000000 00:00 0 ffff8f400000-ffff8f410000 r--p 00000000 00:00 0 ffff8f410000-ffff8f430000 rw-p 00000000 00:00 0 ffff8f430000-ffff9f400000 ---p 00000000 00:00 0 ffff9f600000-ffff9f601000 ---p 00000000 00:00 0 ffff9f601000-ffff9fe01000 rw-p 00000000 00:00 0 ffffa0000000-ffffa0021000 rw-p 00000000 00:00 0 ffffa0021000-ffffa4000000 ---p 00000000 00:00 0 ffffa4a00000-ffffa4a01000 ---p 00000000 00:00 0 ffffa4a01000-ffffa5201000 rw-p 00000000 00:00 0 ffffa5400000-ffffa5401000 ---p 00000000 00:00 0 ffffa5401000-ffffa5c01000 rw-p 00000000 00:00 0 ffffa5e00000-ffffa5e01000 ---p 00000000 00:00 0 ffffa5e01000-ffffa6601000 rw-p 00000000 00:00 0 ffffa6800000-ffffa6801000 ---p 00000000 00:00 0 ffffa6801000-ffffa7001000 rw-p 00000000 00:00 0 ffffa709f000-ffffa70a0000 ---p 00000000 00:00 0 ffffa70a0000-ffffa70c0000 rw-p 00000000 00:00 0 ffffa70c0000-ffffa7103000 rw-p 00000000 00:00 0 ffffa7103000-ffffa7107000 rw-p 00000000 00:00 0 ffffa7107000-ffffa7183000 ---p 00000000 00:00 0 ffffa7183000-ffffa72b3000 rw-p 00000000 00:00 0 ffffa72b3000-ffffa740f000 r-xp 00000000 00:c4 503632 /lib/aarch64-linux-gnu/libc-2.31.so ffffa740f000-ffffa741e000 ---p 0015c000 00:c4 503632 /lib/aarch64-linux-gnu/libc-2.31.so ffffa741e000-ffffa7422000 r--p 0015b000 00:c4 503632 /lib/aarch64-linux-gnu/libc-2.31.so ffffa7422000-ffffa7424000 rw-p 0015f000 00:c4 503632 /lib/aarch64-linux-gnu/libc-2.31.so ffffa7424000-ffffa7427000 rw-p 00000000 00:00 0 ffffa7427000-ffffa7443000 r-xp 00000000 00:c4 503675 /lib/aarch64-linux-gnu/libpthread-2.31.so ffffa7443000-ffffa7452000 ---p 0001c000 00:c4 503675 /lib/aarch64-linux-gnu/libpthread-2.31.so ffffa7452000-ffffa7453000 r--p 0001b000 00:c4 503675 /lib/aarch64-linux-gnu/libpthread-2.31.so ffffa7453000-ffffa7454000 rw-p 0001c000 00:c4 503675 /lib/aarch64-linux-gnu/libpthread-2.31.so ffffa7454000-ffffa7458000 rw-p 00000000 00:00 0 ffffa7458000-ffffa746b000 r-xp 00000000 00:c4 503646 /lib/aarch64-linux-gnu/libgcc_s.so.1 ffffa746b000-ffffa747a000 ---p 00013000 00:c4 503646 /lib/aarch64-linux-gnu/libgcc_s.so.1 ffffa747a000-ffffa747b000 r--p 00012000 00:c4 503646 /lib/aarch64-linux-gnu/libgcc_s.so.1 ffffa747b000-ffffa747c000 rw-p 00013000 00:c4 503646 /lib/aarch64-linux-gnu/libgcc_s.so.1 ffffa747c000-ffffa7515000 r-xp 00000000 00:c4 503653 /lib/aarch64-linux-gnu/libm-2.31.so ffffa7515000-ffffa7525000 ---p 00099000 00:c4 503653 /lib/aarch64-linux-gnu/libm-2.31.so ffffa7525000-ffffa7526000 r--p 00099000 00:c4 503653 /lib/aarch64-linux-gnu/libm-2.31.so ffffa7526000-ffffa7527000 rw-p 0009a000 00:c4 503653 /lib/aarch64-linux-gnu/libm-2.31.so ffffa7527000-ffffa76de000 r-xp 00000000 00:c4 504434 /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.28 ffffa76de000-ffffa76ee000 ---p 001b7000 00:c4 504434 /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.28 ffffa76ee000-ffffa76f9000 r--p 001b7000 00:c4 504434 /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.28 ffffa76f9000-ffffa76fc000 rw-p 001c2000 00:c4 504434 /usr/lib/aarch64-linux-gnu/libstdc++.so.6.0.28 ffffa76fc000-ffffa76ff000 rw-p 00000000 00:00 0 ffffa76ff000-ffffa7702000 r-xp 00000000 00:c4 503640 /lib/aarch64-linux-gnu/libdl-2.31.so ffffa7702000-ffffa7711000 ---p 00003000 00:c4 503640 /lib/aarch64-linux-gnu/libdl-2.31.so ffffa7711000-ffffa7712000 r--p 00002000 00:c4 503640 /lib/aarch64-linux-gnu/libdl-2.31.so ffffa7712000-ffffa7713000 rw-p 00003000 00:c4 503640 /lib/aarch64-linux-gnu/libdl-2.31.so ffffa7713000-ffffa7734000 r-xp 00000000 00:c4 503620 /lib/aarch64-linux-gnu/ld-2.31.so ffffa7737000-ffffa773f000 rw-p 00000000 00:00 0 ffffa7741000-ffffa7743000 r--p 00000000 00:00 0 [vvar] ffffa7743000-ffffa7744000 r-xp 00000000 00:00 0 [vdso] ffffa7744000-ffffa7745000 r--p 00021000 00:c4 503620 /lib/aarch64-linux-gnu/ld-2.31.so ffffa7745000-ffffa7747000 rw-p 00022000 00:c4 503620 /lib/aarch64-linux-gnu/ld-2.31.so ffffe0ee2000-ffffe0f03000 rw-p 00000000 00:00 0 [stack] maps-glibc-first-pass-no-terminating-newline000066400000000000000000000004711524057645600404100ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps7fffff189000-7fffff2c0000 rw-p 00000000 00:00 0 7fffff189000-7fffff2c0000 rw-p 00000000 00:00 0 7fffff2c0000-7fffff2e6000 r--p 00000000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff2e6000-7fffff43c000 r-xp 00026000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6maps-glibc-first-pass-overly-long-line000066400000000000000000000131521524057645600372200ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps7fffff189000-7fffff2c0000 rw-p 00000000 00:00 0 7fffff189000-7fffff2c0000 rw-p 00000000 00:00 0 7fffff2c0000-7fffff2e9999 r--p 00000000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 -- this is a very long line, in fact, it is longer than the length of the read buffer in src/libc.zig, we use it to verify that a line like this is discarded but the parser continues with the next line afterwards ..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... 7fffff2c0000-7fffff2e6000 r--p 00000000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff2e6000-7fffff43c000 r-xp 00026000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff43c000-7fffff48f000 r--p 0017c000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff48f000-7fffff493000 r--p 001cf000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff493000-7fffff495000 rw-p 001d3000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 maps-glibc-second-pass-no-terminating-newline000066400000000000000000000004671524057645600405410ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps7fffff172000-7fffff2a7000 rw-p 00000000 00:00 0 7fffff172001-7fffff2a7001 rw-p 00000000 00:00 0 7fffff2a7000-7fffff2c9000 r--p 00000000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.so 7fffff2c9000-7fffff422000 r-xp 00022000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.somaps-glibc-second-pass-overly-long-line000066400000000000000000000125171524057645600373500ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps7fffff172000-7fffff2a7000 rw-p 00000000 00:00 0 7fffff172001-7fffff2a7001 rw-p 00000000 00:00 0 -- this is a very long line, in fact, it is longer than the length of the read buffer in src/libc.zig, we use it to verify that a line like this is discarded but the parser continues with the next line afterwards ..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... 7fffff2a7000-7fffff2c9000 r--p 00000000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.so 7fffff2c9000-7fffff422000 r-xp 00022000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.so 7fffff422000-7fffff471000 r--p 0017b000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.so 7fffff477000-7fffff47b000 rw-p 00000000 00:00 0 open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps/maps-glibc-x86_64000066400000000000000000000227661524057645600327550ustar00rootroot0000000000000000400000-00f3e000 r--p 00000000 00:c4 743334 /usr/local/bin/node 00f3e000-00f41000 r-xp 00b3e000 00:c4 743334 /usr/local/bin/node 00f41000-00f42000 ---p 00000000 00:00 0 00f42000-03730000 r-xp 00b42000 00:c4 743334 /usr/local/bin/node 03730000-03800000 ---p 00000000 00:00 0 03800000-03801000 r-xp 03400000 00:c4 743334 /usr/local/bin/node 03801000-06e47000 r--p 03401000 00:c4 743334 /usr/local/bin/node 06e47000-06e4b000 r--p 06a46000 00:c4 743334 /usr/local/bin/node 06e4b000-06e76000 rw-p 06a4a000 00:c4 743334 /usr/local/bin/node 06e76000-070d5000 rw-p 00000000 00:00 0 5c81c00000-5c81c40000 rw-p 00000000 00:00 0 7326f900000-7326f907000 rw-p 00000000 00:00 0 98a3d580000-98a3d5c0000 rw-p 00000000 00:00 0 148c1af80000-148c1afc0000 rw-p 00000000 00:00 0 151b33fc0000-151b34000000 rw-p 00000000 00:00 0 1603010c0000-160301100000 rw-p 00000000 00:00 0 160346040000-160346080000 rw-p 00000000 00:00 0 192f70dc0000-192f70e00000 rw-p 00000000 00:00 0 19952dd80000-19952ddc0000 rw-p 00000000 00:00 0 222dbab80000-222dbab9e000 r--p 00000000 00:00 0 224969f80000-224969fc0000 rw-p 00000000 00:00 0 236cf5cc0000-236cf5d00000 rw-p 00000000 00:00 0 26b890c00000-26b890c40000 rw-p 00000000 00:00 0 27503b540000-27503b580000 rw-p 00000000 00:00 0 2cb4601c0000-2cb460200000 rw-p 00000000 00:00 0 2ee9c8c00000-2ee9c8c40000 rw-p 00000000 00:00 0 3332940c0000-333294100000 rw-p 00000000 00:00 0 33a215c80000-33a215cc1000 rw-p 00000000 00:00 0 346046a00000-346046a40000 rw-p 00000000 00:00 0 35dedfe80000-35dedfec0000 rw-p 00000000 00:00 0 380b64bc0000-380b64c00000 rw-p 00000000 00:00 0 38b5edc80000-38b5edc87000 rw-p 00000000 00:00 0 3e9cbf840000-3e9cbf880000 rw-p 00000000 00:00 0 3e9fb0340000-3e9fb0380000 rw-p 00000000 00:00 0 7fffd0000000-7fffd0021000 rw-p 00000000 00:00 0 7fffd0021000-7fffd4000000 ---p 00000000 00:00 0 7fffd8000000-7fffd8021000 rw-p 00000000 00:00 0 7fffd8021000-7fffdc000000 ---p 00000000 00:00 0 7fffe0000000-7fffe8000000 ---p 00000000 00:00 0 7fffe8000000-7fffe8010000 r--p 00000000 00:00 0 7fffe8010000-7fffe8030000 rw-p 00000000 00:00 0 7fffe8030000-7ffff8000000 ---p 00000000 00:00 0 7ffff8000000-7ffff8021000 rw-p 00000000 00:00 0 7ffff8021000-7ffffc000000 ---p 00000000 00:00 0 7ffffc8da000-7ffffc8db000 ---p 00000000 00:00 0 7ffffc8db000-7ffffc8e3000 rw-p 00000000 00:00 0 7ffffc8e3000-7ffffc904000 rw-p 00000000 00:00 0 7ffffc904000-7ffffc908000 rw-p 00000000 00:00 0 7ffffc908000-7ffffc984000 ---p 00000000 00:00 0 7ffffc984000-7ffffc985000 ---p 00000000 00:00 0 7ffffc985000-7ffffd185000 rw-p 00000000 00:00 0 7ffffd185000-7ffffd186000 ---p 00000000 00:00 0 7ffffd186000-7ffffd986000 rw-p 00000000 00:00 0 7ffffd986000-7ffffd987000 ---p 00000000 00:00 0 7ffffd987000-7ffffe187000 rw-p 00000000 00:00 0 7ffffe187000-7ffffe188000 ---p 00000000 00:00 0 7ffffe188000-7ffffe988000 rw-p 00000000 00:00 0 7ffffe988000-7ffffe989000 ---p 00000000 00:00 0 7ffffe989000-7fffff189000 rw-p 00000000 00:00 0 7fffff189000-7fffff2c0000 rw-p 00000000 00:00 0 7fffff2c0000-7fffff2e6000 r--p 00000000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff2e6000-7fffff43c000 r-xp 00026000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff43c000-7fffff48f000 r--p 0017c000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff48f000-7fffff493000 r--p 001cf000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff493000-7fffff495000 rw-p 001d3000 00:c4 739980 /usr/lib/x86_64-linux-gnu/libc.so.6 7fffff495000-7fffff4a2000 rw-p 00000000 00:00 0 7fffff4a2000-7fffff4a3000 r--p 00000000 00:c4 740046 /usr/lib/x86_64-linux-gnu/libpthread.so.0 7fffff4a3000-7fffff4a4000 r-xp 00001000 00:c4 740046 /usr/lib/x86_64-linux-gnu/libpthread.so.0 7fffff4a4000-7fffff4a5000 r--p 00002000 00:c4 740046 /usr/lib/x86_64-linux-gnu/libpthread.so.0 7fffff4a5000-7fffff4a6000 r--p 00002000 00:c4 740046 /usr/lib/x86_64-linux-gnu/libpthread.so.0 7fffff4a6000-7fffff4a7000 rw-p 00003000 00:c4 740046 /usr/lib/x86_64-linux-gnu/libpthread.so.0 7fffff4a7000-7fffff4aa000 r--p 00000000 00:c4 740002 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4aa000-7fffff4c1000 r-xp 00003000 00:c4 740002 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4c1000-7fffff4c5000 r--p 0001a000 00:c4 740002 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4c5000-7fffff4c6000 r--p 0001d000 00:c4 740002 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4c6000-7fffff4c7000 rw-p 0001e000 00:c4 740002 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4c7000-7fffff4d7000 r--p 00000000 00:c4 740019 /usr/lib/x86_64-linux-gnu/libm.so.6 7fffff4d7000-7fffff54b000 r-xp 00010000 00:c4 740019 /usr/lib/x86_64-linux-gnu/libm.so.6 7fffff54b000-7fffff5a5000 r--p 00084000 00:c4 740019 /usr/lib/x86_64-linux-gnu/libm.so.6 7fffff5a5000-7fffff5a6000 r--p 000dd000 00:c4 740019 /usr/lib/x86_64-linux-gnu/libm.so.6 7fffff5a6000-7fffff5a7000 rw-p 000de000 00:c4 740019 /usr/lib/x86_64-linux-gnu/libm.so.6 7fffff5a7000-7fffff640000 r--p 00000000 00:c4 740059 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30 7fffff640000-7fffff741000 r-xp 00099000 00:c4 740059 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30 7fffff741000-7fffff7b0000 r--p 0019a000 00:c4 740059 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30 7fffff7b0000-7fffff7bb000 r--p 00209000 00:c4 740059 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30 7fffff7bb000-7fffff7be000 rw-p 00214000 00:c4 740059 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30 7fffff7be000-7fffff7c1000 rw-p 00000000 00:00 0 7fffff7c1000-7fffff7c2000 r--p 00000000 00:c4 739993 /usr/lib/x86_64-linux-gnu/libdl.so.2 7fffff7c2000-7fffff7c3000 r-xp 00001000 00:c4 739993 /usr/lib/x86_64-linux-gnu/libdl.so.2 7fffff7c3000-7fffff7c4000 r--p 00002000 00:c4 739993 /usr/lib/x86_64-linux-gnu/libdl.so.2 7fffff7c4000-7fffff7c5000 r--p 00002000 00:c4 739993 /usr/lib/x86_64-linux-gnu/libdl.so.2 7fffff7c5000-7fffff7c6000 rw-p 00003000 00:c4 739993 /usr/lib/x86_64-linux-gnu/libdl.so.2 7fffff7c6000-7fffff7c8000 rw-p 00000000 00:00 0 7fffff7ca000-7fffff7cb000 ---p 00000000 00:00 0 7fffff7cb000-7ffffffcb000 rw-p 00000000 00:00 0 7ffffffcb000-7ffffffcc000 r--p 00000000 00:c4 739962 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7ffffffcc000-7fffffff2000 r-xp 00001000 00:c4 739962 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffffff2000-7fffffffc000 r--p 00027000 00:c4 739962 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffffffc000-7fffffffe000 r--p 00031000 00:c4 739962 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 7fffffffe000-800000000000 rw-p 00033000 00:c4 739962 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 800000000000-800000026000 r--p 00000000 00:2a 2 /run/rosetta/rosetta 800000026000-800000097000 r-xp 00026000 00:2a 2 /run/rosetta/rosetta 8000000a0000-8000001a6000 rw-p 000a0000 00:2a 2 /run/rosetta/rosetta effff7860000-effff7998000 rw-p 00000000 00:00 0 effff7998000-effff7999000 ---p 00000000 00:00 0 effff7999000-effff799d000 rw-p 00000000 00:00 0 effff799d000-effff799e000 ---p 00000000 00:00 0 effff799e000-effff7c9d000 rw-p 00000000 00:00 0 effff7c9d000-effff7c9e000 ---p 00000000 00:00 0 effff7c9e000-effff7ca2000 rw-p 00000000 00:00 0 effff7ca2000-effff7ca3000 ---p 00000000 00:00 0 effff7ca3000-effff7cab000 rw-p 00000000 00:00 0 effff7cab000-effff7cac000 ---p 00000000 00:00 0 effff7cac000-effff7cb0000 rw-p 00000000 00:00 0 effff7cb0000-effff7cb1000 ---p 00000000 00:00 0 effff7cb1000-effff7cb9000 rw-p 00000000 00:00 0 effff7cb9000-effff7cba000 ---p 00000000 00:00 0 effff7cba000-effff7cc2000 rw-p 00000000 00:00 0 effff7cc2000-effff7cc3000 ---p 00000000 00:00 0 effff7cc3000-effff7cc7000 rw-p 00000000 00:00 0 effff7cc7000-effff7cc8000 ---p 00000000 00:00 0 effff7cc8000-effff7d8e000 rw-p 00000000 00:00 0 effff7d8e000-effff7d90000 rw-p 00000000 00:00 0 effff7d90000-effff7d91000 ---p 00000000 00:00 0 effff7d91000-effff7d95000 rw-p 00000000 00:00 0 effff7d95000-effff7d96000 ---p 00000000 00:00 0 effff7d96000-effff7de7000 rw-p 00000000 00:00 0 effff7de7000-effff7de8000 ---p 00000000 00:00 0 effff7de8000-effff7dec000 rw-p 00000000 00:00 0 effff7dec000-effff7ded000 ---p 00000000 00:00 0 effff7ded000-effff7ff8000 rw-p 00000000 00:00 0 effff7ff8000-efffffff8000 rwxp 00000000 00:00 0 efffffff8000-efffffff9000 ---p 00000000 00:00 0 efffffff9000-f0001685e000 rw-p 00000000 00:00 0 ffff93400000-ffff93403000 r-xp 00000000 00:00 0 ffff93403000-ffffb31c0000 rwxp 00000000 00:00 0 ffffb31c0000-ffffb33c4000 r-xp 01d51000 00:c4 743334 /usr/local/bin/node ffffb33c4000-ffffb3400000 rwxp 00000000 00:00 0 ffffb35f3000-ffffb35f5000 r--p 00000000 00:00 0 [vvar] ffffb35f5000-ffffb35f6000 r-xp 00000000 00:00 0 [vdso] ffffc07ed000-ffffc080e000 rw-p 00000000 00:00 0 [stack] maps-glibc-x86_64-bullseye000066400000000000000000000227441524057645600345140ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps00400000-00f3e000 r--p 00000000 00:c4 755179 /usr/local/bin/node 00f3e000-00f41000 r-xp 00b3e000 00:c4 755179 /usr/local/bin/node 00f41000-00f42000 ---p 00000000 00:00 0 00f42000-03730000 r-xp 00b42000 00:c4 755179 /usr/local/bin/node 03730000-03800000 ---p 00000000 00:00 0 03800000-03801000 r-xp 03400000 00:c4 755179 /usr/local/bin/node 03801000-06e47000 r--p 03401000 00:c4 755179 /usr/local/bin/node 06e47000-06e4b000 r--p 06a46000 00:c4 755179 /usr/local/bin/node 06e4b000-06e76000 rw-p 06a4a000 00:c4 755179 /usr/local/bin/node 06e76000-070d5000 rw-p 00000000 00:00 0 8fb7180000-8fb71c0000 rw-p 00000000 00:00 0 521ea180000-521ea1c0000 rw-p 00000000 00:00 0 7da70340000-7da70380000 rw-p 00000000 00:00 0 941d8dc0000-941d8e00000 rw-p 00000000 00:00 0 c7d66e00000-c7d66e40000 rw-p 00000000 00:00 0 dde63580000-dde635c0000 rw-p 00000000 00:00 0 111673240000-111673280000 rw-p 00000000 00:00 0 1130e2780000-1130e27c0000 rw-p 00000000 00:00 0 119560100000-119560140000 rw-p 00000000 00:00 0 16dacd880000-16dacd89e000 r--p 00000000 00:00 0 17499d9c0000-17499da00000 rw-p 00000000 00:00 0 19227d5c0000-19227d600000 rw-p 00000000 00:00 0 19b0566c0000-19b056700000 rw-p 00000000 00:00 0 1a906ec80000-1a906ecc0000 rw-p 00000000 00:00 0 1c7d03d40000-1c7d03d47000 rw-p 00000000 00:00 0 1fc068700000-1fc068740000 rw-p 00000000 00:00 0 2907b3b80000-2907b3bc0000 rw-p 00000000 00:00 0 2a5c118c0000-2a5c11900000 rw-p 00000000 00:00 0 2abeba9c0000-2abebaa00000 rw-p 00000000 00:00 0 2bd1f2540000-2bd1f2580000 rw-p 00000000 00:00 0 2e9fd5180000-2e9fd51c1000 rw-p 00000000 00:00 0 35fbc4340000-35fbc4347000 rw-p 00000000 00:00 0 368308580000-3683085c0000 rw-p 00000000 00:00 0 3e887b500000-3e887b540000 rw-p 00000000 00:00 0 7fffd0000000-7fffd0021000 rw-p 00000000 00:00 0 7fffd0021000-7fffd4000000 ---p 00000000 00:00 0 7fffd8000000-7fffd8021000 rw-p 00000000 00:00 0 7fffd8021000-7fffdc000000 ---p 00000000 00:00 0 7fffe0000000-7fffe8000000 ---p 00000000 00:00 0 7fffe8000000-7fffe8010000 r--p 00000000 00:00 0 7fffe8010000-7fffe8030000 rw-p 00000000 00:00 0 7fffe8030000-7ffff8000000 ---p 00000000 00:00 0 7ffff8000000-7ffff8021000 rw-p 00000000 00:00 0 7ffff8021000-7ffffc000000 ---p 00000000 00:00 0 7ffffc8c3000-7ffffc8c4000 ---p 00000000 00:00 0 7ffffc8c4000-7ffffc8cc000 rw-p 00000000 00:00 0 7ffffc8cc000-7ffffc8ed000 rw-p 00000000 00:00 0 7ffffc8ed000-7ffffc8f1000 rw-p 00000000 00:00 0 7ffffc8f1000-7ffffc96d000 ---p 00000000 00:00 0 7ffffc96d000-7ffffc96e000 ---p 00000000 00:00 0 7ffffc96e000-7ffffd16e000 rw-p 00000000 00:00 0 7ffffd16e000-7ffffd16f000 ---p 00000000 00:00 0 7ffffd16f000-7ffffd96f000 rw-p 00000000 00:00 0 7ffffd96f000-7ffffd970000 ---p 00000000 00:00 0 7ffffd970000-7ffffe170000 rw-p 00000000 00:00 0 7ffffe170000-7ffffe171000 ---p 00000000 00:00 0 7ffffe171000-7ffffe971000 rw-p 00000000 00:00 0 7ffffe971000-7ffffe972000 ---p 00000000 00:00 0 7ffffe972000-7fffff172000 rw-p 00000000 00:00 0 7fffff172000-7fffff2a7000 rw-p 00000000 00:00 0 7fffff2a7000-7fffff2c9000 r--p 00000000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.so 7fffff2c9000-7fffff422000 r-xp 00022000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.so 7fffff422000-7fffff471000 r--p 0017b000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.so 7fffff471000-7fffff475000 r--p 001c9000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.so 7fffff475000-7fffff477000 rw-p 001cd000 00:c4 750649 /lib/x86_64-linux-gnu/libc-2.31.so 7fffff477000-7fffff47b000 rw-p 00000000 00:00 0 7fffff47b000-7fffff481000 r--p 00000000 00:c4 750694 /lib/x86_64-linux-gnu/libpthread-2.31.so 7fffff481000-7fffff491000 r-xp 00006000 00:c4 750694 /lib/x86_64-linux-gnu/libpthread-2.31.so 7fffff491000-7fffff497000 r--p 00016000 00:c4 750694 /lib/x86_64-linux-gnu/libpthread-2.31.so 7fffff497000-7fffff498000 r--p 0001b000 00:c4 750694 /lib/x86_64-linux-gnu/libpthread-2.31.so 7fffff498000-7fffff499000 rw-p 0001c000 00:c4 750694 /lib/x86_64-linux-gnu/libpthread-2.31.so 7fffff499000-7fffff49d000 rw-p 00000000 00:00 0 7fffff49d000-7fffff4a0000 r--p 00000000 00:c4 750663 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4a0000-7fffff4b1000 r-xp 00003000 00:c4 750663 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4b1000-7fffff4b5000 r--p 00014000 00:c4 750663 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4b5000-7fffff4b6000 r--p 00017000 00:c4 750663 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4b6000-7fffff4b7000 rw-p 00018000 00:c4 750663 /lib/x86_64-linux-gnu/libgcc_s.so.1 7fffff4b7000-7fffff4c4000 r--p 00000000 00:c4 750670 /lib/x86_64-linux-gnu/libm-2.31.so 7fffff4c4000-7fffff55e000 r-xp 0000d000 00:c4 750670 /lib/x86_64-linux-gnu/libm-2.31.so 7fffff55e000-7fffff5f9000 r--p 000a7000 00:c4 750670 /lib/x86_64-linux-gnu/libm-2.31.so 7fffff5f9000-7fffff5fa000 r--p 00141000 00:c4 750670 /lib/x86_64-linux-gnu/libm-2.31.so 7fffff5fa000-7fffff5fb000 rw-p 00142000 00:c4 750670 /lib/x86_64-linux-gnu/libm-2.31.so 7fffff5fb000-7fffff691000 r--p 00000000 00:c4 751425 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 7fffff691000-7fffff76d000 r-xp 00096000 00:c4 751425 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 7fffff76d000-7fffff7b7000 r--p 00172000 00:c4 751425 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 7fffff7b7000-7fffff7c2000 r--p 001bb000 00:c4 751425 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 7fffff7c2000-7fffff7c5000 rw-p 001c6000 00:c4 751425 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 7fffff7c5000-7fffff7ca000 rw-p 00000000 00:00 0 7fffff7ca000-7fffff7cb000 r--p 00000000 00:c4 750657 /lib/x86_64-linux-gnu/libdl-2.31.so 7fffff7cb000-7fffff7cd000 r-xp 00001000 00:c4 750657 /lib/x86_64-linux-gnu/libdl-2.31.so 7fffff7cd000-7fffff7ce000 r--p 00003000 00:c4 750657 /lib/x86_64-linux-gnu/libdl-2.31.so 7fffff7ce000-7fffff7cf000 r--p 00003000 00:c4 750657 /lib/x86_64-linux-gnu/libdl-2.31.so 7fffff7cf000-7fffff7d0000 rw-p 00004000 00:c4 750657 /lib/x86_64-linux-gnu/libdl-2.31.so 7fffff7d2000-7fffff7d3000 ---p 00000000 00:00 0 7fffff7d3000-7ffffffd3000 rw-p 00000000 00:00 0 7ffffffd3000-7ffffffd4000 r--p 00000000 00:c4 750637 /lib/x86_64-linux-gnu/ld-2.31.so 7ffffffd4000-7fffffff4000 r-xp 00001000 00:c4 750637 /lib/x86_64-linux-gnu/ld-2.31.so 7fffffff4000-7fffffffc000 r--p 00021000 00:c4 750637 /lib/x86_64-linux-gnu/ld-2.31.so 7fffffffc000-7fffffffd000 ---p 00000000 00:00 0 7fffffffd000-7fffffffe000 r--p 00029000 00:c4 750637 /lib/x86_64-linux-gnu/ld-2.31.so 7fffffffe000-7ffffffff000 rw-p 0002a000 00:c4 750637 /lib/x86_64-linux-gnu/ld-2.31.so 7ffffffff000-800000000000 rw-p 00000000 00:00 0 800000000000-800000026000 r--p 00000000 00:2a 2 /run/rosetta/rosetta 800000026000-800000097000 r-xp 00026000 00:2a 2 /run/rosetta/rosetta 8000000a0000-8000001a6000 rw-p 000a0000 00:2a 2 /run/rosetta/rosetta effff785b000-effff7993000 rw-p 00000000 00:00 0 effff7993000-effff7994000 ---p 00000000 00:00 0 effff7994000-effff7998000 rw-p 00000000 00:00 0 effff7998000-effff7999000 ---p 00000000 00:00 0 effff7999000-effff7c9a000 rw-p 00000000 00:00 0 effff7c9a000-effff7c9b000 ---p 00000000 00:00 0 effff7c9b000-effff7c9f000 rw-p 00000000 00:00 0 effff7c9f000-effff7ca0000 ---p 00000000 00:00 0 effff7ca0000-effff7ca8000 rw-p 00000000 00:00 0 effff7ca8000-effff7ca9000 ---p 00000000 00:00 0 effff7ca9000-effff7cad000 rw-p 00000000 00:00 0 effff7cad000-effff7cae000 ---p 00000000 00:00 0 effff7cae000-effff7cb6000 rw-p 00000000 00:00 0 effff7cb6000-effff7cb7000 ---p 00000000 00:00 0 effff7cb7000-effff7cbf000 rw-p 00000000 00:00 0 effff7cbf000-effff7cc0000 ---p 00000000 00:00 0 effff7cc0000-effff7cc4000 rw-p 00000000 00:00 0 effff7cc4000-effff7cc5000 ---p 00000000 00:00 0 effff7cc5000-effff7d8f000 rw-p 00000000 00:00 0 effff7d8f000-effff7d91000 rw-p 00000000 00:00 0 effff7d91000-effff7d92000 ---p 00000000 00:00 0 effff7d92000-effff7d96000 rw-p 00000000 00:00 0 effff7d96000-effff7d97000 ---p 00000000 00:00 0 effff7d97000-effff7de7000 rw-p 00000000 00:00 0 effff7de7000-effff7de8000 ---p 00000000 00:00 0 effff7de8000-effff7dec000 rw-p 00000000 00:00 0 effff7dec000-effff7ded000 ---p 00000000 00:00 0 effff7ded000-effff7ff8000 rw-p 00000000 00:00 0 effff7ff8000-efffffff8000 rwxp 00000000 00:00 0 efffffff8000-efffffff9000 ---p 00000000 00:00 0 efffffff9000-f0001685e000 rw-p 00000000 00:00 0 ffff87e00000-ffff87e03000 r-xp 00000000 00:00 0 ffff87e03000-ffffa7bc0000 rwxp 00000000 00:00 0 ffffa7bc0000-ffffa7dc4000 r-xp 01d51000 00:c4 755179 /usr/local/bin/node ffffa7dc4000-ffffa7e00000 rwxp 00000000 00:00 0 ffffa7ef2000-ffffa7ef4000 r--p 00000000 00:00 0 [vvar] ffffa7ef4000-ffffa7ef5000 r-xp 00000000 00:00 0 [vdso] ffffd6ed1000-ffffd6ef2000 rw-p 00000000 00:00 0 [stack] open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps/maps-musl-arm64000066400000000000000000000076001524057645600326360ustar00rootroot00000000000000b51c0000-b5200000 rw-p 00000000 00:00 0 45f880000-45f8c0000 rw-p 00000000 00:00 0 5a4100000-5a4140000 rw-p 00000000 00:00 0 77f980000-77f9c0000 rw-p 00000000 00:00 0 c2f1c0000-c2f200000 rw-p 00000000 00:00 0 ef5500000-ef5540000 rw-p 00000000 00:00 0 1160d40000-1160d80000 rw-p 00000000 00:00 0 12c6d00000-12c6d40000 r--p 00000000 00:00 0 1300f40000-1300f7d000 rw-p 00000000 00:00 0 131f880000-131f8c0000 rw-p 00000000 00:00 0 13c0940000-13c0980000 rw-p 00000000 00:00 0 1724b80000-1724bc0000 rw-p 00000000 00:00 0 27bbc00000-27bbc41000 rw-p 00000000 00:00 0 28a7f00000-28a7f1b000 r--p 00000000 00:00 0 2b43e00000-2b43e40000 rw-p 00000000 00:00 0 2c84980000-2c849c0000 rw-p 00000000 00:00 0 2dc3e40000-2dc3e80000 rw-p 00000000 00:00 0 35319c0000-35319d3000 rw-p 00000000 00:00 0 3788c00000-3788c40000 rw-p 00000000 00:00 0 3851c80000-3851cc0000 rw-p 00000000 00:00 0 3b3f100000-3b3f140000 rw-p 00000000 00:00 0 3da1040000-3da1080000 rw-p 00000000 00:00 0 aaaad01e0000-aaaad6ac8000 r-xp 00000000 00:c4 712663 /usr/local/bin/node aaaad6ada000-aaaad6c00000 r--p 068ea000 00:c4 712663 /usr/local/bin/node aaaad6c00000-aaaad6c43000 rw-p 06a10000 00:c4 712663 /usr/local/bin/node aaaad6c43000-aaaad6c74000 rw-p 00000000 00:00 0 aaab0aa5c000-aaab0aa5d000 ---p 00000000 00:00 0 [heap] aaab0aa5d000-aaab0aa64000 rw-p 00000000 00:00 0 [heap] ffff87c00000-ffff97c00000 rwxp 00000000 00:00 0 ffff97c00000-ffff9fc00000 ---p 00000000 00:00 0 ffff9fe00000-ffff9fe10000 r--p 00000000 00:00 0 ffff9fe10000-ffff9fe30000 rw-p 00000000 00:00 0 ffff9fe30000-ffffafe00000 ---p 00000000 00:00 0 ffffb0000000-ffffb0002000 ---p 00000000 00:00 0 ffffb0002000-ffffb0803000 rw-p 00000000 00:00 0 ffffb0a00000-ffffb0a02000 ---p 00000000 00:00 0 ffffb0a02000-ffffb1203000 rw-p 00000000 00:00 0 ffffb1400000-ffffb1402000 ---p 00000000 00:00 0 ffffb1402000-ffffb1c03000 rw-p 00000000 00:00 0 ffffb1e00000-ffffb1e02000 ---p 00000000 00:00 0 ffffb1e02000-ffffb2603000 rw-p 00000000 00:00 0 ffffb27d2000-ffffb27d9000 rw-p 00000000 00:00 0 ffffb27e0000-ffffb27f7000 rw-p 00000000 00:00 0 ffffb2800000-ffffb2802000 ---p 00000000 00:00 0 ffffb2802000-ffffb3003000 rw-p 00000000 00:00 0 ffffb300e000-ffffb3035000 rw-p 00000000 00:00 0 ffffb3035000-ffffb3037000 ---p 00000000 00:00 0 ffffb3037000-ffffb3047000 rw-p 00000000 00:00 0 ffffb304c000-ffffb3200000 rw-p 00000000 00:00 0 ffffb3200000-ffffb3492000 r-xp 00000000 00:c4 712519 /usr/lib/libstdc++.so.6.0.33 ffffb3492000-ffffb34a0000 r--p 00292000 00:c4 712519 /usr/lib/libstdc++.so.6.0.33 ffffb34a0000-ffffb34a1000 rw-p 002a0000 00:c4 712519 /usr/lib/libstdc++.so.6.0.33 ffffb34a1000-ffffb34a5000 rw-p 00000000 00:00 0 ffffb34a5000-ffffb34ec000 rw-p 00000000 00:00 0 ffffb34ec000-ffffb34f0000 rw-p 00000000 00:00 0 ffffb34f0000-ffffb356c000 ---p 00000000 00:00 0 ffffb356c000-ffffb363f000 rw-p 00000000 00:00 0 ffffb363f000-ffffb366e000 r-xp 00000000 00:c4 486780 /usr/lib/libgcc_s.so.1 ffffb366e000-ffffb366f000 r--p 0001f000 00:c4 486780 /usr/lib/libgcc_s.so.1 ffffb366f000-ffffb3670000 rw-p 00020000 00:c4 486780 /usr/lib/libgcc_s.so.1 ffffb3670000-ffffb3712000 r-xp 00000000 00:c4 479229 /lib/ld-musl-aarch64.so.1 ffffb3712000-ffffb372c000 rw-p 00000000 00:00 0 ffffb372c000-ffffb372e000 r--p 00000000 00:00 0 [vvar] ffffb372e000-ffffb372f000 r-xp 00000000 00:00 0 [vdso] ffffb372f000-ffffb3730000 r--p 000af000 00:c4 479229 /lib/ld-musl-aarch64.so.1 ffffb3730000-ffffb3731000 rw-p 000b0000 00:c4 479229 /lib/ld-musl-aarch64.so.1 ffffb3731000-ffffb3733000 rw-p 00000000 00:00 0 ffffdebb5000-ffffdebd6000 rw-p 00000000 00:00 0 [stack] maps-musl-no-terminating-newline000066400000000000000000000004431524057645600362160ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps7fffff759000-7fffff75a000 ---p 00000000 00:00 0 7fffff75a000-7ffffff5a000 rw-p 00000000 00:00 0 7ffffff5a000-7ffffff6e000 r--p 00000000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7ffffff6e000-7ffffffc5000 r-xp 00014000 00:c4 965126 /lib/ld-musl-x86_64.so.1maps-musl-overly-long-line000066400000000000000000000127061524057645600350330ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps7fffff759000-7fffff75a000 ---p 00000000 00:00 0 7fffff75a000-7ffffff5a000 rw-p 00000000 00:00 0 -- this is a very long line, in fact, it is longer than the length of the read buffer in src/libc.zig, we use it to verify that a line like this is discarded but the parser continues with the next line afterwards ..................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... 7ffffff5a000-7ffffff6e000 r--p 00000000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7ffffff6e000-7ffffffc5000 r-xp 00014000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7ffffffc5000-7fffffffb000 r--p 0006b000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7fffffffb000-7fffffffc000 r--p 000a0000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7fffffffc000-7fffffffd000 rw-p 000a1000 00:c4 965126 /lib/ld-musl-x86_64.so.1open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps/maps-musl-x86_64000066400000000000000000000157561524057645600326560ustar00rootroot000000000000001b372dc0000-1b372e00000 rw-p 00000000 00:00 0 2fdf7e00000-2fdf7e40000 rw-p 00000000 00:00 0 71256100000-71256107000 rw-p 00000000 00:00 0 76bc4180000-76bc41c0000 rw-p 00000000 00:00 0 b2ae5e40000-b2ae5e80000 rw-p 00000000 00:00 0 d09e48c0000-d09e4900000 rw-p 00000000 00:00 0 10c044300000-10c044340000 rw-p 00000000 00:00 0 1327b2500000-1327b2540000 rw-p 00000000 00:00 0 161877380000-1618773c0000 rw-p 00000000 00:00 0 19ce3d080000-19ce3d0c0000 rw-p 00000000 00:00 0 1ac8c8300000-1ac8c8340000 rw-p 00000000 00:00 0 1b667b880000-1b667b887000 rw-p 00000000 00:00 0 1eb066180000-1eb0661c0000 rw-p 00000000 00:00 0 1ec5ba5c0000-1ec5ba600000 rw-p 00000000 00:00 0 1ee6a9fc0000-1ee6a9fde000 r--p 00000000 00:00 0 25d1dc040000-25d1dc080000 rw-p 00000000 00:00 0 26c025940000-26c025980000 rw-p 00000000 00:00 0 2b3a67cc0000-2b3a67d00000 rw-p 00000000 00:00 0 2be9ab340000-2be9ab380000 rw-p 00000000 00:00 0 3010dd900000-3010dd940000 rw-p 00000000 00:00 0 310eea500000-310eea541000 rw-p 00000000 00:00 0 34e9052c0000-34e905300000 rw-p 00000000 00:00 0 3aec8a480000-3aec8a4c0000 rw-p 00000000 00:00 0 3de5b0240000-3de5b0280000 rw-p 00000000 00:00 0 555555554000-5555562bf000 r--p 00000000 00:c4 735656 /usr/local/bin/node 5555562bf000-555558ca3000 r-xp 00d6b000 00:c4 735656 /usr/local/bin/node 555558ca3000-555558d54000 ---p 00000000 00:00 0 555558d54000-555558d55000 r-xp 03800000 00:c4 735656 /usr/local/bin/node 555558d55000-55555c1d5000 r--p 03801000 00:c4 735656 /usr/local/bin/node 55555c1d5000-55555c30a000 r--p 06c80000 00:c4 735656 /usr/local/bin/node 55555c30a000-55555c335000 rw-p 06db5000 00:c4 735656 /usr/local/bin/node 55555c335000-55555c36c000 rw-p 00000000 00:00 0 55555c36c000-55555c36d000 ---p 00000000 00:00 0 55555c36d000-55555c374000 rw-p 00000000 00:00 0 7fffe4861000-7fffe4872000 rw-p 00000000 00:00 0 7fffe4874000-7fffe487a000 rw-p 00000000 00:00 0 7fffe48a3000-7fffe48c4000 rw-p 00000000 00:00 0 7fffe48cc000-7fffe48ce000 ---p 00000000 00:00 0 7fffe48ce000-7fffe48dd000 rw-p 00000000 00:00 0 7fffe48e4000-7fffe4905000 rw-p 00000000 00:00 0 7fffe490f000-7fffe491c000 rw-p 00000000 00:00 0 7fffe491e000-7fffe49e0000 rw-p 00000000 00:00 0 7fffe49e0000-7fffec9e0000 ---p 00000000 00:00 0 7fffec9e0000-7fffec9f0000 rw-p 00000000 00:00 0 7fffec9f0000-7fffeca00000 r--p 00000000 00:00 0 7fffeca00000-7fffeca20000 rw-p 00000000 00:00 0 7fffeca20000-7ffffc9f0000 ---p 00000000 00:00 0 7ffffc9f0000-7ffffc9f7000 rw-p 00000000 00:00 0 7ffffc9f7000-7ffffc9fb000 rw-p 00000000 00:00 0 7ffffc9fb000-7ffffca77000 ---p 00000000 00:00 0 7ffffca77000-7ffffca79000 ---p 00000000 00:00 0 7ffffca79000-7ffffd27a000 rw-p 00000000 00:00 0 7ffffd27a000-7ffffd27c000 ---p 00000000 00:00 0 7ffffd27c000-7ffffda7d000 rw-p 00000000 00:00 0 7ffffda7d000-7ffffda7f000 ---p 00000000 00:00 0 7ffffda7f000-7ffffe280000 rw-p 00000000 00:00 0 7ffffe280000-7ffffe282000 ---p 00000000 00:00 0 7ffffe282000-7ffffea85000 rw-p 00000000 00:00 0 7ffffea85000-7ffffea87000 ---p 00000000 00:00 0 7ffffea87000-7fffff484000 rw-p 00000000 00:00 0 7fffff484000-7fffff488000 r--p 00000000 00:c4 735647 /usr/lib/libgcc_s.so.1 7fffff488000-7fffff4aa000 r-xp 00004000 00:c4 735647 /usr/lib/libgcc_s.so.1 7fffff4aa000-7fffff4ae000 r--p 00026000 00:c4 735647 /usr/lib/libgcc_s.so.1 7fffff4ae000-7fffff4af000 r--p 00029000 00:c4 735647 /usr/lib/libgcc_s.so.1 7fffff4af000-7fffff4b0000 rw-p 0002a000 00:c4 735647 /usr/lib/libgcc_s.so.1 7fffff4b0000-7fffff560000 r--p 00000000 00:c4 735649 /usr/lib/libstdc++.so.6.0.33 7fffff560000-7fffff6ac000 r-xp 000b0000 00:c4 735649 /usr/lib/libstdc++.so.6.0.33 7fffff6ac000-7fffff746000 r--p 001fc000 00:c4 735649 /usr/lib/libstdc++.so.6.0.33 7fffff746000-7fffff754000 r--p 00296000 00:c4 735649 /usr/lib/libstdc++.so.6.0.33 7fffff754000-7fffff755000 rw-p 002a4000 00:c4 735649 /usr/lib/libstdc++.so.6.0.33 7fffff755000-7fffff759000 rw-p 00000000 00:00 0 7fffff759000-7fffff75a000 ---p 00000000 00:00 0 7fffff75a000-7ffffff5a000 rw-p 00000000 00:00 0 7ffffff5a000-7ffffff6e000 r--p 00000000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7ffffff6e000-7ffffffc5000 r-xp 00014000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7ffffffc5000-7fffffffb000 r--p 0006b000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7fffffffb000-7fffffffc000 r--p 000a0000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7fffffffc000-7fffffffd000 rw-p 000a1000 00:c4 965126 /lib/ld-musl-x86_64.so.1 7fffffffd000-800000000000 rw-p 00000000 00:00 0 800000000000-800000026000 r--p 00000000 00:2a 2 /run/rosetta/rosetta 800000026000-800000097000 r-xp 00026000 00:2a 2 /run/rosetta/rosetta 8000000a0000-8000001a6000 rw-p 000a0000 00:2a 2 /run/rosetta/rosetta effff7940000-effff79e7000 rw-p 00000000 00:00 0 effff79e7000-effff79e8000 ---p 00000000 00:00 0 effff79e8000-effff79ec000 rw-p 00000000 00:00 0 effff79ec000-effff79ed000 ---p 00000000 00:00 0 effff79ed000-effff7d0a000 rw-p 00000000 00:00 0 effff7d0a000-effff7d0b000 ---p 00000000 00:00 0 effff7d0b000-effff7d0f000 rw-p 00000000 00:00 0 effff7d0f000-effff7d10000 ---p 00000000 00:00 0 effff7d10000-effff7d18000 rw-p 00000000 00:00 0 effff7d18000-effff7d19000 ---p 00000000 00:00 0 effff7d19000-effff7d1d000 rw-p 00000000 00:00 0 effff7d1d000-effff7d1e000 ---p 00000000 00:00 0 effff7d1e000-effff7d26000 rw-p 00000000 00:00 0 effff7d26000-effff7d27000 ---p 00000000 00:00 0 effff7d27000-effff7d2b000 rw-p 00000000 00:00 0 effff7d2b000-effff7d2c000 ---p 00000000 00:00 0 effff7d2c000-effff7d34000 rw-p 00000000 00:00 0 effff7d34000-effff7d35000 ---p 00000000 00:00 0 effff7d35000-effff7d3d000 rw-p 00000000 00:00 0 effff7d3d000-effff7d3e000 ---p 00000000 00:00 0 effff7d3e000-effff7d42000 rw-p 00000000 00:00 0 effff7d42000-effff7d43000 ---p 00000000 00:00 0 effff7d43000-effff7de7000 rw-p 00000000 00:00 0 effff7de7000-effff7de8000 ---p 00000000 00:00 0 effff7de8000-effff7dec000 rw-p 00000000 00:00 0 effff7dec000-effff7ded000 ---p 00000000 00:00 0 effff7ded000-effff7ff8000 rw-p 00000000 00:00 0 effff7ff8000-efffffff8000 rwxp 00000000 00:00 0 efffffff8000-efffffff9000 rw-p 00000000 00:00 0 efffffff9000-efffffffa000 ---p 00000000 00:00 0 efffffffa000-f0001685e000 rw-p 00000000 00:00 0 ffff8be00000-ffff8be03000 r-xp 00000000 00:00 0 ffff8be03000-ffffabbc0000 rwxp 00000000 00:00 0 ffffabbc0000-ffffabdc4000 r-xp 02086000 00:c4 735656 /usr/local/bin/node ffffabdc4000-ffffabe00000 rwxp 00000000 00:00 0 ffffabfbc000-ffffabfbe000 r--p 00000000 00:00 0 [vvar] ffffabfbe000-ffffabfbf000 r-xp 00000000 00:00 0 [vdso] ffffe4311000-ffffe4332000 rw-p 00000000 00:00 0 [stack] recreate-proc-self-maps.sh000077500000000000000000000021541524057645600347450ustar00rootroot00000000000000open-telemetry-opentelemetry-injector-ea9dca1/unit-test-assets/proc-self-maps#!/usr/bin/env bash # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" create_proc_maps() { local image="$1" local platform="$2" local output_file="$3" rm -f proc-self-maps docker run \ --rm \ --platform "$platform" \ -v "$(pwd):/workspace" \ "$image" \ node /workspace/copy-proc-self-maps.js mv -f proc-self-maps "$output_file" } # musl/x86_64 create_proc_maps \ node:24-alpine3.22 \ linux/x86_64 \ maps-musl-x86_64 # musl/arm64 create_proc_maps \ node:24-alpine3.22 \ linux/arm64 \ maps-musl-arm64 # glibc/x86_64 create_proc_maps \ node:24-bookworm-slim \ linux/x86_64 \ maps-glibc-x86_64 # glibc/arm64 create_proc_maps \ node:24-bookworm-slim \ linux/arm64 \ maps-glibc-arm64 # glibc/x86_64 (bullseye, maps file does not mention libc.so.6) create_proc_maps \ node:24-bullseye-slim \ linux/x86_64 \ maps-glibc-x86_64-bullseye # glibc/arm64 (bullseye, maps file does not mention libc.so.6) create_proc_maps \ node:24-bullseye-slim \ linux/arm64 \ maps-glibc-arm64-bullseye open-telemetry-opentelemetry-injector-ea9dca1/zig-version000066400000000000000000000002171524057645600241560ustar00rootroot00000000000000# See https://ziglang.org/download/ for available versions. # Keep this in sync with build.zig.zon -> .minimum_zig_version. ZIG_VERSION=0.15.2